Show groups that have the same member list during group creation.

This commit is contained in:
Greyson Parrelli
2026-03-18 22:54:29 -04:00
committed by Cody Henthorne
parent f09bf5b14c
commit 9de75b3e1f
11 changed files with 140 additions and 4 deletions

View File

@@ -29,12 +29,16 @@ import com.bumptech.glide.Glide;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.transition.Transition;
import com.bumptech.glide.RequestManager;
import com.google.android.material.chip.ChipGroup;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.signal.core.util.EditTextUtil;
import org.signal.core.ui.logging.LoggingFragment;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.avatar.picker.AvatarPickerFragment;
import org.thoughtcrime.securesms.contacts.ContactChip;
import org.thoughtcrime.securesms.conversation.ConversationIntents;
import org.thoughtcrime.securesms.components.settings.app.privacy.expire.ExpireTimerSettingsFragment;
import org.thoughtcrime.securesms.groups.ui.GroupMemberListView;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
@@ -150,6 +154,29 @@ public class AddGroupDetailsFragment extends LoggingFragment {
startActivityForResult(RecipientDisappearingMessagesActivity.forCreateGroup(requireContext(), viewModel.getDisappearingMessagesTimer().getValue()), REQUEST_DISAPPEARING_TIMER);
});
View sameGroupsSection = view.findViewById(R.id.same_groups_section);
ChipGroup sameGroupsChipGroup = view.findViewById(R.id.same_groups_chip_group);
if (SignalStore.labs().getGroupSuggestionsForMembers()) {
viewModel.getSameGroups().observe(getViewLifecycleOwner(), groups -> {
sameGroupsChipGroup.removeAllViews();
if (groups.isEmpty()) {
sameGroupsSection.setVisibility(View.GONE);
} else {
sameGroupsSection.setVisibility(View.VISIBLE);
RequestManager requestManager = Glide.with(this);
for (Recipient group : groups) {
ContactChip chip = new ContactChip(requireContext());
chip.setText(group.getDisplayName(requireContext()));
chip.setAvatar(requestManager, group, null);
chip.setCloseIconVisible(false);
chip.setOnClickListener(v -> navigateToConversation(group.getId()));
sameGroupsChipGroup.addView(chip);
}
}
});
}
name.requestFocus();
getParentFragmentManager().setFragmentResultListener(AvatarPickerFragment.REQUEST_KEY_SELECT_AVATAR,
@@ -266,6 +293,14 @@ public class AddGroupDetailsFragment extends LoggingFragment {
create.setEnabled(isEnabled);
}
private void navigateToConversation(@NonNull RecipientId groupRecipientId) {
ConversationIntents.createBuilder(requireContext(), groupRecipientId, -1L)
.subscribe(builder -> {
startActivity(builder.build());
requireActivity().finish();
});
}
private void showAvatarPicker() {
Media media = viewModel.getAvatarMedia();

View File

@@ -8,6 +8,8 @@ import androidx.core.util.Consumer;
import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.GroupRecord;
import org.thoughtcrime.securesms.groups.GroupChangeBusyException;
import org.thoughtcrime.securesms.groups.GroupChangeException;
import org.thoughtcrime.securesms.groups.GroupManager;
@@ -44,6 +46,19 @@ final class AddGroupDetailsRepository {
});
}
void getGroupsWithSameMembers(@NonNull Set<RecipientId> memberIds, Consumer<List<Recipient>> consumer) {
SignalExecutors.BOUNDED.execute(() -> {
List<GroupRecord> groups = SignalDatabase.groups().getGroupsWithExactMembers(memberIds);
List<Recipient> recipients = new ArrayList<>(groups.size());
for (GroupRecord group : groups) {
recipients.add(Recipient.resolved(group.getRecipientId()));
}
consumer.accept(recipients);
});
}
void createGroup(@NonNull Set<RecipientId> members,
@Nullable byte[] avatar,
@Nullable String name,

View File

@@ -16,6 +16,7 @@ import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.groups.ui.GroupMemberEntry;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.signal.core.models.media.Media;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.DefaultValueLiveData;
import org.thoughtcrime.securesms.util.SingleLiveEvent;
@@ -38,6 +39,7 @@ public final class AddGroupDetailsViewModel extends ViewModel {
private final MutableLiveData<Integer> disappearingMessagesTimer = new MutableLiveData<>(SignalStore.settings().getUniversalExpireTimer());
private final LiveData<Boolean> isMms;
private final LiveData<Boolean> canSubmitForm;
private final LiveData<List<Recipient>> sameGroups;
private final AddGroupDetailsRepository repository;
private Media avatarMedia;
@@ -53,6 +55,16 @@ public final class AddGroupDetailsViewModel extends ViewModel {
members = LiveDataUtil.combineLatest(initialMembers, deleted, AddGroupDetailsViewModel::filterDeletedMembers);
isMms = Transformations.map(members, AddGroupDetailsViewModel::isAnyForcedSms);
canSubmitForm = LiveDataUtil.combineLatest(isMms, isValidName, (mms, validName) -> mms || validName);
sameGroups = Transformations.switchMap(members, memberList -> {
MutableLiveData<List<Recipient>> result = new MutableLiveData<>(Collections.emptyList());
if (!memberList.isEmpty()) {
Set<RecipientId> memberIds = Stream.of(memberList)
.map(member -> member.getMember().getId())
.collect(Collectors.toSet());
repository.getGroupsWithSameMembers(memberIds, result::postValue);
}
return result;
});
repository.resolveMembers(recipientIds, initialMembers::postValue);
}
@@ -77,6 +89,10 @@ public final class AddGroupDetailsViewModel extends ViewModel {
return isMms;
}
@NonNull LiveData<List<Recipient>> getSameGroups() {
return sameGroups;
}
@NonNull LiveData<Integer> getDisappearingMessagesTimer() {
return disappearingMessagesTimer;
}