Add pending member activity.
This commit is contained in:
parent
ef0f26b64c
commit
1290d0ead9
17 changed files with 699 additions and 2 deletions
|
@ -251,6 +251,10 @@
|
|||
android:windowSoftInputMode="stateVisible"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".groups.ui.pendingmemberinvites.PendingMemberInvitesActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:theme="@style/TextSecure.LightNoActionBar" />
|
||||
|
||||
<activity android:name=".DatabaseMigrationActivity"
|
||||
android:theme="@style/NoAnimation.Theme.AppCompat.Light.DarkActionBar"
|
||||
android:launchMode="singleTask"
|
||||
|
|
|
@ -150,6 +150,7 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
|||
import org.thoughtcrime.securesms.events.ReminderUpdateEvent;
|
||||
import org.thoughtcrime.securesms.giph.ui.GiphyActivity;
|
||||
import org.thoughtcrime.securesms.groups.ui.LeaveGroupDialog;
|
||||
import org.thoughtcrime.securesms.groups.ui.pendingmemberinvites.PendingMemberInvitesActivity;
|
||||
import org.thoughtcrime.securesms.insights.InsightsLauncher;
|
||||
import org.thoughtcrime.securesms.invites.InviteReminderModel;
|
||||
import org.thoughtcrime.securesms.invites.InviteReminderRepository;
|
||||
|
@ -730,6 +731,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||
} else {
|
||||
menu.findItem(R.id.menu_distribution_conversation).setChecked(true);
|
||||
}
|
||||
} else if (isActiveV2Group()) {
|
||||
inflater.inflate(R.menu.conversation_push_group_v2_options, menu);
|
||||
} else if (isActiveGroup()) {
|
||||
inflater.inflate(R.menu.conversation_push_group_options, menu);
|
||||
}
|
||||
|
@ -835,6 +838,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||
case R.id.menu_distribution_broadcast: handleDistributionBroadcastEnabled(item); return true;
|
||||
case R.id.menu_distribution_conversation: handleDistributionConversationEnabled(item); return true;
|
||||
case R.id.menu_edit_group: handleEditPushGroup(); return true;
|
||||
case R.id.menu_pending_members: handlePendingMembers(); return true;
|
||||
case R.id.menu_leave: handleLeavePushGroup(); return true;
|
||||
case R.id.menu_invite: handleInviteLink(); return true;
|
||||
case R.id.menu_mute_notifications: handleMuteNotifications(); return true;
|
||||
|
@ -1130,6 +1134,10 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||
startActivityForResult(intent, GROUP_EDIT);
|
||||
}
|
||||
|
||||
private void handlePendingMembers() {
|
||||
startActivity(PendingMemberInvitesActivity.newIntent(ConversationActivity.this, recipient.get().requireGroupId().requireV2()));
|
||||
}
|
||||
|
||||
private void handleDistributionBroadcastEnabled(MenuItem item) {
|
||||
distributionType = ThreadDatabase.DistributionTypes.BROADCAST;
|
||||
item.setChecked(true);
|
||||
|
@ -2108,6 +2116,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||
return record.isPresent() && record.get().isActive();
|
||||
}
|
||||
|
||||
private boolean isActiveV2Group() {
|
||||
if (!isGroupConversation()) return false;
|
||||
|
||||
Optional<GroupRecord> record = DatabaseFactory.getGroupDatabase(this).getGroup(getRecipient().getId());
|
||||
return record.isPresent() && record.get().isActive() && record.get().isV2Group();
|
||||
}
|
||||
|
||||
@SuppressWarnings("SimplifiableIfStatement")
|
||||
private boolean isSelfConversation() {
|
||||
if (!TextSecurePreferences.isPushRegistered(this)) return false;
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
package org.thoughtcrime.securesms.groups;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedPendingMember;
|
||||
import org.signal.zkgroup.util.UUIDUtil;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public final class GroupProtoUtil {
|
||||
|
||||
private GroupProtoUtil() {
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static Recipient pendingMemberToRecipient(@NonNull Context context, @NonNull DecryptedPendingMember pendingMember) {
|
||||
return uuidByteStringToRecipient(context, pendingMember.getUuid());
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static Recipient uuidByteStringToRecipient(@NonNull Context context, @NonNull ByteString uuidByteString) {
|
||||
UUID uuid = UUIDUtil.deserialize(uuidByteString.toByteArray());
|
||||
|
||||
if (uuid.equals(GroupsV2Operations.UNKNOWN_UUID)) {
|
||||
return Recipient.UNKNOWN;
|
||||
}
|
||||
|
||||
return Recipient.externalPush(context, uuid, null);
|
||||
}
|
||||
}
|
|
@ -20,7 +20,7 @@ public abstract class GroupMemberEntry {
|
|||
return onClick;
|
||||
}
|
||||
|
||||
public static class FullMember extends GroupMemberEntry {
|
||||
public final static class FullMember extends GroupMemberEntry {
|
||||
|
||||
private final Recipient member;
|
||||
|
||||
|
@ -32,4 +32,40 @@ public abstract class GroupMemberEntry {
|
|||
return member;
|
||||
}
|
||||
}
|
||||
|
||||
public final static class PendingMember extends GroupMemberEntry {
|
||||
private final Recipient invitee;
|
||||
private final byte[] inviteeCipherText;
|
||||
|
||||
public PendingMember(@NonNull Recipient invitee, @NonNull byte[] inviteeCipherText) {
|
||||
this.invitee = invitee;
|
||||
this.inviteeCipherText = inviteeCipherText;
|
||||
}
|
||||
|
||||
public Recipient getInvitee() {
|
||||
return invitee;
|
||||
}
|
||||
|
||||
public byte[] getInviteeCipherText() {
|
||||
return inviteeCipherText;
|
||||
}
|
||||
}
|
||||
|
||||
public final static class UnknownPendingMemberCount extends GroupMemberEntry {
|
||||
private Recipient inviter;
|
||||
private int inviteCount;
|
||||
|
||||
public UnknownPendingMemberCount(@NonNull Recipient inviter, int inviteCount) {
|
||||
this.inviter = inviter;
|
||||
this.inviteCount = inviteCount;
|
||||
}
|
||||
|
||||
public Recipient getInviter() {
|
||||
return inviter;
|
||||
}
|
||||
|
||||
public int getInviteCount() {
|
||||
return inviteCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,9 @@ import java.util.Collection;
|
|||
|
||||
final class GroupMemberListAdapter extends RecyclerView.Adapter<GroupMemberListAdapter.ViewHolder> {
|
||||
|
||||
private static final int FULL_MEMBER = 0;
|
||||
private static final int FULL_MEMBER = 0;
|
||||
private static final int OWN_INVITE_PENDING = 1;
|
||||
private static final int OTHER_INVITE_PENDING_COUNT = 2;
|
||||
|
||||
private final ArrayList<GroupMemberEntry> data = new ArrayList<>();
|
||||
|
||||
|
@ -35,6 +37,14 @@ final class GroupMemberListAdapter extends RecyclerView.Adapter<GroupMemberListA
|
|||
return new FullMemberViewHolder(LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.group_recipient_list_item,
|
||||
parent, false));
|
||||
case OWN_INVITE_PENDING:
|
||||
return new OwnInvitePendingMemberViewHolder(LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.group_recipient_list_item,
|
||||
parent, false));
|
||||
case OTHER_INVITE_PENDING_COUNT:
|
||||
return new UnknownPendingMemberCountViewHolder(LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.group_recipient_list_item,
|
||||
parent, false));
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
@ -51,6 +61,10 @@ final class GroupMemberListAdapter extends RecyclerView.Adapter<GroupMemberListA
|
|||
|
||||
if (groupMemberEntry instanceof GroupMemberEntry.FullMember) {
|
||||
return FULL_MEMBER;
|
||||
} else if (groupMemberEntry instanceof GroupMemberEntry.PendingMember) {
|
||||
return OWN_INVITE_PENDING;
|
||||
} else if (groupMemberEntry instanceof GroupMemberEntry.UnknownPendingMemberCount) {
|
||||
return OTHER_INVITE_PENDING_COUNT;
|
||||
}
|
||||
|
||||
throw new AssertionError();
|
||||
|
@ -112,4 +126,41 @@ final class GroupMemberListAdapter extends RecyclerView.Adapter<GroupMemberListA
|
|||
bindRecipient(fullMember.getMember());
|
||||
}
|
||||
}
|
||||
|
||||
final static class OwnInvitePendingMemberViewHolder extends ViewHolder {
|
||||
|
||||
OwnInvitePendingMemberViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
}
|
||||
|
||||
@Override
|
||||
void bind(@NonNull GroupMemberEntry memberEntry) {
|
||||
super.bind(memberEntry);
|
||||
|
||||
GroupMemberEntry.PendingMember pendingMember = (GroupMemberEntry.PendingMember) memberEntry;
|
||||
|
||||
bindRecipient(pendingMember.getInvitee());
|
||||
}
|
||||
}
|
||||
|
||||
final static class UnknownPendingMemberCountViewHolder extends ViewHolder {
|
||||
|
||||
UnknownPendingMemberCountViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
}
|
||||
|
||||
@Override
|
||||
void bind(@NonNull GroupMemberEntry memberEntry) {
|
||||
super.bind(memberEntry);
|
||||
GroupMemberEntry.UnknownPendingMemberCount pendingMemberCount = (GroupMemberEntry.UnknownPendingMemberCount) memberEntry;
|
||||
|
||||
Recipient inviter = pendingMemberCount.getInviter();
|
||||
String displayName = inviter.getDisplayName(itemView.getContext());
|
||||
String displayText = context.getResources().getQuantityString(R.plurals.GroupMemberList_invited,
|
||||
pendingMemberCount.getInviteCount(),
|
||||
displayName, pendingMemberCount.getInviteCount());
|
||||
|
||||
bindImageAndText(inviter, displayText);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
package org.thoughtcrime.securesms.groups.ui.pendingmemberinvites;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
|
||||
public class PendingMemberInvitesActivity extends PassphraseRequiredActionBarActivity {
|
||||
|
||||
private static final String GROUP_ID = "GROUP_ID";
|
||||
|
||||
private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme();
|
||||
|
||||
public static Intent newIntent(@NonNull Context context, @NonNull GroupId.V2 groupId) {
|
||||
Intent intent = new Intent(context, PendingMemberInvitesActivity.class);
|
||||
intent.putExtra(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.group_pending_member_invites_activity);
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.container, PendingMemberInvitesFragment.newInstance(GroupId.parse(getIntent().getStringExtra(GROUP_ID)).requireV2()))
|
||||
.commitNow();
|
||||
}
|
||||
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
dynamicTheme.onResume(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSupportNavigateUp() {
|
||||
onBackPressed();
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
package org.thoughtcrime.securesms.groups.ui.pendingmemberinvites;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.ViewModelProviders;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.groups.ui.GroupMemberListView;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class PendingMemberInvitesFragment extends Fragment {
|
||||
|
||||
private static final String GROUP_ID = "GROUP_ID";
|
||||
|
||||
private PendingMemberInvitesViewModel viewModel;
|
||||
private GroupMemberListView youInvited;
|
||||
private GroupMemberListView othersInvited;
|
||||
private View youInvitedEmptyState;
|
||||
private View othersInvitedEmptyState;
|
||||
|
||||
public static PendingMemberInvitesFragment newInstance(@NonNull GroupId.V2 groupId) {
|
||||
PendingMemberInvitesFragment fragment = new PendingMemberInvitesFragment();
|
||||
Bundle args = new Bundle();
|
||||
|
||||
args.putString(GROUP_ID, groupId.toString());
|
||||
fragment.setArguments(args);
|
||||
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.group_pending_member_invites_fragment, container, false);
|
||||
|
||||
youInvited = view.findViewById(R.id.members_you_invited);
|
||||
othersInvited = view.findViewById(R.id.members_others_invited);
|
||||
youInvitedEmptyState = view.findViewById(R.id.no_pending_from_you);
|
||||
othersInvitedEmptyState = view.findViewById(R.id.no_pending_from_others);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
GroupId.V2 groupId = GroupId.parse(Objects.requireNonNull(requireArguments().getString(GROUP_ID))).requireV2();
|
||||
|
||||
PendingMemberInvitesViewModel.Factory factory = new PendingMemberInvitesViewModel.Factory(requireContext(), groupId);
|
||||
|
||||
viewModel = ViewModelProviders.of(requireActivity(), factory).get(PendingMemberInvitesViewModel.class);
|
||||
|
||||
viewModel.getWhoYouInvited().observe(getViewLifecycleOwner(), invitees -> {
|
||||
youInvited.setMembers(invitees);
|
||||
youInvitedEmptyState.setVisibility(invitees.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
});
|
||||
|
||||
viewModel.getWhoOthersInvited().observe(getViewLifecycleOwner(), invitees -> {
|
||||
othersInvited.setMembers(invitees);
|
||||
othersInvitedEmptyState.setVisibility(invitees.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
package org.thoughtcrime.securesms.groups.ui.pendingmemberinvites;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.groups.ui.GroupMemberEntry;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class PendingMemberInvitesViewModel extends ViewModel {
|
||||
|
||||
private static final String TAG = Log.tag(PendingMemberInvitesViewModel.class);
|
||||
|
||||
private final Context context;
|
||||
private final GroupId groupId;
|
||||
private final PendingMemberRepository pendingMemberRepository;
|
||||
private final MutableLiveData<List<GroupMemberEntry.PendingMember>> whoYouInvited = new MutableLiveData<>();
|
||||
private final MutableLiveData<List<GroupMemberEntry.UnknownPendingMemberCount>> whoOthersInvited = new MutableLiveData<>();
|
||||
|
||||
PendingMemberInvitesViewModel(@NonNull Context context,
|
||||
@NonNull GroupId.V2 groupId,
|
||||
@NonNull PendingMemberRepository pendingMemberRepository)
|
||||
{
|
||||
this.context = context;
|
||||
this.groupId = groupId;
|
||||
this.pendingMemberRepository = pendingMemberRepository;
|
||||
|
||||
pendingMemberRepository.getInvitees(groupId, this::setMembers);
|
||||
}
|
||||
|
||||
public LiveData<List<GroupMemberEntry.PendingMember>> getWhoYouInvited() {
|
||||
return whoYouInvited;
|
||||
}
|
||||
|
||||
public LiveData<List<GroupMemberEntry.UnknownPendingMemberCount>> getWhoOthersInvited() {
|
||||
return whoOthersInvited;
|
||||
}
|
||||
|
||||
private void setInvitees(List<GroupMemberEntry.PendingMember> byYou, List<GroupMemberEntry.UnknownPendingMemberCount> byOthers) {
|
||||
whoYouInvited.postValue(byYou);
|
||||
whoOthersInvited.postValue(byOthers);
|
||||
}
|
||||
|
||||
private void setMembers(PendingMemberRepository.InviteeResult inviteeResult) {
|
||||
List<GroupMemberEntry.PendingMember> byMe = new ArrayList<>(inviteeResult.getByMe().size());
|
||||
List<GroupMemberEntry.UnknownPendingMemberCount> byOthers = new ArrayList<>(inviteeResult.getByOthers().size());
|
||||
|
||||
for (PendingMemberRepository.SinglePendingMemberInvitedByYou pendingMember : inviteeResult.getByMe()) {
|
||||
byMe.add(new GroupMemberEntry.PendingMember(pendingMember.getInvitee(),
|
||||
pendingMember.getInviteeCipherText()));
|
||||
}
|
||||
|
||||
for (PendingMemberRepository.MultiplePendingMembersInvitedByAnother pendingMembers : inviteeResult.getByOthers()) {
|
||||
byOthers.add(new GroupMemberEntry.UnknownPendingMemberCount(pendingMembers.getInviter(),
|
||||
pendingMembers.getUuidCipherTexts().size()));
|
||||
}
|
||||
|
||||
setInvitees(byMe, byOthers);
|
||||
}
|
||||
|
||||
public static class Factory implements ViewModelProvider.Factory {
|
||||
|
||||
private final Context context;
|
||||
private final GroupId.V2 groupId;
|
||||
|
||||
public Factory(@NonNull Context context, @NonNull GroupId.V2 groupId) {
|
||||
this.context = context;
|
||||
this.groupId = groupId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
|
||||
//noinspection unchecked
|
||||
return (T) new PendingMemberInvitesViewModel(context, groupId, new PendingMemberRepository(context.getApplicationContext()));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
package org.thoughtcrime.securesms.groups.ui.pendingmemberinvites;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.util.Consumer;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedPendingMember;
|
||||
import org.signal.zkgroup.util.UUIDUtil;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||
import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.groups.GroupProtoUtil;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
final class PendingMemberRepository {
|
||||
|
||||
private final Context context;
|
||||
private final Executor executor;
|
||||
|
||||
PendingMemberRepository(@NonNull Context context) {
|
||||
this.context = context.getApplicationContext();
|
||||
this.executor = SignalExecutors.BOUNDED;
|
||||
}
|
||||
|
||||
public void getInvitees(GroupId.V2 groupId, @NonNull Consumer<InviteeResult> onInviteesLoaded) {
|
||||
executor.execute(() -> {
|
||||
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
|
||||
GroupDatabase.V2GroupProperties v2GroupProperties = groupDatabase.getGroup(groupId).get().requireV2GroupProperties();
|
||||
DecryptedGroup decryptedGroup = v2GroupProperties.getDecryptedGroup();
|
||||
List<DecryptedPendingMember> pendingMembersList = decryptedGroup.getPendingMembersList();
|
||||
List<SinglePendingMemberInvitedByYou> byMe = new ArrayList<>(pendingMembersList.size());
|
||||
List<MultiplePendingMembersInvitedByAnother> byOthers = new ArrayList<>(pendingMembersList.size());
|
||||
ByteString self = ByteString.copyFrom(UUIDUtil.serialize(Recipient.self().getUuid().get()));
|
||||
|
||||
Stream.of(pendingMembersList)
|
||||
.groupBy(DecryptedPendingMember::getAddedByUuid)
|
||||
.forEach(g ->
|
||||
{
|
||||
ByteString inviterUuid = g.getKey();
|
||||
List<DecryptedPendingMember> invitedMembers = g.getValue();
|
||||
|
||||
if (self.equals(inviterUuid)) {
|
||||
for (DecryptedPendingMember pendingMember : invitedMembers) {
|
||||
Recipient invitee = GroupProtoUtil.pendingMemberToRecipient(context, pendingMember);
|
||||
byte[] uuidCipherText = pendingMember.getUuidCipherText().toByteArray();
|
||||
|
||||
byMe.add(new SinglePendingMemberInvitedByYou(invitee, uuidCipherText));
|
||||
}
|
||||
} else {
|
||||
Recipient inviter = GroupProtoUtil.uuidByteStringToRecipient(context, inviterUuid);
|
||||
|
||||
ArrayList<byte[]> uuidCipherTexts = new ArrayList<>(invitedMembers.size());
|
||||
for (DecryptedPendingMember pendingMember : invitedMembers) {
|
||||
uuidCipherTexts.add(pendingMember.getUuidCipherText().toByteArray());
|
||||
}
|
||||
|
||||
byOthers.add(new MultiplePendingMembersInvitedByAnother(inviter, uuidCipherTexts));
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
onInviteesLoaded.accept(new InviteeResult(byMe, byOthers));
|
||||
});
|
||||
}
|
||||
|
||||
public static final class InviteeResult {
|
||||
private final List<SinglePendingMemberInvitedByYou> byMe;
|
||||
private final List<MultiplePendingMembersInvitedByAnother> byOthers;
|
||||
|
||||
private InviteeResult(List<SinglePendingMemberInvitedByYou> byMe,
|
||||
List<MultiplePendingMembersInvitedByAnother> byOthers)
|
||||
{
|
||||
this.byMe = byMe;
|
||||
this.byOthers = byOthers;
|
||||
}
|
||||
|
||||
public List<SinglePendingMemberInvitedByYou> getByMe() {
|
||||
return byMe;
|
||||
}
|
||||
|
||||
public List<MultiplePendingMembersInvitedByAnother> getByOthers() {
|
||||
return byOthers;
|
||||
}
|
||||
}
|
||||
|
||||
public final static class SinglePendingMemberInvitedByYou {
|
||||
private final Recipient invitee;
|
||||
private final byte[] inviteeCipherText;
|
||||
|
||||
private SinglePendingMemberInvitedByYou(@NonNull Recipient invitee, @NonNull byte[] inviteeCipherText) {
|
||||
this.invitee = invitee;
|
||||
this.inviteeCipherText = inviteeCipherText;
|
||||
}
|
||||
|
||||
public Recipient getInvitee() {
|
||||
return invitee;
|
||||
}
|
||||
|
||||
public byte[] getInviteeCipherText() {
|
||||
return inviteeCipherText;
|
||||
}
|
||||
}
|
||||
|
||||
public final static class MultiplePendingMembersInvitedByAnother {
|
||||
private final Recipient inviter;
|
||||
private final ArrayList<byte[]> uuidCipherTexts;
|
||||
|
||||
private MultiplePendingMembersInvitedByAnother(@NonNull Recipient inviter, @NonNull ArrayList<byte[]> uuidCipherTexts) {
|
||||
this.inviter = inviter;
|
||||
this.uuidCipherTexts = uuidCipherTexts;
|
||||
}
|
||||
|
||||
public Recipient getInviter() {
|
||||
return inviter;
|
||||
}
|
||||
|
||||
public ArrayList<byte[]> getUuidCipherTexts() {
|
||||
return uuidCipherTexts;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<?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"
|
||||
tools:context="org.thoughtcrime.securesms.groups.ui.pendingmemberinvites.PendingMemberInvitesActivity">
|
||||
|
||||
<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/PendingMemberInvitesActivity_pending_group_invites"
|
||||
app:titleTextColor="?title_text_color_primary" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
</LinearLayout>
|
|
@ -0,0 +1,121 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.core.widget.NestedScrollView 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:background="?attr/pending_member_background"
|
||||
android:fillViewport="true"
|
||||
tools:context="org.thoughtcrime.securesms.groups.ui.pendingmemberinvites.PendingMemberInvitesFragment">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/pendingmemberinvites"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/cardView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:cardBackgroundColor="?android:attr/windowBackground"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
style="@style/TextAppearance.Signal.Subtitle2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:text="@string/PendingMembersActivity_people_you_invited" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/no_pending_from_you"
|
||||
style="@style/TextAppearance.Signal.Body2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:text="@string/PendingMembersActivity_you_have_no_pending_invites"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"
|
||||
android:textColor="?pending_member_empty_text_color" />
|
||||
|
||||
<org.thoughtcrime.securesms.groups.ui.GroupMemberListView
|
||||
android:id="@+id/members_you_invited"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:listitem="@layout/group_recipient_list_item"
|
||||
tools:maxHeight="192dp" />
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
app:cardBackgroundColor="?android:attr/windowBackground"
|
||||
app:layout_constraintTop_toBottomOf="@+id/cardView">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
style="@style/TextAppearance.Signal.Subtitle2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:text="@string/PendingMembersActivity_invites_by_other_group_members" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/no_pending_from_others"
|
||||
style="@style/TextAppearance.Signal.Body2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:text="@string/PendingMembersActivity_no_pending_invites_by_other_group_members"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"
|
||||
android:textColor="?pending_member_empty_text_color" />
|
||||
|
||||
<org.thoughtcrime.securesms.groups.ui.GroupMemberListView
|
||||
android:id="@+id/members_others_invited"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:listitem="@layout/group_recipient_list_item"
|
||||
tools:maxHeight="192dp" />
|
||||
|
||||
<TextView
|
||||
style="@style/TextAppearance.Signal.Caption"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:text="@string/PendingMembersActivity_missing_detail_explanation" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
|
@ -29,6 +29,7 @@
|
|||
android:layout_alignParentEnd="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:background="#00ffffff"
|
||||
android:contentDescription="@string/GroupCreateActivity_remove_member_description"
|
||||
android:src="@drawable/ic_menu_remove_holo_light" />
|
||||
|
||||
</RelativeLayout>
|
16
app/src/main/res/menu/conversation_push_group_v2_options.xml
Normal file
16
app/src/main/res/menu/conversation_push_group_v2_options.xml
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item android:id="@+id/menu_edit_group"
|
||||
android:title="@string/conversation__menu_edit_group"
|
||||
app:showAsAction="collapseActionView" />
|
||||
|
||||
<item android:id="@+id/menu_leave"
|
||||
android:title="@string/conversation__menu_leave_group"
|
||||
app:showAsAction="collapseActionView"/>
|
||||
|
||||
<item android:id="@+id/menu_pending_members"
|
||||
android:title="@string/conversation__menu_pending_members"
|
||||
app:showAsAction="collapseActionView"/>
|
||||
|
||||
</menu>
|
|
@ -271,6 +271,9 @@
|
|||
<attr name="megaphone_reactions_shade" format="color"/>
|
||||
<attr name="megaphone_reactions_close_tint" format="color"/>
|
||||
|
||||
<attr name="pending_member_background" format="color" />
|
||||
<attr name="pending_member_empty_text_color" format="color" />
|
||||
|
||||
<attr name="debuglog_color_none" format="color" />
|
||||
<attr name="debuglog_color_verbose" format="color" />
|
||||
<attr name="debuglog_color_debug" format="color" />
|
||||
|
|
|
@ -432,6 +432,7 @@
|
|||
<string name="GroupCreateActivity_cannot_add_non_push_to_existing_group">Couldn\'t add %1$s because they\'re not a Signal user.</string>
|
||||
<string name="GroupCreateActivity_loading_group_details">Loading group details…</string>
|
||||
<string name="GroupCreateActivity_youre_already_in_the_group">You\'re already in the group.</string>
|
||||
<string name="GroupCreateActivity_remove_member_description">Remove member</string>
|
||||
|
||||
<!-- GroupShareProfileView -->
|
||||
<string name="GroupShareProfileView_share_your_profile_name_and_photo_with_this_group">Share your profile name and photo with this group?</string>
|
||||
|
@ -441,6 +442,19 @@
|
|||
<!-- GroupMembersDialog -->
|
||||
<string name="GroupMembersDialog_you">You</string>
|
||||
|
||||
<!-- PendingMembersActivity -->
|
||||
<string name="PendingMemberInvitesActivity_pending_group_invites">Pending group invites</string>
|
||||
<string name="PendingMembersActivity_people_you_invited">People you invited</string>
|
||||
<string name="PendingMembersActivity_you_have_no_pending_invites">You have no pending invites.</string>
|
||||
<string name="PendingMembersActivity_invites_by_other_group_members">Invites by other group members</string>
|
||||
<string name="PendingMembersActivity_no_pending_invites_by_other_group_members">No pending invites by other group members.</string>
|
||||
<string name="PendingMembersActivity_missing_detail_explanation">Details of people invited by other group members are not shown. If invitees choose to join, their information will be shared with the group at that time. They will not see any messages in the group until they join.</string>
|
||||
|
||||
<plurals name="GroupMemberList_invited">
|
||||
<item quantity="one">%1$s invited 1 person</item>
|
||||
<item quantity="other">%1$s invited %2$d people</item>
|
||||
</plurals>
|
||||
|
||||
<!-- CropImageActivity -->
|
||||
<string name="CropImageActivity_group_avatar">Group avatar</string>
|
||||
<string name="CropImageActivity_profile_avatar">Avatar</string>
|
||||
|
@ -1674,6 +1688,7 @@
|
|||
<string name="conversation__menu_view_all_media">All media</string>
|
||||
<string name="conversation__menu_conversation_settings">Conversation settings</string>
|
||||
<string name="conversation__menu_add_shortcut">Add to home screen</string>
|
||||
<string name="conversation__menu_pending_members">Pending members</string>
|
||||
|
||||
<!-- conversation_popup -->
|
||||
<string name="conversation_popup__menu_expand_popup">Expand popup</string>
|
||||
|
|
|
@ -97,6 +97,15 @@
|
|||
<item name="android:textStyle">bold</item>
|
||||
</style>
|
||||
|
||||
<style name="TextAppearance.Signal.Body2" parent="@style/TextAppearance.MaterialComponents.Body2">
|
||||
</style>
|
||||
|
||||
<style name="TextAppearance.Signal.Caption" parent="@style/TextAppearance.MaterialComponents.Caption">
|
||||
</style>
|
||||
|
||||
<style name="TextAppearance.Signal.Subtitle2" parent="@style/TextAppearance.MaterialComponents.Subtitle2">
|
||||
</style>
|
||||
|
||||
<style name="Signal.Text.MessageRequest.Title" parent="Base.TextAppearance.AppCompat.Title">
|
||||
<item name="android:textSize">20sp</item>
|
||||
<item name="android:textColor">?message_request_text_color_primary</item>
|
||||
|
|
|
@ -414,6 +414,11 @@
|
|||
<item name="shared_contact_details_header_background">@color/grey_100</item>
|
||||
<item name="shared_contact_details_titlebar">@color/grey_400</item>
|
||||
<item name="shared_contact_item_button_color">@color/core_grey_02</item>
|
||||
|
||||
<item name="pending_member_background">@color/core_grey_02</item>
|
||||
<item name="pending_member_empty_text_color">@color/core_grey_60</item>
|
||||
|
||||
<item name="colorControlNormal">@color/core_grey_90</item>
|
||||
</style>
|
||||
|
||||
<style name="TextSecure.DarkTheme" parent="@style/TextSecure.BaseDarkTheme">
|
||||
|
@ -684,6 +689,11 @@
|
|||
<item name="shared_contact_details_header_background">@color/grey_800</item>
|
||||
<item name="shared_contact_details_titlebar">@color/grey_900</item>
|
||||
<item name="shared_contact_item_button_color">@color/core_grey_85</item>
|
||||
|
||||
<item name="pending_member_background">@color/core_grey_80</item>
|
||||
<item name="pending_member_empty_text_color">@color/core_grey_25</item>
|
||||
|
||||
<item name="colorControlNormal">@color/core_white</item>
|
||||
</style>
|
||||
|
||||
<style name="RationaleDialogLight" parent="Theme.AppCompat.Light.Dialog.Alert">
|
||||
|
|
Loading…
Add table
Reference in a new issue