diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/details/AddGroupDetailsFragment.java b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/details/AddGroupDetailsFragment.java index 941b803be6..157a5757e0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/details/AddGroupDetailsFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/details/AddGroupDetailsFragment.java @@ -31,6 +31,7 @@ import com.dd.CircularProgressButton; import org.thoughtcrime.securesms.LoggingFragment; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.groups.ui.GroupMemberListView; +import org.thoughtcrime.securesms.groups.ui.creategroup.dialogs.NonGv2MemberDialog; import org.thoughtcrime.securesms.mediasend.AvatarSelectionActivity; import org.thoughtcrime.securesms.mediasend.AvatarSelectionBottomSheetDialogFragment; import org.thoughtcrime.securesms.mediasend.Media; @@ -43,6 +44,7 @@ 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 org.thoughtcrime.securesms.util.views.LearnMoreTextView; import java.util.Arrays; import java.util.List; @@ -90,6 +92,7 @@ public class AddGroupDetailsFragment extends LoggingFragment { GroupMemberListView members = view.findViewById(R.id.member_list); ImageView avatar = view.findViewById(R.id.group_avatar); View mmsWarning = view.findViewById(R.id.mms_warning); + LearnMoreTextView gv2Warning = view.findViewById(R.id.gv2_warning); avatarPlaceholder = VectorDrawableCompat.create(getResources(), R.drawable.ic_camera_outline_32_ultramarine, requireActivity().getTheme()); @@ -118,6 +121,12 @@ public class AddGroupDetailsFragment extends LoggingFragment { avatar.setVisibility(isMms ? View.GONE : View.VISIBLE); toolbar.setTitle(isMms ? R.string.AddGroupDetailsFragment__create_group : R.string.AddGroupDetailsFragment__name_this_group); }); + viewModel.getNonGv2CapableMembers().observe(getViewLifecycleOwner(), nonGv2CapableMembers -> { + gv2Warning.setVisibility(nonGv2CapableMembers.isEmpty() ? View.GONE : View.VISIBLE); + gv2Warning.setText(requireContext().getResources().getQuantityString(R.plurals.AddGroupDetailsFragment__d_members_do_not_support_new_groups, nonGv2CapableMembers.size(), nonGv2CapableMembers.size())); + gv2Warning.setLearnMoreVisible(true); + gv2Warning.setOnLinkClickListener(v -> NonGv2MemberDialog.showNonGv2Members(requireContext(), nonGv2CapableMembers)); + }); viewModel.getAvatar().observe(getViewLifecycleOwner(), avatarBytes -> { if (avatarBytes == null) { avatar.setImageDrawable(new InsetDrawable(avatarPlaceholder, ViewUtil.dpToPx(AVATAR_PLACEHOLDER_INSET_DP))); diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/details/AddGroupDetailsRepository.java b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/details/AddGroupDetailsRepository.java index 6dbfcdc3dd..ed0b2e4616 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/details/AddGroupDetailsRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/details/AddGroupDetailsRepository.java @@ -4,6 +4,7 @@ import android.content.Context; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.WorkerThread; import androidx.core.util.Consumer; import com.annimon.stream.Stream; @@ -11,7 +12,9 @@ import com.annimon.stream.Stream; import org.thoughtcrime.securesms.groups.GroupChangeBusyException; import org.thoughtcrime.securesms.groups.GroupChangeException; import org.thoughtcrime.securesms.groups.GroupManager; +import org.thoughtcrime.securesms.groups.GroupsV2CapabilityChecker; import org.thoughtcrime.securesms.groups.ui.GroupMemberEntry; +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; @@ -25,6 +28,8 @@ import java.util.Set; final class AddGroupDetailsRepository { + private static final String TAG = Log.tag(AddGroupDetailsRepository.class); + private final Context context; AddGroupDetailsRepository(@NonNull Context context) { @@ -65,4 +70,17 @@ final class AddGroupDetailsRepository { } }); } + + @WorkerThread + List checkCapabilities(@NonNull Collection newPotentialMemberList) { + try { + GroupsV2CapabilityChecker.refreshCapabilitiesIfNecessary(Recipient.resolvedList(newPotentialMemberList)); + } catch (IOException e) { + Log.w(TAG, "Could not get latest profiles for users, using known gv2 capability state", e); + } + + return Stream.of(Recipient.resolvedList(newPotentialMemberList)) + .filter(m -> m.getGroupsV2Capability() != Recipient.Capability.SUPPORTED) + .toList(); + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/details/AddGroupDetailsViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/details/AddGroupDetailsViewModel.java index 242e1bcda8..fb49121e34 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/details/AddGroupDetailsViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/details/AddGroupDetailsViewModel.java @@ -13,12 +13,16 @@ import androidx.lifecycle.ViewModelProvider; import com.annimon.stream.Collectors; import com.annimon.stream.Stream; +import org.thoughtcrime.securesms.groups.GroupsV2CapabilityChecker; import org.thoughtcrime.securesms.groups.ui.GroupMemberEntry; +import org.thoughtcrime.securesms.logging.Log; +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.io.IOException; import java.util.Collection; import java.util.HashSet; import java.util.List; @@ -35,6 +39,7 @@ public final class AddGroupDetailsViewModel extends ViewModel { private final LiveData isMms; private final LiveData canSubmitForm; private final AddGroupDetailsRepository repository; + private final LiveData> nonGv2CapableMembers; private AddGroupDetailsViewModel(@NonNull Collection recipientIds, @NonNull AddGroupDetailsRepository repository) @@ -44,9 +49,11 @@ public final class AddGroupDetailsViewModel extends ViewModel { MutableLiveData> initialMembers = new MutableLiveData<>(); LiveData isValidName = Transformations.map(name, name -> !TextUtils.isEmpty(name)); - members = LiveDataUtil.combineLatest(initialMembers, deleted, AddGroupDetailsViewModel::filterDeletedMembers); - isMms = Transformations.map(members, AddGroupDetailsViewModel::isAnyForcedSms); - canSubmitForm = LiveDataUtil.combineLatest(isMms, isValidName, (mms, validName) -> mms || validName); + + members = LiveDataUtil.combineLatest(initialMembers, deleted, AddGroupDetailsViewModel::filterDeletedMembers); + nonGv2CapableMembers = LiveDataUtil.mapAsync(members, memberList -> repository.checkCapabilities(Stream.of(memberList).map(newGroupCandidate -> newGroupCandidate.getMember().getId()).toList())); + isMms = Transformations.map(members, AddGroupDetailsViewModel::isAnyForcedSms); + canSubmitForm = LiveDataUtil.combineLatest(isMms, isValidName, (mms, validName) -> mms || validName); repository.resolveMembers(recipientIds, initialMembers::postValue); } @@ -71,6 +78,10 @@ public final class AddGroupDetailsViewModel extends ViewModel { return isMms; } + @NonNull LiveData> getNonGv2CapableMembers() { + return nonGv2CapableMembers; + } + void setAvatar(@Nullable byte[] avatar) { this.avatar.setValue(avatar); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/dialogs/NonGv2MemberDialog.java b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/dialogs/NonGv2MemberDialog.java new file mode 100644 index 0000000000..07b9136c67 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/dialogs/NonGv2MemberDialog.java @@ -0,0 +1,56 @@ +package org.thoughtcrime.securesms.groups.ui.creategroup.dialogs; + +import android.app.Dialog; +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; + +import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.groups.ui.GroupMemberEntry; +import org.thoughtcrime.securesms.groups.ui.GroupMemberListView; +import org.thoughtcrime.securesms.recipients.Recipient; + +import java.util.ArrayList; +import java.util.List; + +public final class NonGv2MemberDialog { + + private NonGv2MemberDialog() { + } + + public static @Nullable Dialog showNonGv2Members(@NonNull Context context, @NonNull List recipients) { + int size = recipients.size(); + if (size == 0) { + return null; + } + + AlertDialog.Builder builder = new AlertDialog.Builder(context) + // TODO: GV2 Need a URL for learn more + // .setNegativeButton(R.string.NonGv2MemberDialog_learn_more, (dialog, which) -> { + // }) + .setPositiveButton(android.R.string.ok, null); + if (size == 1) { + builder.setMessage(context.getString(R.string.NonGv2MemberDialog_single_users_are_non_gv2_capable, recipients.get(0).getDisplayName(context))); + } else { + builder.setMessage(context.getResources().getQuantityString(R.plurals.NonGv2MemberDialog_d_users_are_non_gv2_capable, size, size)) + .setView(R.layout.dialog_multiple_members_non_gv2_capable); + } + + Dialog dialog = builder.show(); + if (size > 1) { + GroupMemberListView nonGv2CapableMembers = dialog.findViewById(R.id.list_non_gv2_members); + + List pendingMembers = new ArrayList<>(recipients.size()); + for (Recipient r : recipients) { + pendingMembers.add(new GroupMemberEntry.NewGroupCandidate(r)); + } + + //noinspection ConstantConditions + nonGv2CapableMembers.setMembers(pendingMembers); + } + + return dialog; + } +} diff --git a/app/src/main/res/layout/add_group_details_fragment.xml b/app/src/main/res/layout/add_group_details_fragment.xml index 360a6f6633..aafdafac7c 100644 --- a/app/src/main/res/layout/add_group_details_fragment.xml +++ b/app/src/main/res/layout/add_group_details_fragment.xml @@ -54,10 +54,28 @@ android:textAppearance="@style/TextAppearance.Signal.Body2" android:textColor="@color/white" android:visibility="gone" - app:layout_constraintBottom_toTopOf="@id/member_list_header" + app:layout_constraintBottom_toTopOf="@id/gv2_warning" app:layout_constraintTop_toBottomOf="@id/group_avatar" tools:visibility="visible" /> + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7a1322a628..33a94b3a2b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -525,6 +525,17 @@ Remove SMS contact Remove %1$s from this group? + + %d member does not support New Groups, so this will be a Legacy Group. + %d members do not support New Groups, so this group will be a Legacy Group. + + + + A Legacy Group will be created because ā€œ%1$sā€ is using an old version of Signal. You can create a New Style Group with them after they update Signal, or remove them before creating the group. + + A Legacy Group will be created because %1$d member is using an old version of Signal. You can create a New Style Group with them after they update Signal, or remove them before creating the group. + A Legacy Group will be created because %1$d members are using an old version of Signal. You can create a New Style Group with them after they update Signal, or remove them before creating the group. + Disappearing messages