Prevent last admin from leaving without selecting new admin.
parent
b10fc6a0b0
commit
ae2b6e4d7a
|
@ -504,6 +504,9 @@
|
|||
<activity android:name=".groups.ui.creategroup.details.AddGroupDetailsActivity"
|
||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar" />
|
||||
|
||||
<activity android:name=".groups.ui.chooseadmin.ChooseNewAdminActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
||||
|
||||
<service android:enabled="true" android:name="org.thoughtcrime.securesms.service.WebRtcCallService"/>
|
||||
<service android:enabled="true" android:name=".service.ApplicationMigrationService"/>
|
||||
<service android:enabled="true" android:exported="false" android:name=".service.KeyCachingService"/>
|
||||
|
|
|
@ -1203,10 +1203,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
|||
return;
|
||||
}
|
||||
|
||||
LeaveGroupDialog.handleLeavePushGroup(ConversationActivity.this,
|
||||
getLifecycle(),
|
||||
getRecipient().requireGroupId().requirePush(),
|
||||
null);
|
||||
LeaveGroupDialog.handleLeavePushGroup(this, getRecipient().requireGroupId().requirePush(), this::finish);
|
||||
}
|
||||
|
||||
private void handleManageGroup() {
|
||||
|
|
|
@ -139,6 +139,16 @@ public final class GroupManager {
|
|||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static void addMemberAdminsAndLeaveGroup(@NonNull Context context, @NonNull GroupId.V2 groupId, @NonNull Collection<RecipientId> newAdmins)
|
||||
throws GroupChangeBusyException, GroupChangeFailedException, IOException, GroupInsufficientRightsException, GroupNotAMemberException
|
||||
{
|
||||
try (GroupManagerV2.GroupEditor edit = new GroupManagerV2(context).edit(groupId.requireV2())) {
|
||||
edit.addMemberAdminsAndLeaveGroup(newAdmins);
|
||||
Log.i(TAG, "Left group " + groupId);
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static void ejectFromGroup(@NonNull Context context, @NonNull GroupId.V2 groupId, @NonNull Recipient recipient)
|
||||
throws GroupChangeBusyException, GroupChangeFailedException, GroupInsufficientRightsException, GroupNotAMemberException, IOException
|
||||
|
|
|
@ -6,6 +6,7 @@ import androidx.annotation.NonNull;
|
|||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
|
||||
import org.signal.storageservice.protos.groups.AccessControl;
|
||||
|
@ -296,6 +297,17 @@ final class GroupManagerV2 {
|
|||
return commitChangeWithConflictResolution(groupOperations.createRemoveMembersChange(Collections.singleton(recipient.getUuid().get())));
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
@NonNull GroupManager.GroupActionResult addMemberAdminsAndLeaveGroup(Collection<RecipientId> newAdmins)
|
||||
throws GroupChangeFailedException, GroupNotAMemberException, GroupInsufficientRightsException, IOException
|
||||
{
|
||||
Recipient self = Recipient.self();
|
||||
List<UUID> newAdminRecipients = Stream.of(newAdmins).map(id -> Recipient.resolved(id).getUuid().get()).toList();
|
||||
|
||||
return commitChangeWithConflictResolution(groupOperations.createLeaveAndPromoteMembersToAdmin(self.getUuid().get(),
|
||||
newAdminRecipients));
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
@Nullable GroupManager.GroupActionResult updateSelfProfileKeyInGroup()
|
||||
throws GroupChangeFailedException, GroupInsufficientRightsException, IOException, GroupNotAMemberException
|
||||
|
|
|
@ -37,9 +37,10 @@ public final class LiveGroup {
|
|||
.thenComparing(HAS_DISPLAY_NAME)
|
||||
.thenComparing(ALPHABETICAL);
|
||||
|
||||
private final GroupDatabase groupDatabase;
|
||||
private final LiveData<Recipient> recipient;
|
||||
private final LiveData<GroupDatabase.GroupRecord> groupRecord;
|
||||
private final GroupDatabase groupDatabase;
|
||||
private final LiveData<Recipient> recipient;
|
||||
private final LiveData<GroupDatabase.GroupRecord> groupRecord;
|
||||
private final LiveData<List<GroupMemberEntry.FullMember>> fullMembers;
|
||||
|
||||
public LiveGroup(@NonNull GroupId groupId) {
|
||||
Context context = ApplicationDependencies.getApplication();
|
||||
|
@ -47,7 +48,15 @@ public final class LiveGroup {
|
|||
|
||||
this.groupDatabase = DatabaseFactory.getGroupDatabase(context);
|
||||
this.recipient = Transformations.switchMap(liveRecipient, LiveRecipient::getLiveData);
|
||||
this.groupRecord = LiveDataUtil.filterNotNull(LiveDataUtil.mapAsync(recipient, groupRecipient-> groupDatabase.getGroup(groupRecipient.getId()).orNull()));
|
||||
this.groupRecord = LiveDataUtil.filterNotNull(LiveDataUtil.mapAsync(recipient, groupRecipient -> groupDatabase.getGroup(groupRecipient.getId()).orNull()));
|
||||
this.fullMembers = LiveDataUtil.mapAsync(groupRecord,
|
||||
g -> Stream.of(g.getMembers())
|
||||
.map(m -> {
|
||||
Recipient recipient = Recipient.resolved(m);
|
||||
return new GroupMemberEntry.FullMember(recipient, g.isAdmin(recipient));
|
||||
})
|
||||
.sorted(MEMBER_ORDER)
|
||||
.toList());
|
||||
|
||||
SignalExecutors.BOUNDED.execute(() -> liveRecipient.postValue(Recipient.externalGroup(context, groupId).live()));
|
||||
}
|
||||
|
@ -90,15 +99,15 @@ public final class LiveGroup {
|
|||
return Transformations.map(groupRecord, GroupDatabase.GroupRecord::getAttributesAccessControl);
|
||||
}
|
||||
|
||||
public LiveData<List<GroupMemberEntry.FullMember>> getNonAdminFullMembers() {
|
||||
return Transformations.map(fullMembers,
|
||||
members -> Stream.of(members)
|
||||
.filterNot(GroupMemberEntry.FullMember::isAdmin)
|
||||
.toList());
|
||||
}
|
||||
|
||||
public LiveData<List<GroupMemberEntry.FullMember>> getFullMembers() {
|
||||
return LiveDataUtil.mapAsync(groupRecord,
|
||||
g -> Stream.of(g.getMembers())
|
||||
.map(m -> {
|
||||
Recipient recipient = Recipient.resolved(m);
|
||||
return new GroupMemberEntry.FullMember(recipient, g.isAdmin(recipient));
|
||||
})
|
||||
.sorted(MEMBER_ORDER)
|
||||
.toList());
|
||||
return fullMembers;
|
||||
}
|
||||
|
||||
public LiveData<Integer> getExpireMessages() {
|
||||
|
|
|
@ -4,6 +4,7 @@ import android.content.Context;
|
|||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
|
@ -17,10 +18,11 @@ import org.thoughtcrime.securesms.components.AvatarImageView;
|
|||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.LifecycleRecyclerAdapter;
|
||||
import org.thoughtcrime.securesms.util.LifecycleViewHolder;
|
||||
import org.thoughtcrime.securesms.util.ThemeUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
final class GroupMemberListAdapter extends LifecycleRecyclerAdapter<GroupMemberListAdapter.ViewHolder> {
|
||||
|
||||
|
@ -29,11 +31,20 @@ final class GroupMemberListAdapter extends LifecycleRecyclerAdapter<GroupMemberL
|
|||
private static final int OTHER_INVITE_PENDING_COUNT = 2;
|
||||
private static final int NEW_GROUP_CANDIDATE = 3;
|
||||
|
||||
private final ArrayList<GroupMemberEntry> data = new ArrayList<>();
|
||||
private final List<GroupMemberEntry> data = new ArrayList<>();
|
||||
private final Set<GroupMemberEntry> selection = new HashSet<>();
|
||||
private final SelectionChangeListener selectionChangeListener = new SelectionChangeListener();
|
||||
|
||||
@Nullable private AdminActionsListener adminActionsListener;
|
||||
@Nullable private RecipientClickListener recipientClickListener;
|
||||
@Nullable private RecipientLongClickListener recipientLongClickListener;
|
||||
private final boolean selectable;
|
||||
|
||||
@Nullable private AdminActionsListener adminActionsListener;
|
||||
@Nullable private RecipientClickListener recipientClickListener;
|
||||
@Nullable private RecipientLongClickListener recipientLongClickListener;
|
||||
@Nullable private RecipientSelectionChangeListener recipientSelectionChangeListener;
|
||||
|
||||
GroupMemberListAdapter(boolean selectable) {
|
||||
this.selectable = selectable;
|
||||
}
|
||||
|
||||
void updateData(@NonNull List<? extends GroupMemberEntry> recipients) {
|
||||
if (data.isEmpty()) {
|
||||
|
@ -43,6 +54,21 @@ final class GroupMemberListAdapter extends LifecycleRecyclerAdapter<GroupMemberL
|
|||
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffCallback(data, recipients));
|
||||
data.clear();
|
||||
data.addAll(recipients);
|
||||
|
||||
if (!selection.isEmpty()) {
|
||||
Set<GroupMemberEntry> newSelection = new HashSet<>();
|
||||
for (GroupMemberEntry entry : recipients) {
|
||||
if (selection.contains(entry)) {
|
||||
newSelection.add(entry);
|
||||
}
|
||||
}
|
||||
selection.clear();
|
||||
selection.addAll(newSelection);
|
||||
if (recipientSelectionChangeListener != null) {
|
||||
recipientSelectionChangeListener.onSelectionChanged(selection);
|
||||
}
|
||||
}
|
||||
|
||||
diffResult.dispatchUpdatesTo(this);
|
||||
}
|
||||
}
|
||||
|
@ -55,22 +81,26 @@ final class GroupMemberListAdapter extends LifecycleRecyclerAdapter<GroupMemberL
|
|||
.inflate(R.layout.group_recipient_list_item, parent, false),
|
||||
recipientClickListener,
|
||||
recipientLongClickListener,
|
||||
adminActionsListener);
|
||||
adminActionsListener,
|
||||
selectionChangeListener);
|
||||
case OWN_INVITE_PENDING:
|
||||
return new OwnInvitePendingMemberViewHolder(LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.group_recipient_list_item, parent, false),
|
||||
recipientClickListener,
|
||||
recipientLongClickListener,
|
||||
adminActionsListener);
|
||||
adminActionsListener,
|
||||
selectionChangeListener);
|
||||
case OTHER_INVITE_PENDING_COUNT:
|
||||
return new UnknownPendingMemberCountViewHolder(LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.group_recipient_list_item, parent, false),
|
||||
adminActionsListener);
|
||||
adminActionsListener,
|
||||
selectionChangeListener);
|
||||
case NEW_GROUP_CANDIDATE:
|
||||
return new NewGroupInviteeViewHolder(LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.group_new_candidate_recipient_list_item, parent, false),
|
||||
recipientClickListener,
|
||||
recipientLongClickListener);
|
||||
recipientLongClickListener,
|
||||
selectionChangeListener);
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
@ -88,9 +118,14 @@ final class GroupMemberListAdapter extends LifecycleRecyclerAdapter<GroupMemberL
|
|||
this.recipientLongClickListener = recipientLongClickListener;
|
||||
}
|
||||
|
||||
void setRecipientSelectionChangeListener(@Nullable RecipientSelectionChangeListener recipientSelectionChangeListener) {
|
||||
this.recipientSelectionChangeListener = recipientSelectionChangeListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||
holder.bind(data.get(position));
|
||||
GroupMemberEntry entry = data.get(position);
|
||||
holder.bind(entry, selection.contains(entry));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -120,10 +155,12 @@ final class GroupMemberListAdapter extends LifecycleRecyclerAdapter<GroupMemberL
|
|||
final Context context;
|
||||
final AvatarImageView avatar;
|
||||
final TextView recipient;
|
||||
final CheckBox selected;
|
||||
final PopupMenuView popupMenu;
|
||||
final View popupMenuContainer;
|
||||
final ProgressBar busyProgress;
|
||||
final View admin;
|
||||
final SelectionChangeListener selectionChangeListener;
|
||||
@Nullable final RecipientClickListener recipientClickListener;
|
||||
@Nullable final AdminActionsListener adminActionsListener;
|
||||
@Nullable final RecipientLongClickListener recipientLongClickListener;
|
||||
|
@ -131,13 +168,15 @@ final class GroupMemberListAdapter extends LifecycleRecyclerAdapter<GroupMemberL
|
|||
ViewHolder(@NonNull View itemView,
|
||||
@Nullable RecipientClickListener recipientClickListener,
|
||||
@Nullable RecipientLongClickListener recipientLongClickListener,
|
||||
@Nullable AdminActionsListener adminActionsListener)
|
||||
@Nullable AdminActionsListener adminActionsListener,
|
||||
@NonNull SelectionChangeListener selectionChangeListener)
|
||||
{
|
||||
super(itemView);
|
||||
|
||||
this.context = itemView.getContext();
|
||||
this.avatar = itemView.findViewById(R.id.recipient_avatar);
|
||||
this.recipient = itemView.findViewById(R.id.recipient_name);
|
||||
this.selected = itemView.findViewById(R.id.recipient_selected);
|
||||
this.popupMenu = itemView.findViewById(R.id.popupMenu);
|
||||
this.popupMenuContainer = itemView.findViewById(R.id.popupMenuProgressContainer);
|
||||
this.busyProgress = itemView.findViewById(R.id.menuBusyProgress);
|
||||
|
@ -145,6 +184,7 @@ final class GroupMemberListAdapter extends LifecycleRecyclerAdapter<GroupMemberL
|
|||
this.recipientClickListener = recipientClickListener;
|
||||
this.recipientLongClickListener = recipientLongClickListener;
|
||||
this.adminActionsListener = adminActionsListener;
|
||||
this.selectionChangeListener = selectionChangeListener;
|
||||
}
|
||||
|
||||
void bindRecipient(@NonNull Recipient recipient) {
|
||||
|
@ -166,20 +206,22 @@ final class GroupMemberListAdapter extends LifecycleRecyclerAdapter<GroupMemberL
|
|||
|
||||
this.itemView.setEnabled(true);
|
||||
this.itemView.setOnClickListener(v -> {
|
||||
if (recipientClickListener != null && getAdapterPosition() != RecyclerView.NO_POSITION) {
|
||||
recipientClickListener.onClick(recipient);
|
||||
if (getAdapterPosition() != RecyclerView.NO_POSITION) {
|
||||
if (recipientClickListener != null) {
|
||||
recipientClickListener.onClick(recipient);
|
||||
}
|
||||
selectionChangeListener.onSelectionChange(getAdapterPosition(), !selected.isChecked());
|
||||
}
|
||||
});
|
||||
this.itemView.setOnLongClickListener(v -> {
|
||||
if (recipientLongClickListener != null && getAdapterPosition() != RecyclerView.NO_POSITION) {
|
||||
return recipientLongClickListener.onLongClick(recipient);
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
void bind(@NonNull GroupMemberEntry memberEntry) {
|
||||
void bind(@NonNull GroupMemberEntry memberEntry, boolean isSelected) {
|
||||
busyProgress.setVisibility(View.GONE);
|
||||
admin.setVisibility(View.GONE);
|
||||
hideMenu();
|
||||
|
@ -190,6 +232,8 @@ final class GroupMemberListAdapter extends LifecycleRecyclerAdapter<GroupMemberL
|
|||
busyProgress.setVisibility(busy ? View.VISIBLE : View.GONE);
|
||||
popupMenu.setVisibility(busy ? View.GONE : View.VISIBLE);
|
||||
});
|
||||
|
||||
selected.setChecked(isSelected);
|
||||
}
|
||||
|
||||
void hideMenu() {
|
||||
|
@ -208,14 +252,15 @@ final class GroupMemberListAdapter extends LifecycleRecyclerAdapter<GroupMemberL
|
|||
FullMemberViewHolder(@NonNull View itemView,
|
||||
@Nullable RecipientClickListener recipientClickListener,
|
||||
@Nullable RecipientLongClickListener recipientLongClickListener,
|
||||
@Nullable AdminActionsListener adminActionsListener)
|
||||
@Nullable AdminActionsListener adminActionsListener,
|
||||
@NonNull SelectionChangeListener selectionChangeListener)
|
||||
{
|
||||
super(itemView, recipientClickListener, recipientLongClickListener, adminActionsListener);
|
||||
super(itemView, recipientClickListener, recipientLongClickListener, adminActionsListener, selectionChangeListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
void bind(@NonNull GroupMemberEntry memberEntry) {
|
||||
super.bind(memberEntry);
|
||||
void bind(@NonNull GroupMemberEntry memberEntry, boolean isSelected) {
|
||||
super.bind(memberEntry, isSelected);
|
||||
|
||||
GroupMemberEntry.FullMember fullMember = (GroupMemberEntry.FullMember) memberEntry;
|
||||
|
||||
|
@ -231,16 +276,17 @@ final class GroupMemberListAdapter extends LifecycleRecyclerAdapter<GroupMemberL
|
|||
|
||||
NewGroupInviteeViewHolder(@NonNull View itemView,
|
||||
@Nullable RecipientClickListener recipientClickListener,
|
||||
@Nullable RecipientLongClickListener recipientLongClickListener)
|
||||
@Nullable RecipientLongClickListener recipientLongClickListener,
|
||||
@NonNull SelectionChangeListener selectionChangeListener)
|
||||
{
|
||||
super(itemView, recipientClickListener, recipientLongClickListener, null);
|
||||
super(itemView, recipientClickListener, recipientLongClickListener, null, selectionChangeListener);
|
||||
|
||||
smsContact = itemView.findViewById(R.id.sms_contact);
|
||||
smsWarning = itemView.findViewById(R.id.sms_warning);
|
||||
}
|
||||
|
||||
@Override
|
||||
void bind(@NonNull GroupMemberEntry memberEntry) {
|
||||
void bind(@NonNull GroupMemberEntry memberEntry, boolean isSelected) {
|
||||
GroupMemberEntry.NewGroupCandidate newGroupCandidate = (GroupMemberEntry.NewGroupCandidate) memberEntry;
|
||||
|
||||
bindRecipient(newGroupCandidate.getMember());
|
||||
|
@ -256,16 +302,17 @@ final class GroupMemberListAdapter extends LifecycleRecyclerAdapter<GroupMemberL
|
|||
final static class OwnInvitePendingMemberViewHolder extends ViewHolder {
|
||||
|
||||
OwnInvitePendingMemberViewHolder(@NonNull View itemView,
|
||||
@Nullable RecipientClickListener recipientClickListener,
|
||||
@Nullable RecipientLongClickListener recipientLongClickListener,
|
||||
@Nullable AdminActionsListener adminActionsListener)
|
||||
@Nullable RecipientClickListener recipientClickListener,
|
||||
@Nullable RecipientLongClickListener recipientLongClickListener,
|
||||
@Nullable AdminActionsListener adminActionsListener,
|
||||
@NonNull SelectionChangeListener selectionChangeListener)
|
||||
{
|
||||
super(itemView, recipientClickListener, recipientLongClickListener, adminActionsListener);
|
||||
super(itemView, recipientClickListener, recipientLongClickListener, adminActionsListener, selectionChangeListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
void bind(@NonNull GroupMemberEntry memberEntry) {
|
||||
super.bind(memberEntry);
|
||||
void bind(@NonNull GroupMemberEntry memberEntry, boolean isSelected) {
|
||||
super.bind(memberEntry, isSelected);
|
||||
|
||||
GroupMemberEntry.PendingMember pendingMember = (GroupMemberEntry.PendingMember) memberEntry;
|
||||
|
||||
|
@ -288,13 +335,16 @@ final class GroupMemberListAdapter extends LifecycleRecyclerAdapter<GroupMemberL
|
|||
|
||||
final static class UnknownPendingMemberCountViewHolder extends ViewHolder {
|
||||
|
||||
UnknownPendingMemberCountViewHolder(@NonNull View itemView, @Nullable AdminActionsListener adminActionsListener) {
|
||||
super(itemView, null, null, adminActionsListener);
|
||||
UnknownPendingMemberCountViewHolder(@NonNull View itemView,
|
||||
@Nullable AdminActionsListener adminActionsListener,
|
||||
@NonNull SelectionChangeListener selectionChangeListener)
|
||||
{
|
||||
super(itemView, null, null, adminActionsListener, selectionChangeListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
void bind(@NonNull GroupMemberEntry memberEntry) {
|
||||
super.bind(memberEntry);
|
||||
void bind(@NonNull GroupMemberEntry memberEntry, boolean isSelected) {
|
||||
super.bind(memberEntry, isSelected);
|
||||
GroupMemberEntry.UnknownPendingMemberCount pendingMembers = (GroupMemberEntry.UnknownPendingMemberCount) memberEntry;
|
||||
|
||||
Recipient inviter = pendingMembers.getInviter();
|
||||
|
@ -327,6 +377,23 @@ final class GroupMemberListAdapter extends LifecycleRecyclerAdapter<GroupMemberL
|
|||
}
|
||||
}
|
||||
|
||||
private final class SelectionChangeListener {
|
||||
void onSelectionChange(int position, boolean isChecked) {
|
||||
if (selectable) {
|
||||
if (isChecked) {
|
||||
selection.add(data.get(position));
|
||||
} else {
|
||||
selection.remove(data.get(position));
|
||||
}
|
||||
notifyItemChanged(position);
|
||||
|
||||
if (recipientSelectionChangeListener != null) {
|
||||
recipientSelectionChangeListener.onSelectionChanged(selection);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final static class DiffCallback extends DiffUtil.Callback {
|
||||
private final List<? extends GroupMemberEntry> oldData;
|
||||
private final List<? extends GroupMemberEntry> newData;
|
||||
|
|
|
@ -15,8 +15,8 @@ import java.util.List;
|
|||
|
||||
public final class GroupMemberListView extends RecyclerView {
|
||||
|
||||
private final GroupMemberListAdapter membersAdapter = new GroupMemberListAdapter();
|
||||
private int maxHeight;
|
||||
private GroupMemberListAdapter membersAdapter;
|
||||
private int maxHeight;
|
||||
|
||||
public GroupMemberListView(@NonNull Context context) {
|
||||
super(context);
|
||||
|
@ -38,17 +38,20 @@ public final class GroupMemberListView extends RecyclerView {
|
|||
setHasFixedSize(true);
|
||||
}
|
||||
|
||||
setLayoutManager(new LinearLayoutManager(context));
|
||||
setAdapter(membersAdapter);
|
||||
|
||||
boolean selectable = false;
|
||||
if (attrs != null) {
|
||||
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.GroupMemberListView, 0, 0);
|
||||
try {
|
||||
maxHeight = typedArray.getDimensionPixelSize(R.styleable.GroupMemberListView_maxHeight, 0);
|
||||
selectable = typedArray.getBoolean(R.styleable.GroupMemberListView_selectable, false);
|
||||
} finally {
|
||||
typedArray.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
membersAdapter = new GroupMemberListAdapter(selectable);
|
||||
setLayoutManager(new LinearLayoutManager(context));
|
||||
setAdapter(membersAdapter);
|
||||
}
|
||||
|
||||
public void setAdminActionsListener(@Nullable AdminActionsListener adminActionsListener) {
|
||||
|
@ -63,6 +66,10 @@ public final class GroupMemberListView extends RecyclerView {
|
|||
membersAdapter.setRecipientLongClickListener(listener);
|
||||
}
|
||||
|
||||
public void setRecipientSelectionChangeListener(@Nullable RecipientSelectionChangeListener listener) {
|
||||
membersAdapter.setRecipientSelectionChangeListener(listener);
|
||||
}
|
||||
|
||||
public void setMembers(@NonNull List<? extends GroupMemberEntry> recipients) {
|
||||
membersAdapter.updateData(recipients);
|
||||
}
|
||||
|
|
|
@ -1,60 +1,115 @@
|
|||
package org.thoughtcrime.securesms.groups.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.lifecycle.Lifecycle;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||
import org.thoughtcrime.securesms.groups.GroupChangeBusyException;
|
||||
import org.thoughtcrime.securesms.groups.GroupChangeFailedException;
|
||||
import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.groups.GroupManager;
|
||||
import org.thoughtcrime.securesms.groups.ui.chooseadmin.ChooseNewAdminActivity;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
public final class LeaveGroupDialog {
|
||||
|
||||
private static final String TAG = Log.tag(LeaveGroupDialog.class);
|
||||
|
||||
private LeaveGroupDialog() {
|
||||
@NonNull private final FragmentActivity activity;
|
||||
@NonNull private final GroupId.Push groupId;
|
||||
@Nullable private final Runnable onSuccess;
|
||||
|
||||
public static void handleLeavePushGroup(@NonNull FragmentActivity activity,
|
||||
@NonNull GroupId.Push groupId,
|
||||
@Nullable Runnable onSuccess) {
|
||||
new LeaveGroupDialog(activity, groupId, onSuccess).show();
|
||||
}
|
||||
|
||||
public static void handleLeavePushGroup(@NonNull Context context,
|
||||
@NonNull Lifecycle lifecycle,
|
||||
@NonNull GroupId.Push groupId,
|
||||
@Nullable Runnable onSuccess)
|
||||
{
|
||||
new AlertDialog.Builder(context)
|
||||
.setTitle(context.getString(R.string.ConversationActivity_leave_group))
|
||||
private LeaveGroupDialog(@NonNull FragmentActivity activity,
|
||||
@NonNull GroupId.Push groupId,
|
||||
@Nullable Runnable onSuccess) {
|
||||
this.activity = activity;
|
||||
this.groupId = groupId;
|
||||
this.onSuccess = onSuccess;
|
||||
}
|
||||
|
||||
public void show() {
|
||||
if (!groupId.isV2()) {
|
||||
showLeaveDialog();
|
||||
return;
|
||||
}
|
||||
|
||||
SimpleTask.run(activity.getLifecycle(), () -> {
|
||||
GroupDatabase.V2GroupProperties groupProperties = DatabaseFactory.getGroupDatabase(activity)
|
||||
.getGroup(groupId)
|
||||
.transform(GroupDatabase.GroupRecord::requireV2GroupProperties)
|
||||
.orNull();
|
||||
|
||||
if (groupProperties != null && groupProperties.isAdmin(Recipient.self())) {
|
||||
List<Recipient> otherMemberRecipients = groupProperties.getMemberRecipients(GroupDatabase.MemberSet.FULL_MEMBERS_EXCLUDING_SELF);
|
||||
long otherAdminsCount = Stream.of(otherMemberRecipients).filter(groupProperties::isAdmin).count();
|
||||
|
||||
return otherAdminsCount == 0 && !otherMemberRecipients.isEmpty();
|
||||
}
|
||||
|
||||
return false;
|
||||
}, mustSelectNewAdmin -> {
|
||||
if (mustSelectNewAdmin) {
|
||||
showSelectNewAdminDialog();
|
||||
} else {
|
||||
showLeaveDialog();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void showSelectNewAdminDialog() {
|
||||
new AlertDialog.Builder(activity)
|
||||
.setTitle(R.string.ConversationActivity_choose_new_admin)
|
||||
.setMessage(R.string.ConversationActivity_before_you_leave_you_must_choose_at_least_one_new_admin_for_this_group)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(R.string.ConversationActivity_choose_admin, (d,w) -> activity.startActivity(ChooseNewAdminActivity.createIntent(activity, groupId.requireV2())))
|
||||
.show();
|
||||
}
|
||||
|
||||
private void showLeaveDialog() {
|
||||
new AlertDialog.Builder(activity)
|
||||
.setTitle(R.string.ConversationActivity_leave_group)
|
||||
.setIconAttribute(R.attr.dialog_info_icon)
|
||||
.setCancelable(true)
|
||||
.setMessage(context.getString(R.string.ConversationActivity_are_you_sure_you_want_to_leave_this_group))
|
||||
.setPositiveButton(R.string.yes, (dialog, which) ->
|
||||
SimpleTask.run(
|
||||
lifecycle,
|
||||
() -> {
|
||||
try {
|
||||
GroupManager.leaveGroup(context, groupId);
|
||||
return true;
|
||||
} catch (GroupChangeFailedException | GroupChangeBusyException | IOException e) {
|
||||
Log.w(TAG, e);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
(success) -> {
|
||||
if (success) {
|
||||
if (onSuccess != null) onSuccess.run();
|
||||
} else {
|
||||
Toast.makeText(context, R.string.ConversationActivity_error_leaving_group, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}))
|
||||
.setMessage(R.string.ConversationActivity_are_you_sure_you_want_to_leave_this_group)
|
||||
.setPositiveButton(R.string.yes, (dialog, which) -> SimpleTask.run(activity.getLifecycle(), this::leaveGroup, this::handleLeaveGroupResult))
|
||||
.setNegativeButton(R.string.no, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
private boolean leaveGroup() {
|
||||
try {
|
||||
GroupManager.leaveGroup(activity, groupId);
|
||||
return true;
|
||||
} catch (GroupChangeFailedException | GroupChangeBusyException | IOException e) {
|
||||
Log.w(TAG, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void handleLeaveGroupResult(boolean success) {
|
||||
if (success) {
|
||||
if (onSuccess != null) onSuccess.run();
|
||||
} else {
|
||||
Toast.makeText(activity, R.string.ConversationActivity_error_leaving_group, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
package org.thoughtcrime.securesms.groups.ui;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public interface RecipientSelectionChangeListener {
|
||||
void onSelectionChanged(@NonNull Set<GroupMemberEntry> selection);
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
package org.thoughtcrime.securesms.groups.ui.chooseadmin;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.lifecycle.ViewModelProviders;
|
||||
|
||||
import com.annimon.stream.Collectors;
|
||||
import com.annimon.stream.Stream;
|
||||
import com.dd.CircularProgressButton;
|
||||
|
||||
import org.thoughtcrime.securesms.MainActivity;
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.groups.BadGroupIdException;
|
||||
import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.groups.ui.GroupErrors;
|
||||
import org.thoughtcrime.securesms.groups.ui.GroupMemberEntry;
|
||||
import org.thoughtcrime.securesms.groups.ui.GroupMemberListView;
|
||||
import org.thoughtcrime.securesms.groups.ui.chooseadmin.ChooseNewAdminRepository.UpdateResult;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public final class ChooseNewAdminActivity extends PassphraseRequiredActivity {
|
||||
|
||||
private static final String EXTRA_GROUP_ID = "group_id";
|
||||
|
||||
private ChooseNewAdminViewModel viewModel;
|
||||
private GroupMemberListView groupList;
|
||||
private CircularProgressButton done;
|
||||
private GroupId.V2 groupId;
|
||||
|
||||
private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme();
|
||||
|
||||
public static Intent createIntent(@NonNull Context context, @NonNull GroupId.V2 groupId) {
|
||||
Intent intent = new Intent(context, ChooseNewAdminActivity.class);
|
||||
intent.putExtra(EXTRA_GROUP_ID, groupId.toString());
|
||||
return intent;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreCreate() {
|
||||
dynamicTheme.onCreate(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState, boolean ready) {
|
||||
super.onCreate(savedInstanceState, ready);
|
||||
setContentView(R.layout.choose_new_admin_activity);
|
||||
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
//noinspection ConstantConditions
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
try {
|
||||
groupId = GroupId.parse(Objects.requireNonNull(getIntent().getStringExtra(EXTRA_GROUP_ID))).requireV2();
|
||||
} catch (BadGroupIdException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
|
||||
groupList = findViewById(R.id.choose_new_admin_group_list);
|
||||
done = findViewById(R.id.choose_new_admin_done);
|
||||
done.setIndeterminateProgressMode(true);
|
||||
|
||||
initializeViewModel();
|
||||
|
||||
groupList.setRecipientSelectionChangeListener(selection -> viewModel.setSelection(Stream.of(selection)
|
||||
.select(GroupMemberEntry.FullMember.class)
|
||||
.collect(Collectors.toSet())));
|
||||
|
||||
done.setOnClickListener(v -> {
|
||||
done.setClickable(false);
|
||||
done.setProgress(50);
|
||||
viewModel.updateAdminsAndLeave(this::handleUpdateAndLeaveResult);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
dynamicTheme.onResume(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void initializeViewModel() {
|
||||
viewModel = ViewModelProviders.of(this, new ChooseNewAdminViewModel.Factory(groupId)).get(ChooseNewAdminViewModel.class);
|
||||
|
||||
viewModel.getNonAdminFullMembers().observe(this, groupList::setMembers);
|
||||
viewModel.getSelection().observe(this, selection -> done.setVisibility(selection.isEmpty() ? View.GONE : View.VISIBLE));
|
||||
}
|
||||
|
||||
private void handleUpdateAndLeaveResult(@NonNull UpdateResult updateResult) {
|
||||
if (updateResult.isSuccess()) {
|
||||
String title = Recipient.externalGroup(this, groupId).getDisplayName(this);
|
||||
Toast.makeText(this, getString(R.string.ChooseNewAdminActivity_you_left, title), Toast.LENGTH_LONG).show();
|
||||
startActivity(new Intent(this, MainActivity.class));
|
||||
finish();
|
||||
} else {
|
||||
done.setClickable(true);
|
||||
done.setProgress(0);
|
||||
//noinspection ConstantConditions
|
||||
Toast.makeText(this, GroupErrors.getUserDisplayMessage(updateResult.getFailureReason()), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
package org.thoughtcrime.securesms.groups.ui.chooseadmin;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
import androidx.core.util.Consumer;
|
||||
|
||||
import org.thoughtcrime.securesms.groups.GroupChangeBusyException;
|
||||
import org.thoughtcrime.securesms.groups.GroupChangeFailedException;
|
||||
import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.groups.GroupInsufficientRightsException;
|
||||
import org.thoughtcrime.securesms.groups.GroupManager;
|
||||
import org.thoughtcrime.securesms.groups.GroupNotAMemberException;
|
||||
import org.thoughtcrime.securesms.groups.ui.GroupChangeFailureReason;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
public final class ChooseNewAdminRepository {
|
||||
private Application context;
|
||||
|
||||
ChooseNewAdminRepository(@NonNull Application context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
@NonNull UpdateResult updateAdminsAndLeave(@NonNull GroupId.V2 groupId, @NonNull List<RecipientId> newAdminIds) {
|
||||
try {
|
||||
GroupManager.addMemberAdminsAndLeaveGroup(context, groupId, newAdminIds);
|
||||
return new UpdateResult();
|
||||
} catch (GroupInsufficientRightsException e) {
|
||||
return new UpdateResult(GroupChangeFailureReason.NO_RIGHTS);
|
||||
} catch (GroupNotAMemberException e) {
|
||||
return new UpdateResult(GroupChangeFailureReason.NOT_A_MEMBER);
|
||||
} catch (GroupChangeFailedException | GroupChangeBusyException | IOException e) {
|
||||
return new UpdateResult(GroupChangeFailureReason.OTHER);
|
||||
}
|
||||
}
|
||||
|
||||
static final class UpdateResult {
|
||||
final @Nullable GroupChangeFailureReason failureReason;
|
||||
|
||||
UpdateResult() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
UpdateResult(@Nullable GroupChangeFailureReason failureReason) {
|
||||
this.failureReason = failureReason;
|
||||
}
|
||||
|
||||
boolean isSuccess() {
|
||||
return failureReason == null;
|
||||
}
|
||||
|
||||
@Nullable GroupChangeFailureReason getFailureReason() {
|
||||
return failureReason;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
package org.thoughtcrime.securesms.groups.ui.chooseadmin;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.core.util.Consumer;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.Transformations;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.groups.LiveGroup;
|
||||
import org.thoughtcrime.securesms.groups.ui.GroupErrors;
|
||||
import org.thoughtcrime.securesms.groups.ui.GroupMemberEntry;
|
||||
import org.thoughtcrime.securesms.groups.ui.chooseadmin.ChooseNewAdminRepository.UpdateResult;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
final class ChooseNewAdminViewModel extends ViewModel {
|
||||
|
||||
private final GroupId.V2 groupId;
|
||||
private final ChooseNewAdminRepository repository;
|
||||
private final LiveGroup liveGroup;
|
||||
private final MutableLiveData<Set<GroupMemberEntry.FullMember>> selection;
|
||||
|
||||
public ChooseNewAdminViewModel(@NonNull GroupId.V2 groupId, @NonNull ChooseNewAdminRepository repository) {
|
||||
this.groupId = groupId;
|
||||
this.repository = repository;
|
||||
|
||||
liveGroup = new LiveGroup(groupId);
|
||||
selection = new MutableLiveData<>(Collections.emptySet());
|
||||
}
|
||||
|
||||
@NonNull LiveData<List<GroupMemberEntry.FullMember>> getNonAdminFullMembers() {
|
||||
return liveGroup.getNonAdminFullMembers();
|
||||
}
|
||||
|
||||
@NonNull LiveData<Set<GroupMemberEntry.FullMember>> getSelection() {
|
||||
return selection;
|
||||
}
|
||||
|
||||
void setSelection(@NonNull Set<GroupMemberEntry.FullMember> selection) {
|
||||
this.selection.setValue(selection);
|
||||
}
|
||||
|
||||
void updateAdminsAndLeave(@NonNull Consumer<UpdateResult> consumer) {
|
||||
//noinspection ConstantConditions
|
||||
List<RecipientId> recipientIds = Stream.of(selection.getValue()).map(entry -> entry.getMember().getId()).toList();
|
||||
SimpleTask.run(() -> repository.updateAdminsAndLeave(groupId, recipientIds), consumer::accept);
|
||||
}
|
||||
|
||||
static final class Factory implements ViewModelProvider.Factory {
|
||||
|
||||
private final GroupId.V2 groupId;
|
||||
|
||||
Factory(@NonNull GroupId.V2 groupId) {
|
||||
this.groupId = groupId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
|
||||
//noinspection ConstantConditions
|
||||
return modelClass.cast(new ChooseNewAdminViewModel(groupId, new ChooseNewAdminRepository(ApplicationDependencies.getApplication())));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -24,6 +24,7 @@ import com.google.android.material.snackbar.Snackbar;
|
|||
|
||||
import org.thoughtcrime.securesms.AvatarPreviewActivity;
|
||||
import org.thoughtcrime.securesms.LoggingFragment;
|
||||
import org.thoughtcrime.securesms.MainActivity;
|
||||
import org.thoughtcrime.securesms.MediaPreviewActivity;
|
||||
import org.thoughtcrime.securesms.MuteDialog;
|
||||
import org.thoughtcrime.securesms.PushContactSelectionActivity;
|
||||
|
@ -234,10 +235,7 @@ public class ManageGroupFragment extends LoggingFragment {
|
|||
});
|
||||
|
||||
leaveGroup.setVisibility(groupId.isPush() ? View.VISIBLE : View.GONE);
|
||||
leaveGroup.setOnClickListener(v -> LeaveGroupDialog.handleLeavePushGroup(context,
|
||||
getLifecycle(),
|
||||
groupId.requirePush(),
|
||||
null));
|
||||
leaveGroup.setOnClickListener(v -> LeaveGroupDialog.handleLeavePushGroup(requireActivity(), groupId.requirePush(), () -> startActivity(new Intent(requireActivity(), MainActivity.class))));
|
||||
|
||||
viewModel.getDisappearingMessageTimer().observe(getViewLifecycleOwner(), string -> disappearingMessages.setText(string));
|
||||
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/toolbar_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:attr/windowBackground"
|
||||
app:elevation="0dp">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?actionBarSize"
|
||||
android:background="@android:color/transparent"
|
||||
app:title="@string/ChooseNewAdminActivity_choose_new_admin"
|
||||
app:titleTextColor="?title_text_color_primary" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<FrameLayout android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<org.thoughtcrime.securesms.groups.ui.GroupMemberListView
|
||||
android:id="@+id/choose_new_admin_group_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:selectable="true"
|
||||
tools:listitem="@layout/group_recipient_list_item" />
|
||||
|
||||
<com.dd.CircularProgressButton
|
||||
android:id="@+id/choose_new_admin_done"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:textColor="@color/white"
|
||||
app:cpb_colorIndicator="@color/white"
|
||||
app:cpb_colorProgress="?colorAccent"
|
||||
app:cpb_cornerRadius="28dp"
|
||||
app:cpb_selectorIdle="?attr/circular_progress_button_state"
|
||||
app:cpb_textIdle="@string/ChooseNewAdminActivity_done"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
|
@ -15,7 +15,20 @@
|
|||
android:layout_marginStart="16dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@tools:sample/avatars"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatCheckBox
|
||||
android:id="@+id/recipient_selected"
|
||||
android:background="?attr/contact_selection_checkbox_background"
|
||||
android:button="@null"
|
||||
android:layout_width="22dp"
|
||||
android:layout_height="22dp"
|
||||
android:focusable="false"
|
||||
android:clickable="false"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/recipient_avatar"
|
||||
app:layout_constraintEnd_toEndOf="@+id/recipient_avatar"
|
||||
tools:checked="true"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/recipient_name"
|
||||
|
|
|
@ -529,6 +529,7 @@
|
|||
|
||||
<declare-styleable name="GroupMemberListView">
|
||||
<attr name="maxHeight" />
|
||||
<attr name="selectable" format="boolean" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="WebRtcAudioOutputToggleButtonState">
|
||||
|
|
|
@ -227,6 +227,9 @@
|
|||
<string name="ConversationActivity_this_device_does_not_appear_to_support_dial_actions">This device does not appear to support dial actions.</string>
|
||||
<string name="ConversationActivity_leave_group">Leave group?</string>
|
||||
<string name="ConversationActivity_are_you_sure_you_want_to_leave_this_group">Are you sure you want to leave this group?</string>
|
||||
<string name="ConversationActivity_choose_new_admin">Choose new admin</string>
|
||||
<string name="ConversationActivity_before_you_leave_you_must_choose_at_least_one_new_admin_for_this_group">Before you leave, you must choose at least one new admin for this group.</string>
|
||||
<string name="ConversationActivity_choose_admin">Choose admin</string>
|
||||
<string name="ConversationActivity_transport_insecure_sms">Insecure SMS</string>
|
||||
<string name="ConversationActivity_transport_insecure_mms">Insecure MMS</string>
|
||||
<string name="ConversationActivity_transport_signal">Signal</string>
|
||||
|
@ -442,6 +445,11 @@
|
|||
<string name="AddToGroupActivity_add_to_group">Add to group</string>
|
||||
<string name="AddToGroupActivity_add_to_groups">Add to groups</string>
|
||||
|
||||
<!-- ChooseNewAdminActivity -->
|
||||
<string name="ChooseNewAdminActivity_choose_new_admin">Choose new admin</string>
|
||||
<string name="ChooseNewAdminActivity_done">Done</string>
|
||||
<string name="ChooseNewAdminActivity_you_left">You left \"%1$s.\"</string>
|
||||
|
||||
<!-- GroupShareProfileView -->
|
||||
<string name="GroupShareProfileView_share_your_profile_name_and_photo_with_this_group">Share your profile name and photo with this group?</string>
|
||||
<string name="GroupShareProfileView_do_you_want_to_make_your_profile_name_and_photo_visible_to_all_current_and_future_members_of_this_group">Do you want to make your profile name and photo visible to all current and future members of this group?</string>
|
||||
|
|
|
@ -207,6 +207,18 @@ public final class GroupsV2Operations {
|
|||
return actions;
|
||||
}
|
||||
|
||||
public GroupChange.Actions.Builder createLeaveAndPromoteMembersToAdmin(UUID self, List<UUID> membersToMakeAdmin) {
|
||||
GroupChange.Actions.Builder actions = createRemoveMembersChange(Collections.singleton(self));
|
||||
|
||||
for (UUID member : membersToMakeAdmin) {
|
||||
actions.addModifyMemberRoles(GroupChange.Actions.ModifyMemberRoleAction.newBuilder()
|
||||
.setUserId(encryptUuid(member))
|
||||
.setRole(Member.Role.ADMINISTRATOR));
|
||||
}
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
public GroupChange.Actions.Builder createModifyGroupTimerChange(int timerDurationSeconds) {
|
||||
return GroupChange.Actions
|
||||
.newBuilder()
|
||||
|
|
Loading…
Reference in New Issue