mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-25 03:11:10 +01:00
Implement new group creation screens behind flag.
This commit is contained in:
@@ -31,7 +31,49 @@ public abstract class GroupMemberEntry {
|
||||
@Override
|
||||
public abstract int hashCode();
|
||||
|
||||
abstract boolean sameId(GroupMemberEntry newItem);
|
||||
abstract boolean sameId(@NonNull GroupMemberEntry newItem);
|
||||
|
||||
public final static class NewGroupCandidate extends GroupMemberEntry {
|
||||
|
||||
private final DefaultValueLiveData<Boolean> isSelected = new DefaultValueLiveData<>(false);
|
||||
private final Recipient member;
|
||||
|
||||
public NewGroupCandidate(@NonNull Recipient member) {
|
||||
this.member = member;
|
||||
}
|
||||
|
||||
public @NonNull Recipient getMember() {
|
||||
return member;
|
||||
}
|
||||
|
||||
public @NonNull LiveData<Boolean> isSelected() {
|
||||
return isSelected;
|
||||
}
|
||||
|
||||
public void setSelected(boolean isSelected) {
|
||||
this.isSelected.postValue(isSelected);
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean sameId(@NonNull GroupMemberEntry newItem) {
|
||||
if (getClass() != newItem.getClass()) return false;
|
||||
|
||||
return member.getId().equals(((NewGroupCandidate) newItem).member.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (!(obj instanceof NewGroupCandidate)) return false;
|
||||
|
||||
NewGroupCandidate other = (NewGroupCandidate) obj;
|
||||
return other.member.equals(member);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return member.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
public final static class FullMember extends GroupMemberEntry {
|
||||
|
||||
@@ -52,7 +94,7 @@ public abstract class GroupMemberEntry {
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean sameId(GroupMemberEntry newItem) {
|
||||
boolean sameId(@NonNull GroupMemberEntry newItem) {
|
||||
if (getClass() != newItem.getClass()) return false;
|
||||
|
||||
return member.getId().equals(((GroupMemberEntry.FullMember) newItem).member.getId());
|
||||
@@ -97,7 +139,7 @@ public abstract class GroupMemberEntry {
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean sameId(GroupMemberEntry newItem) {
|
||||
boolean sameId(@NonNull GroupMemberEntry newItem) {
|
||||
if (getClass() != newItem.getClass()) return false;
|
||||
|
||||
return invitee.getId().equals(((GroupMemberEntry.PendingMember) newItem).invitee.getId());
|
||||
@@ -153,7 +195,7 @@ public abstract class GroupMemberEntry {
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean sameId(GroupMemberEntry newItem) {
|
||||
boolean sameId(@NonNull GroupMemberEntry newItem) {
|
||||
if (getClass() != newItem.getClass()) return false;
|
||||
|
||||
return inviter.getId().equals(((GroupMemberEntry.UnknownPendingMemberCount) newItem).inviter.getId());
|
||||
|
||||
@@ -17,6 +17,7 @@ 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.List;
|
||||
@@ -26,11 +27,13 @@ final class GroupMemberListAdapter extends LifecycleRecyclerAdapter<GroupMemberL
|
||||
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 static final int NEW_GROUP_CANDIDATE = 3;
|
||||
|
||||
private final ArrayList<GroupMemberEntry> data = new ArrayList<>();
|
||||
|
||||
@Nullable private AdminActionsListener adminActionsListener;
|
||||
@Nullable private RecipientClickListener recipientClickListener;
|
||||
@Nullable private AdminActionsListener adminActionsListener;
|
||||
@Nullable private RecipientClickListener recipientClickListener;
|
||||
@Nullable private RecipientLongClickListener recipientLongClickListener;
|
||||
|
||||
void updateData(@NonNull List<? extends GroupMemberEntry> recipients) {
|
||||
if (data.isEmpty()) {
|
||||
@@ -49,16 +52,25 @@ final class GroupMemberListAdapter extends LifecycleRecyclerAdapter<GroupMemberL
|
||||
switch (viewType) {
|
||||
case FULL_MEMBER:
|
||||
return new FullMemberViewHolder(LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.group_recipient_list_item,
|
||||
parent, false), recipientClickListener, adminActionsListener);
|
||||
.inflate(R.layout.group_recipient_list_item, parent, false),
|
||||
recipientClickListener,
|
||||
recipientLongClickListener,
|
||||
adminActionsListener);
|
||||
case OWN_INVITE_PENDING:
|
||||
return new OwnInvitePendingMemberViewHolder(LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.group_recipient_list_item,
|
||||
parent, false), recipientClickListener, adminActionsListener);
|
||||
.inflate(R.layout.group_recipient_list_item, parent, false),
|
||||
recipientClickListener,
|
||||
recipientLongClickListener,
|
||||
adminActionsListener);
|
||||
case OTHER_INVITE_PENDING_COUNT:
|
||||
return new UnknownPendingMemberCountViewHolder(LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.group_recipient_list_item,
|
||||
parent, false), adminActionsListener);
|
||||
.inflate(R.layout.group_recipient_list_item, parent, false),
|
||||
adminActionsListener);
|
||||
case NEW_GROUP_CANDIDATE:
|
||||
return new NewGroupInviteeViewHolder(LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.group_new_candidate_recipient_list_item, parent, false),
|
||||
recipientClickListener,
|
||||
recipientLongClickListener);
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
@@ -72,6 +84,10 @@ final class GroupMemberListAdapter extends LifecycleRecyclerAdapter<GroupMemberL
|
||||
this.recipientClickListener = recipientClickListener;
|
||||
}
|
||||
|
||||
void setRecipientLongClickListener(@Nullable RecipientLongClickListener recipientLongClickListener) {
|
||||
this.recipientLongClickListener = recipientLongClickListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||
holder.bind(data.get(position));
|
||||
@@ -87,6 +103,8 @@ final class GroupMemberListAdapter extends LifecycleRecyclerAdapter<GroupMemberL
|
||||
return OWN_INVITE_PENDING;
|
||||
} else if (groupMemberEntry instanceof GroupMemberEntry.UnknownPendingMemberCount) {
|
||||
return OTHER_INVITE_PENDING_COUNT;
|
||||
} else if (groupMemberEntry instanceof GroupMemberEntry.NewGroupCandidate) {
|
||||
return NEW_GROUP_CANDIDATE;
|
||||
}
|
||||
|
||||
throw new AssertionError();
|
||||
@@ -99,31 +117,34 @@ final class GroupMemberListAdapter extends LifecycleRecyclerAdapter<GroupMemberL
|
||||
|
||||
static abstract class ViewHolder extends LifecycleViewHolder {
|
||||
|
||||
final Context context;
|
||||
final AvatarImageView avatar;
|
||||
final TextView recipient;
|
||||
final PopupMenuView popupMenu;
|
||||
final View popupMenuContainer;
|
||||
final ProgressBar busyProgress;
|
||||
final View admin;
|
||||
@Nullable final RecipientClickListener recipientClickListener;
|
||||
@Nullable final AdminActionsListener adminActionsListener;
|
||||
final Context context;
|
||||
final AvatarImageView avatar;
|
||||
final TextView recipient;
|
||||
final PopupMenuView popupMenu;
|
||||
final View popupMenuContainer;
|
||||
final ProgressBar busyProgress;
|
||||
final View admin;
|
||||
@Nullable final RecipientClickListener recipientClickListener;
|
||||
@Nullable final AdminActionsListener adminActionsListener;
|
||||
@Nullable final RecipientLongClickListener recipientLongClickListener;
|
||||
|
||||
ViewHolder(@NonNull View itemView,
|
||||
@Nullable RecipientClickListener recipientClickListener,
|
||||
@Nullable RecipientLongClickListener recipientLongClickListener,
|
||||
@Nullable AdminActionsListener adminActionsListener)
|
||||
{
|
||||
super(itemView);
|
||||
|
||||
this.context = itemView.getContext();
|
||||
this.avatar = itemView.findViewById(R.id.recipient_avatar);
|
||||
this.recipient = itemView.findViewById(R.id.recipient_name);
|
||||
this.popupMenu = itemView.findViewById(R.id.popupMenu);
|
||||
this.popupMenuContainer = itemView.findViewById(R.id.popupMenuProgressContainer);
|
||||
this.busyProgress = itemView.findViewById(R.id.menuBusyProgress);
|
||||
this.admin = itemView.findViewById(R.id.admin);
|
||||
this.recipientClickListener = recipientClickListener;
|
||||
this.adminActionsListener = adminActionsListener;
|
||||
this.context = itemView.getContext();
|
||||
this.avatar = itemView.findViewById(R.id.recipient_avatar);
|
||||
this.recipient = itemView.findViewById(R.id.recipient_name);
|
||||
this.popupMenu = itemView.findViewById(R.id.popupMenu);
|
||||
this.popupMenuContainer = itemView.findViewById(R.id.popupMenuProgressContainer);
|
||||
this.busyProgress = itemView.findViewById(R.id.menuBusyProgress);
|
||||
this.admin = itemView.findViewById(R.id.admin);
|
||||
this.recipientClickListener = recipientClickListener;
|
||||
this.recipientLongClickListener = recipientLongClickListener;
|
||||
this.adminActionsListener = adminActionsListener;
|
||||
}
|
||||
|
||||
void bindRecipient(@NonNull Recipient recipient) {
|
||||
@@ -149,6 +170,13 @@ final class GroupMemberListAdapter extends LifecycleRecyclerAdapter<GroupMemberL
|
||||
recipientClickListener.onClick(recipient);
|
||||
}
|
||||
});
|
||||
this.itemView.setOnLongClickListener(v -> {
|
||||
if (recipientLongClickListener != null && getAdapterPosition() != RecyclerView.NO_POSITION) {
|
||||
return recipientLongClickListener.onLongClick(recipient);
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
void bind(@NonNull GroupMemberEntry memberEntry) {
|
||||
@@ -179,9 +207,10 @@ final class GroupMemberListAdapter extends LifecycleRecyclerAdapter<GroupMemberL
|
||||
|
||||
FullMemberViewHolder(@NonNull View itemView,
|
||||
@Nullable RecipientClickListener recipientClickListener,
|
||||
@Nullable RecipientLongClickListener recipientLongClickListener,
|
||||
@Nullable AdminActionsListener adminActionsListener)
|
||||
{
|
||||
super(itemView, recipientClickListener, adminActionsListener);
|
||||
super(itemView, recipientClickListener, recipientLongClickListener, adminActionsListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -195,14 +224,46 @@ final class GroupMemberListAdapter extends LifecycleRecyclerAdapter<GroupMemberL
|
||||
admin.setVisibility(fullMember.isAdmin() ? View.VISIBLE : View.INVISIBLE);
|
||||
}
|
||||
}
|
||||
final static class NewGroupInviteeViewHolder extends ViewHolder {
|
||||
|
||||
private final View smsContact;
|
||||
private final View smsWarning;
|
||||
|
||||
NewGroupInviteeViewHolder(@NonNull View itemView,
|
||||
@Nullable RecipientClickListener recipientClickListener,
|
||||
@Nullable RecipientLongClickListener recipientLongClickListener)
|
||||
{
|
||||
super(itemView, recipientClickListener, recipientLongClickListener, null);
|
||||
|
||||
smsContact = itemView.findViewById(R.id.sms_contact);
|
||||
smsWarning = itemView.findViewById(R.id.sms_warning);
|
||||
}
|
||||
|
||||
@Override
|
||||
void bind(@NonNull GroupMemberEntry memberEntry) {
|
||||
GroupMemberEntry.NewGroupCandidate newGroupCandidate = (GroupMemberEntry.NewGroupCandidate) memberEntry;
|
||||
|
||||
bindRecipient(newGroupCandidate.getMember());
|
||||
bindRecipientClick(newGroupCandidate.getMember());
|
||||
|
||||
itemView.setSelected(false);
|
||||
newGroupCandidate.isSelected().observe(this, itemView::setSelected);
|
||||
|
||||
int smsWarningVisibility = newGroupCandidate.getMember().isRegistered() ? View.GONE : View.VISIBLE;
|
||||
|
||||
smsContact.setVisibility(smsWarningVisibility);
|
||||
smsWarning.setVisibility(smsWarningVisibility);
|
||||
}
|
||||
}
|
||||
|
||||
final static class OwnInvitePendingMemberViewHolder extends ViewHolder {
|
||||
|
||||
OwnInvitePendingMemberViewHolder(@NonNull View itemView,
|
||||
@Nullable RecipientClickListener recipientClickListener,
|
||||
@Nullable RecipientLongClickListener recipientLongClickListener,
|
||||
@Nullable AdminActionsListener adminActionsListener)
|
||||
{
|
||||
super(itemView, recipientClickListener, adminActionsListener);
|
||||
super(itemView, recipientClickListener, recipientLongClickListener, adminActionsListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -231,7 +292,7 @@ final class GroupMemberListAdapter extends LifecycleRecyclerAdapter<GroupMemberL
|
||||
final static class UnknownPendingMemberCountViewHolder extends ViewHolder {
|
||||
|
||||
UnknownPendingMemberCountViewHolder(@NonNull View itemView, @Nullable AdminActionsListener adminActionsListener) {
|
||||
super(itemView, null, adminActionsListener);
|
||||
super(itemView, null, null, adminActionsListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -59,6 +59,10 @@ public final class GroupMemberListView extends RecyclerView {
|
||||
membersAdapter.setRecipientClickListener(listener);
|
||||
}
|
||||
|
||||
public void setRecipientLongClickListener(@Nullable RecipientLongClickListener listener) {
|
||||
membersAdapter.setRecipientLongClickListener(listener);
|
||||
}
|
||||
|
||||
public void setMembers(@NonNull List<? extends GroupMemberEntry> recipients) {
|
||||
membersAdapter.updateData(recipients);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.thoughtcrime.securesms.groups.ui;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
|
||||
public interface RecipientLongClickListener {
|
||||
boolean onLongClick(@NonNull Recipient recipient);
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package org.thoughtcrime.securesms.groups.ui.creategroup;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import org.thoughtcrime.securesms.ContactSelectionActivity;
|
||||
import org.thoughtcrime.securesms.ContactSelectionListFragment;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader;
|
||||
import org.thoughtcrime.securesms.groups.ui.creategroup.details.AddGroupDetailsActivity;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
public class CreateGroupActivity extends ContactSelectionActivity {
|
||||
|
||||
private static final int MINIMUM_GROUP_SIZE = 1;
|
||||
private static final short REQUEST_CODE_ADD_DETAILS = 17275;
|
||||
|
||||
private View next;
|
||||
|
||||
public static Intent newIntent(@NonNull Context context) {
|
||||
Intent intent = new Intent(context, CreateGroupActivity.class);
|
||||
|
||||
intent.putExtra(ContactSelectionListFragment.MULTI_SELECT, true);
|
||||
intent.putExtra(ContactSelectionListFragment.REFRESHABLE, false);
|
||||
intent.putExtra(ContactSelectionActivity.EXTRA_LAYOUT_RES_ID, R.layout.create_group_activity);
|
||||
|
||||
int displayMode = TextSecurePreferences.isSmsEnabled(context) ? ContactsCursorLoader.DisplayMode.FLAG_SMS | ContactsCursorLoader.DisplayMode.FLAG_PUSH
|
||||
: ContactsCursorLoader.DisplayMode.FLAG_PUSH;
|
||||
|
||||
intent.putExtra(ContactSelectionListFragment.DISPLAY_MODE, displayMode);
|
||||
|
||||
return intent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle bundle, boolean ready) {
|
||||
super.onCreate(bundle, ready);
|
||||
assert getSupportActionBar() != null;
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
next = findViewById(R.id.next);
|
||||
|
||||
disableNext();
|
||||
next.setOnClickListener(v -> handleNextPressed());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
|
||||
if (requestCode == REQUEST_CODE_ADD_DETAILS && resultCode == RESULT_OK) {
|
||||
finish();
|
||||
} else {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onContactSelected(Optional<RecipientId> recipientId, String number) {
|
||||
if (contactsFragment.getSelectedContactsCount() >= MINIMUM_GROUP_SIZE) {
|
||||
enableNext();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onContactDeselected(Optional<RecipientId> recipientId, String number) {
|
||||
if (contactsFragment.getSelectedContactsCount() < MINIMUM_GROUP_SIZE) {
|
||||
disableNext();
|
||||
}
|
||||
}
|
||||
|
||||
private void enableNext() {
|
||||
next.setEnabled(true);
|
||||
next.animate().alpha(1f);
|
||||
}
|
||||
|
||||
private void disableNext() {
|
||||
next.setEnabled(false);
|
||||
next.animate().alpha(0.5f);
|
||||
}
|
||||
|
||||
private void handleNextPressed() {
|
||||
RecipientId[] ids = Stream.of(contactsFragment.getSelectedContacts())
|
||||
.map(selectedContact -> selectedContact.getOrCreateRecipientId(this))
|
||||
.toArray(RecipientId[]::new);
|
||||
|
||||
startActivityForResult(AddGroupDetailsActivity.newIntent(this, ids), REQUEST_CODE_ADD_DETAILS);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package org.thoughtcrime.securesms.groups.ui.creategroup.details;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.navigation.NavGraph;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.conversation.ConversationActivity;
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
|
||||
public class AddGroupDetailsActivity extends PassphraseRequiredActionBarActivity implements AddGroupDetailsFragment.Callback {
|
||||
|
||||
private static final String EXTRA_RECIPIENTS = "recipient_ids";
|
||||
|
||||
private final DynamicTheme theme = new DynamicNoActionBarTheme();
|
||||
|
||||
public static Intent newIntent(@NonNull Context context, @NonNull RecipientId[] recipients) {
|
||||
Intent intent = new Intent(context, AddGroupDetailsActivity.class);
|
||||
|
||||
intent.putExtra(EXTRA_RECIPIENTS, recipients);
|
||||
|
||||
return intent;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle bundle, boolean ready) {
|
||||
theme.onCreate(this);
|
||||
|
||||
setContentView(R.layout.add_group_details_activity);
|
||||
|
||||
if (bundle == null) {
|
||||
Parcelable[] parcelables = getIntent().getParcelableArrayExtra(EXTRA_RECIPIENTS);
|
||||
RecipientId[] ids = new RecipientId[parcelables.length];
|
||||
|
||||
System.arraycopy(parcelables, 0, ids, 0, parcelables.length);
|
||||
|
||||
AddGroupDetailsFragmentArgs arguments = new AddGroupDetailsFragmentArgs.Builder(ids).build();
|
||||
NavGraph graph = Navigation.findNavController(this, R.id.nav_host_fragment).getGraph();
|
||||
|
||||
Navigation.findNavController(this, R.id.nav_host_fragment).setGraph(graph, arguments.toBundle());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
theme.onResume(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGroupCreated(@NonNull RecipientId recipientId, long threadId) {
|
||||
Intent intent = ConversationActivity.buildIntent(this,
|
||||
recipientId,
|
||||
threadId,
|
||||
ThreadDatabase.DistributionTypes.DEFAULT,
|
||||
-1);
|
||||
|
||||
startActivity(intent);
|
||||
setResult(RESULT_OK);
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNavigationButtonPressed() {
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
package org.thoughtcrime.securesms.groups.ui.creategroup.details;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.InsetDrawable;
|
||||
import android.os.Bundle;
|
||||
import android.view.ActionMode;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.ViewModelProviders;
|
||||
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
|
||||
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
import com.bumptech.glide.request.target.CustomTarget;
|
||||
import com.bumptech.glide.request.transition.Transition;
|
||||
import com.dd.CircularProgressButton;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.groups.ui.GroupMemberListView;
|
||||
import org.thoughtcrime.securesms.mediasend.AvatarSelectionActivity;
|
||||
import org.thoughtcrime.securesms.mediasend.AvatarSelectionBottomSheetDialogFragment;
|
||||
import org.thoughtcrime.securesms.mediasend.Media;
|
||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader;
|
||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.BitmapUtil;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.text.AfterTextChanged;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class AddGroupDetailsFragment extends Fragment {
|
||||
|
||||
private static final int AVATAR_PLACEHOLDER_INSET_DP = 18;
|
||||
private static final short REQUEST_CODE_AVATAR = 27621;
|
||||
private static final String ARG_RECIPIENT_IDS = "recipient_ids";
|
||||
|
||||
private CircularProgressButton create;
|
||||
private Callback callback;
|
||||
private AddGroupDetailsViewModel viewModel;
|
||||
private Drawable avatarPlaceholder;
|
||||
private EditText name;
|
||||
private Toolbar toolbar;
|
||||
private ActionMode actionMode;
|
||||
|
||||
private ActionMode.Callback recipientActionModeCallback = new ActionMode.Callback() {
|
||||
@Override
|
||||
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
|
||||
mode.getMenuInflater().inflate(R.menu.add_group_details_fragment_context_menu, menu);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
|
||||
if (item.getItemId() == R.id.action_delete) {
|
||||
viewModel.deleteSelected();
|
||||
mode.finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyActionMode(ActionMode mode) {
|
||||
actionMode = null;
|
||||
viewModel.clearSelected();
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
super.onAttach(context);
|
||||
|
||||
if (context instanceof Callback) {
|
||||
callback = (Callback) context;
|
||||
} else {
|
||||
throw new ClassCastException("Parent context should implement AddGroupDetailsFragment.Callback");
|
||||
}
|
||||
}
|
||||
|
||||
public static Fragment create(@NonNull RecipientId[] recipientIds) {
|
||||
AddGroupDetailsFragment fragment = new AddGroupDetailsFragment();
|
||||
Bundle arguments = new Bundle();
|
||||
|
||||
arguments.putParcelableArray(ARG_RECIPIENT_IDS, recipientIds);
|
||||
fragment.setArguments(arguments);
|
||||
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable View onCreateView(@NonNull LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState)
|
||||
{
|
||||
return inflater.inflate(R.layout.add_group_details_fragment, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
create = view.findViewById(R.id.create);
|
||||
name = view.findViewById(R.id.group_name);
|
||||
toolbar = view.findViewById(R.id.toolbar);
|
||||
|
||||
setCreateEnabled(false, false);
|
||||
|
||||
GroupMemberListView members = view.findViewById(R.id.member_list);
|
||||
ImageView avatar = view.findViewById(R.id.group_avatar);
|
||||
View mmsWarning = view.findViewById(R.id.mms_warning);
|
||||
|
||||
avatarPlaceholder = VectorDrawableCompat.create(getResources(), R.drawable.ic_camera_outline_32_ultramarine, requireActivity().getTheme());
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
avatar.setImageDrawable(new InsetDrawable(avatarPlaceholder, ViewUtil.dpToPx(AVATAR_PLACEHOLDER_INSET_DP)));
|
||||
}
|
||||
|
||||
initializeViewModel();
|
||||
|
||||
avatar.setOnClickListener(v -> AvatarSelectionBottomSheetDialogFragment.create(false, true, REQUEST_CODE_AVATAR, true)
|
||||
.show(getChildFragmentManager(), "BOTTOM"));
|
||||
members.setRecipientLongClickListener(this::handleRecipientLongClick);
|
||||
members.setRecipientClickListener(this::handleRecipientClick);
|
||||
name.addTextChangedListener(new AfterTextChanged(editable -> viewModel.setName(editable.toString())));
|
||||
toolbar.setNavigationOnClickListener(unused -> callback.onNavigationButtonPressed());
|
||||
create.setOnClickListener(v -> handleCreateClicked());
|
||||
viewModel.getMembers().observe(getViewLifecycleOwner(), members::setMembers);
|
||||
viewModel.getCanSubmitForm().observe(getViewLifecycleOwner(), isFormValid -> setCreateEnabled(isFormValid, true));
|
||||
viewModel.getIsMms().observe(getViewLifecycleOwner(), isMms -> mmsWarning.setVisibility(isMms ? View.VISIBLE : View.GONE));
|
||||
viewModel.getAvatar().observe(getViewLifecycleOwner(), avatarBytes -> {
|
||||
if (avatarBytes == null) {
|
||||
avatar.setImageDrawable(new InsetDrawable(avatarPlaceholder, ViewUtil.dpToPx(AVATAR_PLACEHOLDER_INSET_DP)));
|
||||
} else {
|
||||
GlideApp.with(this)
|
||||
.load(avatarBytes)
|
||||
.circleCrop()
|
||||
.skipMemoryCache(true)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.into(avatar);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
|
||||
if (requestCode == REQUEST_CODE_AVATAR && resultCode == Activity.RESULT_OK && data != null) {
|
||||
final Media result = data.getParcelableExtra(AvatarSelectionActivity.EXTRA_MEDIA);
|
||||
final DecryptableStreamUriLoader.DecryptableUri decryptableUri = new DecryptableStreamUriLoader.DecryptableUri(result.getUri());
|
||||
|
||||
GlideApp.with(this)
|
||||
.asBitmap()
|
||||
.load(decryptableUri)
|
||||
.skipMemoryCache(true)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.centerCrop()
|
||||
.override(AvatarHelper.AVATAR_DIMENSIONS, AvatarHelper.AVATAR_DIMENSIONS)
|
||||
.into(new CustomTarget<Bitmap>() {
|
||||
@Override
|
||||
public void onResourceReady(@NonNull Bitmap resource, Transition<? super Bitmap> transition) {
|
||||
viewModel.setAvatar(Objects.requireNonNull(BitmapUtil.toByteArray(resource)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadCleared(@Nullable Drawable placeholder) {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeViewModel() {
|
||||
AddGroupDetailsFragmentArgs args = AddGroupDetailsFragmentArgs.fromBundle(requireArguments());
|
||||
AddGroupDetailsRepository repository = new AddGroupDetailsRepository(requireContext());
|
||||
AddGroupDetailsViewModel.Factory factory = new AddGroupDetailsViewModel.Factory(args.getRecipientIds(), repository);
|
||||
|
||||
viewModel = ViewModelProviders.of(this, factory).get(AddGroupDetailsViewModel.class);
|
||||
|
||||
viewModel.getGroupCreateResult().observe(getViewLifecycleOwner(), this::handleGroupCreateResult);
|
||||
}
|
||||
|
||||
private void handleCreateClicked() {
|
||||
create.setClickable(false);
|
||||
create.setIndeterminateProgressMode(true);
|
||||
create.setProgress(50);
|
||||
|
||||
viewModel.create();
|
||||
}
|
||||
|
||||
private void handleRecipientClick(@NonNull Recipient recipient) {
|
||||
if (actionMode == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int size = viewModel.toggleSelected(recipient);
|
||||
if (size == 0) {
|
||||
actionMode.finish();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean handleRecipientLongClick(@NonNull Recipient recipient) {
|
||||
if (actionMode != null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
actionMode = toolbar.startActionMode(recipientActionModeCallback);
|
||||
|
||||
if (actionMode != null) {
|
||||
viewModel.toggleSelected(recipient);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void handleGroupCreateResult(@NonNull GroupCreateResult groupCreateResult) {
|
||||
groupCreateResult.consume(this::handleGroupCreateResultSuccess, this::handleGroupCreateResultError);
|
||||
}
|
||||
|
||||
private void handleGroupCreateResultSuccess(@NonNull GroupCreateResult.Success success) {
|
||||
callback.onGroupCreated(success.getGroupRecipient().getId(), success.getThreadId());
|
||||
}
|
||||
|
||||
private void handleGroupCreateResultError(@NonNull GroupCreateResult.Error error) {
|
||||
switch (error.getErrorType()) {
|
||||
case ERROR_IO:
|
||||
case ERROR_BUSY:
|
||||
toast(R.string.AddGroupDetailsFragment__try_again_later);
|
||||
break;
|
||||
case ERROR_FAILED:
|
||||
toast(R.string.AddGroupDetailsFragment__group_creation_failed);
|
||||
break;
|
||||
case ERROR_INVALID_NAME:
|
||||
name.setError(getString(R.string.AddGroupDetailsFragment__this_field_is_required));
|
||||
break;
|
||||
case ERROR_INVALID_MEMBER_COUNT:
|
||||
toast(R.string.AddGroupDetailsFragment__groups_require_at_least_two_members);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unexpected error: " + error.getErrorType().name());
|
||||
}
|
||||
}
|
||||
|
||||
private void toast(@StringRes int toastStringId) {
|
||||
Toast.makeText(requireContext(), toastStringId, Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void setCreateEnabled(boolean isEnabled, boolean animate) {
|
||||
if (create.isEnabled() == isEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
create.setEnabled(isEnabled);
|
||||
create.animate()
|
||||
.setDuration(animate ? 300 : 0)
|
||||
.alpha(isEnabled ? 1f : 0.5f);
|
||||
}
|
||||
|
||||
public interface Callback {
|
||||
void onGroupCreated(@NonNull RecipientId recipientId, long threadId);
|
||||
void onNavigationButtonPressed();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package org.thoughtcrime.securesms.groups.ui.creategroup.details;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.util.Consumer;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import org.thoughtcrime.securesms.groups.GroupChangeBusyException;
|
||||
import org.thoughtcrime.securesms.groups.GroupChangeFailedException;
|
||||
import org.thoughtcrime.securesms.groups.GroupManager;
|
||||
import org.thoughtcrime.securesms.groups.ui.GroupMemberEntry;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
final class AddGroupDetailsRepository {
|
||||
|
||||
private final Context context;
|
||||
|
||||
AddGroupDetailsRepository(@NonNull Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
void resolveMembers(@NonNull RecipientId[] recipientIds, Consumer<List<GroupMemberEntry.NewGroupCandidate>> consumer) {
|
||||
SignalExecutors.BOUNDED.execute(() -> {
|
||||
List<GroupMemberEntry.NewGroupCandidate> members = new ArrayList<>(recipientIds.length);
|
||||
|
||||
for (RecipientId id : recipientIds) {
|
||||
members.add(new GroupMemberEntry.NewGroupCandidate(Recipient.resolved(id)));
|
||||
}
|
||||
|
||||
consumer.accept(members);
|
||||
});
|
||||
}
|
||||
|
||||
void createPushGroup(@NonNull Set<RecipientId> members,
|
||||
@Nullable byte[] avatar,
|
||||
@Nullable String name,
|
||||
boolean mms,
|
||||
Consumer<GroupCreateResult> resultConsumer)
|
||||
{
|
||||
SignalExecutors.BOUNDED.execute(() -> {
|
||||
Set<Recipient> recipients = new HashSet<>(Stream.of(members).map(Recipient::resolved).toList());
|
||||
|
||||
try {
|
||||
GroupManager.GroupActionResult result = GroupManager.createGroup(context, recipients, avatar, name, mms);
|
||||
|
||||
resultConsumer.accept(GroupCreateResult.success(result));
|
||||
} catch (GroupChangeBusyException e) {
|
||||
resultConsumer.accept(GroupCreateResult.error(GroupCreateResult.Error.Type.ERROR_BUSY));
|
||||
} catch (GroupChangeFailedException e) {
|
||||
resultConsumer.accept(GroupCreateResult.error(GroupCreateResult.Error.Type.ERROR_FAILED));
|
||||
} catch (IOException e) {
|
||||
resultConsumer.accept(GroupCreateResult.error(GroupCreateResult.Error.Type.ERROR_IO));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
package org.thoughtcrime.securesms.groups.ui.creategroup.details;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.Transformations;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import com.annimon.stream.Collectors;
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import org.thoughtcrime.securesms.groups.ui.GroupMemberEntry;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.DefaultValueLiveData;
|
||||
import org.thoughtcrime.securesms.util.SingleLiveEvent;
|
||||
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
public final class AddGroupDetailsViewModel extends ViewModel {
|
||||
|
||||
private final LiveData<List<GroupMemberEntry.NewGroupCandidate>> members;
|
||||
private final DefaultValueLiveData<Set<RecipientId>> selected = new DefaultValueLiveData<>(new HashSet<>());
|
||||
private final DefaultValueLiveData<Set<RecipientId>> deleted = new DefaultValueLiveData<>(new HashSet<>());
|
||||
private final MutableLiveData<String> name = new MutableLiveData<>("");
|
||||
private final MutableLiveData<byte[]> avatar = new MutableLiveData<>();
|
||||
private final LiveData<Boolean> isMms;
|
||||
private final SingleLiveEvent<GroupCreateResult> groupCreateResult = new SingleLiveEvent<>();
|
||||
private final LiveData<Boolean> canSubmitForm = Transformations.map(name, name -> !TextUtils.isEmpty(name));
|
||||
private final AddGroupDetailsRepository repository;
|
||||
|
||||
AddGroupDetailsViewModel(@NonNull RecipientId[] recipientIds,
|
||||
@NonNull AddGroupDetailsRepository repository)
|
||||
{
|
||||
this.repository = repository;
|
||||
|
||||
MutableLiveData<List<GroupMemberEntry.NewGroupCandidate>> initialMembers = new MutableLiveData<>();
|
||||
LiveData<List<GroupMemberEntry.NewGroupCandidate>> membersWithoutDeleted = LiveDataUtil.combineLatest(initialMembers,
|
||||
deleted,
|
||||
AddGroupDetailsViewModel::filterDeletedMembers);
|
||||
|
||||
members = LiveDataUtil.combineLatest(membersWithoutDeleted, selected, AddGroupDetailsViewModel::updateSelectedMembers);
|
||||
isMms = Transformations.map(members, this::isAnyForcedSms);
|
||||
|
||||
repository.resolveMembers(recipientIds, initialMembers::postValue);
|
||||
}
|
||||
|
||||
@NonNull LiveData<List<GroupMemberEntry.NewGroupCandidate>> getMembers() {
|
||||
return members;
|
||||
}
|
||||
|
||||
@NonNull LiveData<Boolean> getCanSubmitForm() {
|
||||
return canSubmitForm;
|
||||
}
|
||||
|
||||
@NonNull LiveData<GroupCreateResult> getGroupCreateResult() {
|
||||
return groupCreateResult;
|
||||
}
|
||||
|
||||
@NonNull LiveData<byte[]> getAvatar() {
|
||||
return avatar;
|
||||
}
|
||||
|
||||
@NonNull LiveData<Boolean> getIsMms() {
|
||||
return isMms;
|
||||
}
|
||||
|
||||
void setAvatar(@NonNull byte[] avatar) {
|
||||
this.avatar.setValue(avatar);
|
||||
}
|
||||
|
||||
void setName(@NonNull String name) {
|
||||
this.name.setValue(name);
|
||||
}
|
||||
|
||||
int toggleSelected(@NonNull Recipient recipient) {
|
||||
Set<RecipientId> selected = this.selected.getValue();
|
||||
|
||||
if (!selected.add(recipient.getId())) {
|
||||
selected.remove(recipient.getId());
|
||||
}
|
||||
|
||||
this.selected.setValue(selected);
|
||||
|
||||
return selected.size();
|
||||
}
|
||||
|
||||
void clearSelected() {
|
||||
this.selected.setValue(new HashSet<>());
|
||||
}
|
||||
|
||||
void deleteSelected() {
|
||||
Set<RecipientId> selected = this.selected.getValue();
|
||||
Set<RecipientId> deleted = this.deleted.getValue();
|
||||
|
||||
deleted.addAll(selected);
|
||||
this.deleted.setValue(deleted);
|
||||
}
|
||||
|
||||
void create() {
|
||||
List<GroupMemberEntry.NewGroupCandidate> members = Objects.requireNonNull(this.members.getValue());
|
||||
Set<RecipientId> memberIds = Stream.of(members).map(member -> member.getMember().getId()).collect(Collectors.toSet());
|
||||
byte[] avatarBytes = avatar.getValue();
|
||||
String groupName = name.getValue();
|
||||
boolean isGroupMms = isMms.getValue() == Boolean.TRUE;
|
||||
|
||||
if (TextUtils.isEmpty(groupName)) {
|
||||
groupCreateResult.postValue(GroupCreateResult.error(GroupCreateResult.Error.Type.ERROR_INVALID_NAME));
|
||||
return;
|
||||
}
|
||||
|
||||
if (memberIds.isEmpty()) {
|
||||
groupCreateResult.postValue(GroupCreateResult.error(GroupCreateResult.Error.Type.ERROR_INVALID_MEMBER_COUNT));
|
||||
return;
|
||||
}
|
||||
|
||||
repository.createPushGroup(memberIds,
|
||||
avatarBytes,
|
||||
groupName,
|
||||
isGroupMms,
|
||||
groupCreateResult::postValue);
|
||||
}
|
||||
|
||||
private static @NonNull List<GroupMemberEntry.NewGroupCandidate> filterDeletedMembers(@NonNull List<GroupMemberEntry.NewGroupCandidate> members, @NonNull Set<RecipientId> deleted) {
|
||||
return Stream.of(members)
|
||||
.filterNot(member -> deleted.contains(member.getMember().getId()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private static @NonNull List<GroupMemberEntry.NewGroupCandidate> updateSelectedMembers(@NonNull List<GroupMemberEntry.NewGroupCandidate> members, @NonNull Set<RecipientId> selected) {
|
||||
for (GroupMemberEntry.NewGroupCandidate member : members) {
|
||||
member.setSelected(selected.contains(member.getMember().getId()));
|
||||
}
|
||||
|
||||
return members;
|
||||
}
|
||||
|
||||
private boolean isAnyForcedSms(@NonNull List<GroupMemberEntry.NewGroupCandidate> members) {
|
||||
return Stream.of(members)
|
||||
.anyMatch(member -> !member.getMember().isRegistered());
|
||||
}
|
||||
|
||||
static final class Factory implements ViewModelProvider.Factory {
|
||||
|
||||
private final RecipientId[] recipientIds;
|
||||
private final AddGroupDetailsRepository repository;
|
||||
|
||||
Factory(@NonNull RecipientId[] recipientIds, @NonNull AddGroupDetailsRepository repository) {
|
||||
this.recipientIds = recipientIds;
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
|
||||
return Objects.requireNonNull(modelClass.cast(new AddGroupDetailsViewModel(recipientIds, repository)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package org.thoughtcrime.securesms.groups.ui.creategroup.details;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.util.Consumer;
|
||||
|
||||
import org.thoughtcrime.securesms.groups.GroupManager;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
|
||||
abstract class GroupCreateResult {
|
||||
|
||||
static GroupCreateResult success(@NonNull GroupManager.GroupActionResult result) {
|
||||
return new GroupCreateResult.Success(result.getThreadId(), result.getGroupRecipient());
|
||||
}
|
||||
|
||||
static GroupCreateResult error(@NonNull GroupCreateResult.Error.Type errorType) {
|
||||
return new GroupCreateResult.Error(errorType);
|
||||
}
|
||||
|
||||
private GroupCreateResult() {
|
||||
}
|
||||
|
||||
static final class Success extends GroupCreateResult {
|
||||
private final long threadId;
|
||||
private final Recipient groupRecipient;
|
||||
|
||||
private Success(long threadId, @NonNull Recipient groupRecipient) {
|
||||
this.threadId = threadId;
|
||||
this.groupRecipient = groupRecipient;
|
||||
}
|
||||
|
||||
long getThreadId() {
|
||||
return threadId;
|
||||
}
|
||||
|
||||
@NonNull Recipient getGroupRecipient() {
|
||||
return groupRecipient;
|
||||
}
|
||||
|
||||
@Override
|
||||
void consume(@NonNull Consumer<Success> successConsumer,
|
||||
@NonNull Consumer<Error> errorConsumer)
|
||||
{
|
||||
successConsumer.accept(this);
|
||||
}
|
||||
}
|
||||
|
||||
static final class Error extends GroupCreateResult {
|
||||
private final Error.Type errorType;
|
||||
|
||||
private Error(Error.Type errorType) {
|
||||
this.errorType = errorType;
|
||||
}
|
||||
|
||||
@Override
|
||||
void consume(@NonNull Consumer<Success> successConsumer,
|
||||
@NonNull Consumer<Error> errorConsumer)
|
||||
{
|
||||
errorConsumer.accept(this);
|
||||
}
|
||||
|
||||
public Type getErrorType() {
|
||||
return errorType;
|
||||
}
|
||||
|
||||
enum Type {
|
||||
ERROR_IO,
|
||||
ERROR_BUSY,
|
||||
ERROR_FAILED,
|
||||
ERROR_INVALID_NAME,
|
||||
ERROR_INVALID_MEMBER_COUNT
|
||||
}
|
||||
}
|
||||
|
||||
abstract void consume(@NonNull Consumer<Success> successConsumer,
|
||||
@NonNull Consumer<Error> errorConsumer);
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user