diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 6b644d755b..4d6bf724e0 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -492,6 +492,9 @@
+
+
diff --git a/app/src/main/java/org/thoughtcrime/securesms/ContactSelectionListFragment.java b/app/src/main/java/org/thoughtcrime/securesms/ContactSelectionListFragment.java
index 948236a4bd..84cb24ce54 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/ContactSelectionListFragment.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/ContactSelectionListFragment.java
@@ -255,7 +255,7 @@ public final class ContactSelectionListFragment extends Fragment
: Collections.unmodifiableSet(Stream.of(currentSelection).collect(Collectors.toSet()));
}
- private boolean isMulti() {
+ public boolean isMulti() {
return requireActivity().getIntent().getBooleanExtra(MULTI_SELECT, false);
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/AvatarImageView.java b/app/src/main/java/org/thoughtcrime/securesms/components/AvatarImageView.java
index 5b4ae785a8..9e5d90e7aa 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/AvatarImageView.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/AvatarImageView.java
@@ -11,6 +11,7 @@ import android.util.AttributeSet;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatImageView;
+import androidx.fragment.app.FragmentActivity;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
@@ -24,6 +25,7 @@ import org.thoughtcrime.securesms.groups.ui.managegroup.ManageGroupActivity;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.recipients.Recipient;
+import org.thoughtcrime.securesms.recipients.ui.bottomsheet.RecipientBottomSheetDialogFragment;
import org.thoughtcrime.securesms.util.AvatarUtil;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.ThemeUtil;
@@ -162,11 +164,17 @@ public final class AvatarImageView extends AppCompatImageView {
private void setAvatarClickHandler(@NonNull final Recipient recipient, boolean quickContactEnabled) {
if (quickContactEnabled) {
super.setOnClickListener(v -> {
+ Context context = getContext();
if (FeatureFlags.newGroupUI() && recipient.isPushGroup()) {
- getContext().startActivity(ManageGroupActivity.newIntent(getContext(), recipient.requireGroupId().requirePush()),
- ManageGroupActivity.createTransitionBundle(getContext(), this));
+ context.startActivity(ManageGroupActivity.newIntent(context, recipient.requireGroupId().requirePush()),
+ ManageGroupActivity.createTransitionBundle(context, this));
} else {
- getContext().startActivity(RecipientPreferenceActivity.getLaunchIntent(getContext(), recipient.getId()));
+ if (context instanceof FragmentActivity) {
+ RecipientBottomSheetDialogFragment.create(recipient.getId(), null)
+ .show(((FragmentActivity) context).getSupportFragmentManager(), "BOTTOM");
+ } else {
+ context.startActivity(RecipientPreferenceActivity.getLaunchIntent(context, recipient.getId()));
+ }
}
});
} else {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ContactFilterToolbar.java b/app/src/main/java/org/thoughtcrime/securesms/components/ContactFilterToolbar.java
index b8f32d0c7f..68a20046fd 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/ContactFilterToolbar.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/ContactFilterToolbar.java
@@ -3,11 +3,6 @@ package org.thoughtcrime.securesms.components;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Rect;
-
-import androidx.annotation.NonNull;
-import androidx.appcompat.widget.Toolbar;
-import androidx.core.widget.TextViewCompat;
-
import android.text.Editable;
import android.text.InputType;
import android.text.TextWatcher;
@@ -18,20 +13,23 @@ import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
+import androidx.annotation.NonNull;
+import androidx.annotation.StringRes;
+import androidx.core.widget.TextViewCompat;
+
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.util.ServiceUtil;
-import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.views.DarkOverflowToolbar;
-public class ContactFilterToolbar extends DarkOverflowToolbar {
+public final class ContactFilterToolbar extends DarkOverflowToolbar {
private OnFilterChangedListener listener;
- private EditText searchText;
- private AnimatingToggle toggle;
- private ImageView keyboardToggle;
- private ImageView dialpadToggle;
- private ImageView clearToggle;
- private LinearLayout toggleContainer;
+ private final EditText searchText;
+ private final AnimatingToggle toggle;
+ private final ImageView keyboardToggle;
+ private final ImageView dialpadToggle;
+ private final ImageView clearToggle;
+ private final LinearLayout toggleContainer;
public ContactFilterToolbar(Context context) {
this(context, null);
@@ -45,12 +43,12 @@ public class ContactFilterToolbar extends DarkOverflowToolbar {
super(context, attrs, defStyleAttr);
inflate(context, R.layout.contact_filter_toolbar, this);
- this.searchText = ViewUtil.findById(this, R.id.search_view);
- this.toggle = ViewUtil.findById(this, R.id.button_toggle);
- this.keyboardToggle = ViewUtil.findById(this, R.id.search_keyboard);
- this.dialpadToggle = ViewUtil.findById(this, R.id.search_dialpad);
- this.clearToggle = ViewUtil.findById(this, R.id.search_clear);
- this.toggleContainer = ViewUtil.findById(this, R.id.toggle_container);
+ this.searchText = findViewById(R.id.search_view);
+ this.toggle = findViewById(R.id.button_toggle);
+ this.keyboardToggle = findViewById(R.id.search_keyboard);
+ this.dialpadToggle = findViewById(R.id.search_dialpad);
+ this.clearToggle = findViewById(R.id.search_clear);
+ this.toggleContainer = findViewById(R.id.toggle_container);
this.keyboardToggle.setOnClickListener(new View.OnClickListener() {
@Override
@@ -103,11 +101,11 @@ public class ContactFilterToolbar extends DarkOverflowToolbar {
setLogo(null);
setContentInsetStartWithNavigation(0);
expandTapArea(toggleContainer, dialpadToggle);
- styleSearchText(searchText, context, attrs, defStyleAttr);
+ applyAttributes(searchText, context, attrs, defStyleAttr);
searchText.requestFocus();
}
- private void styleSearchText(@NonNull EditText searchText,
+ private void applyAttributes(@NonNull EditText searchText,
@NonNull Context context,
@NonNull AttributeSet attrs,
int defStyle)
@@ -121,6 +119,9 @@ public class ContactFilterToolbar extends DarkOverflowToolbar {
if (styleResource != -1) {
TextViewCompat.setTextAppearance(searchText, styleResource);
}
+ if (!attributes.getBoolean(R.styleable.ContactFilterToolbar_showDialpad, true)) {
+ dialpadToggle.setVisibility(GONE);
+ }
attributes.recycle();
}
@@ -133,6 +134,10 @@ public class ContactFilterToolbar extends DarkOverflowToolbar {
this.listener = listener;
}
+ public void setHint(@StringRes int hint) {
+ searchText.setHint(hint);
+ }
+
private void notifyListener() {
if (listener != null) listener.onFilterChanged(searchText.getText().toString());
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.java
index 78dcdcd293..13333b7ff2 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.java
@@ -101,7 +101,7 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter getUnfilteredResults() {
ArrayList cursorList = new ArrayList<>();
- addRecentsSection(cursorList);
- addContactsSection(cursorList);
+ if (groupsOnly(mode)) {
+ addGroupsSection(cursorList);
+ } else {
+ addRecentsSection(cursorList);
+ addContactsSection(cursorList);
+ }
return cursorList;
}
@@ -376,6 +380,10 @@ public class ContactsCursorLoader extends CursorLoader {
return flagSet(mode, DisplayMode.FLAG_ACTIVE_GROUPS);
}
+ private static boolean groupsOnly(int mode) {
+ return mode == DisplayMode.FLAG_ACTIVE_GROUPS;
+ }
+
private static boolean flagSet(int mode, int flag) {
return (mode & flag) > 0;
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/addtogroup/AddToGroupRepository.java b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/addtogroup/AddToGroupRepository.java
new file mode 100644
index 0000000000..a9e9852624
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/addtogroup/AddToGroupRepository.java
@@ -0,0 +1,59 @@
+package org.thoughtcrime.securesms.groups.ui.addtogroup;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+
+import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
+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.MembershipNotSuitableForV2Exception;
+import org.thoughtcrime.securesms.groups.ui.GroupChangeErrorCallback;
+import org.thoughtcrime.securesms.groups.ui.GroupChangeFailureReason;
+import org.thoughtcrime.securesms.logging.Log;
+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.Collections;
+
+final class AddToGroupRepository {
+
+ private static final String TAG = Log.tag(AddToGroupRepository.class);
+
+ private final Context context;
+
+ AddToGroupRepository() {
+ this.context = ApplicationDependencies.getApplication();
+ }
+
+ public void add(@NonNull RecipientId recipientId,
+ @NonNull Recipient groupRecipient,
+ @NonNull GroupChangeErrorCallback error,
+ @NonNull Runnable success)
+ {
+ SignalExecutors.UNBOUNDED.execute(() -> {
+ try {
+ GroupId.Push pushGroupId = groupRecipient.requireGroupId().requirePush();
+
+ GroupManager.addMembers(context, pushGroupId, Collections.singletonList(recipientId));
+
+ success.run();
+ } catch (GroupInsufficientRightsException | GroupNotAMemberException e) {
+ Log.w(TAG, e);
+ error.onError(GroupChangeFailureReason.NO_RIGHTS);
+ } catch (GroupChangeFailedException | GroupChangeBusyException | IOException e) {
+ Log.w(TAG, e);
+ error.onError(GroupChangeFailureReason.OTHER);
+ } catch (MembershipNotSuitableForV2Exception e) {
+ Log.w(TAG, e);
+ error.onError(GroupChangeFailureReason.NOT_CAPABLE);
+ }
+ });
+ }
+}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/addtogroup/AddToGroupViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/addtogroup/AddToGroupViewModel.java
new file mode 100644
index 0000000000..dc7de1c4fe
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/addtogroup/AddToGroupViewModel.java
@@ -0,0 +1,125 @@
+package org.thoughtcrime.securesms.groups.ui.addtogroup;
+
+import android.app.Application;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.ViewModel;
+import androidx.lifecycle.ViewModelProvider;
+
+import org.thoughtcrime.securesms.R;
+import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
+import org.thoughtcrime.securesms.groups.ui.GroupErrors;
+import org.thoughtcrime.securesms.recipients.Recipient;
+import org.thoughtcrime.securesms.recipients.RecipientId;
+import org.thoughtcrime.securesms.util.SingleLiveEvent;
+import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
+
+import java.util.List;
+import java.util.Objects;
+
+public final class AddToGroupViewModel extends ViewModel {
+
+ private final Application context;
+ private final AddToGroupRepository repository;
+ private final RecipientId recipientId;
+ private final SingleLiveEvent events = new SingleLiveEvent<>();
+
+ private AddToGroupViewModel(@NonNull RecipientId recipientId) {
+ this.context = ApplicationDependencies.getApplication();
+ this.recipientId = recipientId;
+ this.repository = new AddToGroupRepository();
+ }
+
+ public SingleLiveEvent getEvents() {
+ return events;
+ }
+
+ void onContinueWithSelection(@NonNull List groupRecipientIds) {
+ if (groupRecipientIds.isEmpty()) {
+ events.postValue(new Event.CloseEvent());
+ } else if (groupRecipientIds.size() == 1) {
+ SignalExecutors.BOUNDED.execute(() -> {
+ Recipient groupRecipient = Recipient.resolved(groupRecipientIds.get(0));
+ String recipientName = Recipient.resolved(recipientId).getDisplayName(context);
+ String groupName = groupRecipient.getDisplayName(context);
+
+ events.postValue(new Event.AddToSingleGroupConfirmationEvent(context.getResources().getString(R.string.AddToGroupActivity_add_member),
+ context.getResources().getString(R.string.AddToGroupActivity_add_s_to_s, recipientName, groupName),
+ groupRecipient, recipientName, groupName));
+ });
+ } else {
+ throw new AssertionError("Does not support multi-select");
+ }
+ }
+
+ void onAddToGroupsConfirmed(@NonNull Event.AddToSingleGroupConfirmationEvent event) {
+ repository.add(recipientId,
+ event.groupRecipient,
+ error -> events.postValue(new Event.ToastEvent(context.getResources().getString(GroupErrors.getUserDisplayMessage(error)))),
+ () -> {
+ events.postValue(new Event.ToastEvent(context.getResources().getString(R.string.AddToGroupActivity_s_added_to_s, event.recipientName, event.groupName)));
+ events.postValue(new Event.CloseEvent());
+ });
+ }
+
+ static abstract class Event {
+
+ static class CloseEvent extends Event {
+ }
+
+ static class ToastEvent extends Event {
+ private final String message;
+
+ ToastEvent(@NonNull String message) {
+ this.message = message;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+ }
+
+ static class AddToSingleGroupConfirmationEvent extends Event {
+ private final String title;
+ private final String message;
+ private final Recipient groupRecipient;
+ private final String recipientName;
+ private final String groupName;
+
+ AddToSingleGroupConfirmationEvent(@NonNull String title,
+ @NonNull String message,
+ @NonNull Recipient groupRecipient,
+ @NonNull String recipientName,
+ @NonNull String groupName)
+ {
+ this.title = title;
+ this.message = message;
+ this.groupRecipient = groupRecipient;
+ this.recipientName = recipientName;
+ this.groupName = groupName;
+ }
+
+ String getTitle() {
+ return title;
+ }
+
+ String getMessage() {
+ return message;
+ }
+ }
+ }
+
+ public static class Factory implements ViewModelProvider.Factory {
+
+ private final RecipientId recipientId;
+
+ public Factory(@NonNull RecipientId recipientId) {
+ this.recipientId = recipientId;
+ }
+
+ @Override
+ public @NonNull T create(@NonNull Class modelClass) {
+ return Objects.requireNonNull(modelClass.cast(new AddToGroupViewModel(recipientId)));
+ }
+ }
+}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/addtogroup/AddToGroupsActivity.java b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/addtogroup/AddToGroupsActivity.java
new file mode 100644
index 0000000000..8de88a1721
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/addtogroup/AddToGroupsActivity.java
@@ -0,0 +1,164 @@
+package org.thoughtcrime.securesms.groups.ui.addtogroup;
+
+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.app.AlertDialog;
+import androidx.lifecycle.ViewModelProviders;
+
+import com.annimon.stream.Stream;
+
+import org.thoughtcrime.securesms.ContactSelectionActivity;
+import org.thoughtcrime.securesms.ContactSelectionListFragment;
+import org.thoughtcrime.securesms.GroupCreateActivity;
+import org.thoughtcrime.securesms.R;
+import org.thoughtcrime.securesms.contacts.ContactsCursorLoader;
+import org.thoughtcrime.securesms.groups.ui.addtogroup.AddToGroupViewModel.Event;
+import org.thoughtcrime.securesms.recipients.RecipientId;
+import org.thoughtcrime.securesms.util.FeatureFlags;
+import org.whispersystems.libsignal.util.guava.Optional;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Group selection activity, will add a single member to selected groups.
+ */
+public final class AddToGroupsActivity extends ContactSelectionActivity {
+
+ private static final int MINIMUM_GROUP_SELECT_SIZE = 1;
+
+ private static final String EXTRA_RECIPIENT_ID = "RECIPIENT_ID";
+
+ private View next;
+ private AddToGroupViewModel viewModel;
+
+ public static Intent newIntent(@NonNull Context context,
+ @NonNull RecipientId recipientId,
+ @NonNull List currentGroupsMemberOf)
+ {
+ if (!FeatureFlags.newGroupUI()) {
+ return new Intent(context, GroupCreateActivity.class);
+ }
+
+ Intent intent = new Intent(context, AddToGroupsActivity.class);
+
+ intent.putExtra(ContactSelectionListFragment.MULTI_SELECT, false);
+ intent.putExtra(ContactSelectionListFragment.REFRESHABLE, false);
+ intent.putExtra(ContactSelectionActivity.EXTRA_LAYOUT_RES_ID, R.layout.add_to_group_activity);
+ intent.putExtra(EXTRA_RECIPIENT_ID, recipientId);
+
+ intent.putExtra(ContactSelectionListFragment.DISPLAY_MODE, ContactsCursorLoader.DisplayMode.FLAG_ACTIVE_GROUPS);
+ intent.putExtra(ContactSelectionListFragment.TOTAL_CAPACITY, ContactSelectionListFragment.NO_LIMIT);
+
+ intent.putParcelableArrayListExtra(ContactSelectionListFragment.CURRENT_SELECTION, new ArrayList<>(currentGroupsMemberOf));
+
+ return intent;
+ }
+
+ @Override
+ public void onCreate(Bundle bundle, boolean ready) {
+ super.onCreate(bundle, ready);
+
+ Objects.requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true);
+
+ next = findViewById(R.id.next);
+
+ getToolbar().setHint(contactsFragment.isMulti() ? R.string.AddToGroupActivity_add_to_groups : R.string.AddToGroupActivity_add_to_group);
+
+ next.setVisibility(contactsFragment.isMulti() ? View.VISIBLE : View.GONE);
+
+ disableNext();
+ next.setOnClickListener(v -> handleNextPressed());
+
+ AddToGroupViewModel.Factory factory = new AddToGroupViewModel.Factory(getRecipientId());
+ viewModel = ViewModelProviders.of(this, factory)
+ .get(AddToGroupViewModel.class);
+
+
+ viewModel.getEvents().observe(this, event -> {
+ if (event instanceof Event.CloseEvent) {
+ finish();
+ } else if (event instanceof Event.ToastEvent) {
+ Toast.makeText(this, ((Event.ToastEvent) event).getMessage(), Toast.LENGTH_SHORT).show();
+ } else if (event instanceof Event.AddToSingleGroupConfirmationEvent) {
+ Event.AddToSingleGroupConfirmationEvent addEvent = (Event.AddToSingleGroupConfirmationEvent) event;
+ new AlertDialog.Builder(this)
+ .setTitle(addEvent.getTitle())
+ .setMessage(addEvent.getMessage())
+ .setPositiveButton(android.R.string.ok, (dialog, which) -> viewModel.onAddToGroupsConfirmed(addEvent))
+ .setNegativeButton(android.R.string.cancel, null)
+ .show();
+ } else {
+ throw new AssertionError();
+ }
+ });
+ }
+
+ private @NonNull RecipientId getRecipientId() {
+ return getIntent().getParcelableExtra(EXTRA_RECIPIENT_ID);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
+ finish();
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public void onContactSelected(Optional recipientId, String number) {
+ if (contactsFragment.isMulti()) {
+ if (contactsFragment.hasQueryFilter()) {
+ getToolbar().clear();
+ }
+
+ if (contactsFragment.getSelectedContactsCount() >= MINIMUM_GROUP_SELECT_SIZE) {
+ enableNext();
+ }
+ } else {
+ if (recipientId.isPresent()) {
+ viewModel.onContinueWithSelection(Collections.singletonList(recipientId.get()));
+ }
+ }
+ }
+
+ @Override
+ public void onContactDeselected(Optional recipientId, String number) {
+ if (contactsFragment.hasQueryFilter()) {
+ getToolbar().clear();
+ }
+
+ if (contactsFragment.getSelectedContactsCount() < MINIMUM_GROUP_SELECT_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() {
+ List groupsRecipientIds = Stream.of(contactsFragment.getSelectedContacts())
+ .map(selectedContact -> selectedContact.getOrCreateRecipientId(this))
+ .toList();
+
+ viewModel.onContinueWithSelection(groupsRecipientIds);
+ }
+}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientBottomSheetDialogFragment.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientBottomSheetDialogFragment.java
index 4e2616e5f8..f29edd2b47 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientBottomSheetDialogFragment.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientBottomSheetDialogFragment.java
@@ -50,6 +50,7 @@ public final class RecipientBottomSheetDialogFragment extends BottomSheetDialogF
private Button blockButton;
private Button unblockButton;
private Button addContactButton;
+ private Button addToGroupButton;
private Button viewSafetyNumberButton;
private Button makeGroupAdminButton;
private Button removeAdminButton;
@@ -93,6 +94,7 @@ public final class RecipientBottomSheetDialogFragment extends BottomSheetDialogF
blockButton = view.findViewById(R.id.rbs_block_button);
unblockButton = view.findViewById(R.id.rbs_unblock_button);
addContactButton = view.findViewById(R.id.rbs_add_contact_button);
+ addToGroupButton = view.findViewById(R.id.rbs_add_to_group_button);
viewSafetyNumberButton = view.findViewById(R.id.rbs_view_safety_number_button);
makeGroupAdminButton = view.findViewById(R.id.rbs_make_group_admin_button);
removeAdminButton = view.findViewById(R.id.rbs_remove_group_admin_button);
@@ -188,6 +190,11 @@ public final class RecipientBottomSheetDialogFragment extends BottomSheetDialogF
removeFromGroupButton.setOnClickListener(view -> viewModel.onRemoveFromGroupClicked(requireActivity(), this::dismiss));
+ addToGroupButton.setOnClickListener(view -> {
+ dismiss();
+ viewModel.onAddToGroupButton(requireActivity());
+ });
+
viewModel.getAdminActionBusy().observe(getViewLifecycleOwner(), busy -> {
adminActionBusy.setVisibility(busy ? View.VISIBLE : View.GONE);
diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientDialogRepository.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientDialogRepository.java
index 2e494bb105..7f7157f1ad 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientDialogRepository.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientDialogRepository.java
@@ -8,6 +8,7 @@ import androidx.core.util.Consumer;
import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper;
import org.thoughtcrime.securesms.database.DatabaseFactory;
+import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.IdentityDatabase;
import org.thoughtcrime.securesms.groups.GroupChangeBusyException;
import org.thoughtcrime.securesms.groups.GroupChangeFailedException;
@@ -24,6 +25,8 @@ import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Objects;
final class RecipientDialogRepository {
@@ -117,6 +120,22 @@ final class RecipientDialogRepository {
onComplete::accept);
}
+ void getGroupMembership(@NonNull Consumer> onComplete) {
+ SimpleTask.run(SignalExecutors.UNBOUNDED,
+ () -> {
+ GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
+ List groupRecords = groupDatabase.getPushGroupsContainingMember(recipientId);
+ ArrayList groupRecipients = new ArrayList<>(groupRecords.size());
+
+ for (GroupDatabase.GroupRecord groupRecord : groupRecords) {
+ groupRecipients.add(groupRecord.getRecipientId());
+ }
+
+ return groupRecipients;
+ },
+ onComplete::accept);
+ }
+
interface IdentityCallback {
void remoteIdentity(@Nullable IdentityDatabase.IdentityRecord identityRecord);
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientDialogViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientDialogViewModel.java
index c32fca8e5a..5b04ab3be1 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientDialogViewModel.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientDialogViewModel.java
@@ -11,7 +11,6 @@ import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
-import androidx.lifecycle.Transformations;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
@@ -24,6 +23,7 @@ import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.groups.LiveGroup;
import org.thoughtcrime.securesms.groups.ui.GroupChangeFailureReason;
import org.thoughtcrime.securesms.groups.ui.GroupErrors;
+import org.thoughtcrime.securesms.groups.ui.addtogroup.AddToGroupsActivity;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
@@ -171,6 +171,10 @@ final class RecipientDialogViewModel extends ViewModel {
recipientDialogRepository.refreshRecipient();
}
+ void onAddToGroupButton(@NonNull Activity activity) {
+ recipientDialogRepository.getGroupMembership(existingGroups -> activity.startActivity(AddToGroupsActivity.newIntent(activity, recipientDialogRepository.getRecipientId(), existingGroups)));
+ }
+
@WorkerThread
private void showErrorToast(@NonNull GroupChangeFailureReason e) {
Util.runOnMain(() -> Toast.makeText(context, GroupErrors.getUserDisplayMessage(e), Toast.LENGTH_LONG).show());
diff --git a/app/src/main/res/layout/add_to_group_activity.xml b/app/src/main/res/layout/add_to_group_activity.xml
new file mode 100644
index 0000000000..871e8534e9
--- /dev/null
+++ b/app/src/main/res/layout/add_to_group_activity.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/recipient_bottom_sheet.xml b/app/src/main/res/layout/recipient_bottom_sheet.xml
index 97a58534d8..6627bd910e 100644
--- a/app/src/main/res/layout/recipient_bottom_sheet.xml
+++ b/app/src/main/res/layout/recipient_bottom_sheet.xml
@@ -119,6 +119,16 @@
app:drawableStartCompat="?attr/recipient_add_contact_icon"
tools:visibility="visible" />
+
+