From fe11ebce553e5392c5b6e7c3658d73a123cad7d1 Mon Sep 17 00:00:00 2001 From: Alan Evans Date: Mon, 30 Nov 2020 14:50:11 -0400 Subject: [PATCH] Inline Group Invite Link feature flags. --- app/src/main/AndroidManifest.xml | 4 -- .../conversation/ConversationActivity.java | 2 +- .../ConversationGroupViewModel.java | 1 - .../GroupJoinBottomSheetDialogFragment.java | 39 ++-------- ...dateRequiredBottomSheetDialogFragment.java | 26 ++----- .../ui/managegroup/ManageGroupFragment.java | 50 ++++--------- .../PendingMemberInvitesActivity.java | 72 ------------------- .../securesms/util/FeatureFlags.java | 35 --------- .../main/res/layout/group_manage_fragment.xml | 47 +----------- ...pending_and_requesting_member_activity.xml | 2 +- .../group_pending_member_invites_activity.xml | 32 --------- app/src/main/res/values/strings.xml | 6 +- 12 files changed, 29 insertions(+), 287 deletions(-) delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/groups/ui/pendingmemberinvites/PendingMemberInvitesActivity.java delete mode 100644 app/src/main/res/layout/group_pending_member_invites_activity.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 769e9caee7..fc7482e89f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -280,10 +280,6 @@ android:launchMode="singleTask" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/> - - diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java index 21e2a57cde..3f844207e2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -1745,7 +1745,7 @@ public class ConversationActivity extends PassphraseRequiredActivity reminderView.get().setOnActionClickListener(this::handleReminderAction); reminderView.get().setOnDismissListener(() -> inviteReminderModel.dismissReminder()); reminderView.get().showReminder(inviteReminder.get()); - } else if (actionableRequestingMembers != null && actionableRequestingMembers > 0 && FeatureFlags.groupsV2manageGroupLinks()) { + } else if (actionableRequestingMembers != null && actionableRequestingMembers > 0) { reminderView.get().showReminder(PendingGroupJoinRequestsReminder.create(this, actionableRequestingMembers)); reminderView.get().setOnActionClickListener(id -> { if (id == R.id.reminder_action_review_join_requests) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationGroupViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationGroupViewModel.java index 360e2c7712..8e62abdb14 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationGroupViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationGroupViewModel.java @@ -138,7 +138,6 @@ final class ConversationGroupViewModel extends ViewModel { private static int mapToActionableRequestingMemberCount(@Nullable GroupRecord record) { if (record != null && - FeatureFlags.groupsV2manageGroupLinks() && record.isV2Group() && record.memberLevel(Recipient.self()) == GroupDatabase.MemberLevel.ADMINISTRATOR) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/invitesandrequests/joining/GroupJoinBottomSheetDialogFragment.java b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/invitesandrequests/joining/GroupJoinBottomSheetDialogFragment.java index 71f55228ab..0ce9f2c5ee 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/invitesandrequests/joining/GroupJoinBottomSheetDialogFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/invitesandrequests/joining/GroupJoinBottomSheetDialogFragment.java @@ -30,8 +30,6 @@ import org.thoughtcrime.securesms.jobs.RetrieveProfileJob; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.BottomSheetUtil; -import org.thoughtcrime.securesms.util.FeatureFlags; -import org.thoughtcrime.securesms.util.PlayStoreUtil; import org.thoughtcrime.securesms.util.ThemeUtil; public final class GroupJoinBottomSheetDialogFragment extends BottomSheetDialogFragment { @@ -101,20 +99,6 @@ public final class GroupJoinBottomSheetDialogFragment extends BottomSheetDialogF groupDetails.setText(requireContext().getResources().getQuantityString(R.plurals.GroupJoinBottomSheetDialogFragment_group_dot_d_members, details.getGroupMembershipCount(), details.getGroupMembershipCount())); switch (getGroupJoinStatus()) { - case COMING_SOON: - groupJoinExplain.setText(R.string.GroupJoinUpdateRequiredBottomSheetDialogFragment_coming_soon); - groupCancelButton.setText(android.R.string.ok); - groupJoinButton.setVisibility(View.GONE); - break; - case UPDATE_TO_JOIN: - groupJoinExplain.setText(R.string.GroupJoinUpdateRequiredBottomSheetDialogFragment_update_message); - groupJoinButton.setText(R.string.GroupJoinUpdateRequiredBottomSheetDialogFragment_update_signal); - groupJoinButton.setOnClickListener(v -> { - PlayStoreUtil.openPlayStoreOrOurApkDownloadPage(requireContext()); - dismiss(); - }); - groupJoinButton.setVisibility(View.VISIBLE); - break; case UPDATE_LINKED_DEVICE_TO_JOIN: groupJoinExplain.setText(R.string.GroupJoinUpdateRequiredBottomSheetDialogFragment_update_linked_device_message); groupCancelButton.setText(android.R.string.ok); @@ -162,19 +146,10 @@ public final class GroupJoinBottomSheetDialogFragment extends BottomSheetDialogF } private static ExtendedGroupJoinStatus getGroupJoinStatus() { - FeatureFlags.GroupJoinStatus groupJoinStatus = FeatureFlags.clientLocalGroupJoinStatus(); - - switch (groupJoinStatus) { - case COMING_SOON : return ExtendedGroupJoinStatus.COMING_SOON; - case UPDATE_TO_JOIN: return ExtendedGroupJoinStatus.UPDATE_TO_JOIN; - case LOCAL_CAN_JOIN: { - if (Recipient.self().getGroupsV2Capability() != Recipient.Capability.SUPPORTED) { - return ExtendedGroupJoinStatus.UPDATE_LINKED_DEVICE_TO_JOIN; - } - - return ExtendedGroupJoinStatus.LOCAL_CAN_JOIN; - } - default: throw new AssertionError(); + if (Recipient.self().getGroupsV2Capability() != Recipient.Capability.SUPPORTED) { + return ExtendedGroupJoinStatus.UPDATE_LINKED_DEVICE_TO_JOIN; + } else { + return ExtendedGroupJoinStatus.LOCAL_CAN_JOIN; } } @@ -215,12 +190,6 @@ public final class GroupJoinBottomSheetDialogFragment extends BottomSheetDialogF } public enum ExtendedGroupJoinStatus { - /** No version of the client that can join V2 groups by link is in production. */ - COMING_SOON, - - /** A newer version of the client is in production that will allow joining via GV2 group links. */ - UPDATE_TO_JOIN, - /** Locally we're using a version that can use group links, but one or more linked devices needs updating for GV2. */ UPDATE_LINKED_DEVICE_TO_JOIN, diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/invitesandrequests/joining/GroupJoinUpdateRequiredBottomSheetDialogFragment.java b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/invitesandrequests/joining/GroupJoinUpdateRequiredBottomSheetDialogFragment.java index 75884d1eaa..e1689c83c3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/invitesandrequests/joining/GroupJoinUpdateRequiredBottomSheetDialogFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/invitesandrequests/joining/GroupJoinUpdateRequiredBottomSheetDialogFragment.java @@ -16,7 +16,6 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.util.BottomSheetUtil; -import org.thoughtcrime.securesms.util.FeatureFlags; import org.thoughtcrime.securesms.util.PlayStoreUtil; import org.thoughtcrime.securesms.util.ThemeUtil; @@ -54,24 +53,13 @@ public final class GroupJoinUpdateRequiredBottomSheetDialogFragment extends Bott public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - switch (FeatureFlags.clientLocalGroupJoinStatus()) { - case COMING_SOON: - groupJoinTitle.setText(R.string.GroupJoinUpdateRequiredBottomSheetDialogFragment_group_links_coming_soon); - groupJoinExplain.setText(R.string.GroupJoinUpdateRequiredBottomSheetDialogFragment_coming_soon); - groupJoinButton.setText(android.R.string.ok); - groupJoinButton.setOnClickListener(v -> dismiss()); - break; - case UPDATE_TO_JOIN: - case LOCAL_CAN_JOIN: - groupJoinTitle.setText(R.string.GroupJoinUpdateRequiredBottomSheetDialogFragment_update_signal_to_use_group_links); - groupJoinExplain.setText(R.string.GroupJoinUpdateRequiredBottomSheetDialogFragment_update_message); - groupJoinButton.setText(R.string.GroupJoinUpdateRequiredBottomSheetDialogFragment_update_signal); - groupJoinButton.setOnClickListener(v -> { - PlayStoreUtil.openPlayStoreOrOurApkDownloadPage(requireContext()); - dismiss(); - }); - break; - } + groupJoinTitle.setText(R.string.GroupJoinUpdateRequiredBottomSheetDialogFragment_update_signal_to_use_group_links); + groupJoinExplain.setText(R.string.GroupJoinUpdateRequiredBottomSheetDialogFragment_update_message); + groupJoinButton.setText(R.string.GroupJoinUpdateRequiredBottomSheetDialogFragment_update_signal); + groupJoinButton.setOnClickListener(v -> { + PlayStoreUtil.openPlayStoreOrOurApkDownloadPage(requireContext()); + dismiss(); + }); } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/managegroup/ManageGroupFragment.java b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/managegroup/ManageGroupFragment.java index 62d4c19b03..8f6bcdfa4c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/managegroup/ManageGroupFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/managegroup/ManageGroupFragment.java @@ -46,7 +46,6 @@ import org.thoughtcrime.securesms.groups.ui.managegroup.dialogs.GroupInviteSentD import org.thoughtcrime.securesms.groups.ui.managegroup.dialogs.GroupRightsDialog; import org.thoughtcrime.securesms.groups.ui.managegroup.dialogs.GroupsLearnMoreBottomSheetDialogFragment; import org.thoughtcrime.securesms.groups.ui.migration.GroupsV1MigrationInitiationBottomSheetDialogFragment; -import org.thoughtcrime.securesms.groups.ui.pendingmemberinvites.PendingMemberInvitesActivity; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.mediaoverview.MediaOverviewActivity; import org.thoughtcrime.securesms.mms.GlideApp; @@ -79,8 +78,6 @@ public class ManageGroupFragment extends LoggingFragment { private ManageGroupViewModel viewModel; private GroupMemberListView groupMemberList; - private View pendingMembersRow; - private TextView pendingMembersCount; private View pendingAndRequestingRow; private TextView pendingAndRequestingCount; private Toolbar toolbar; @@ -92,7 +89,6 @@ public class ManageGroupFragment extends LoggingFragment { private ThreadPhotoRailView threadPhotoRailView; private View groupMediaCard; private View accessControlCard; - private View pendingMembersCard; private View groupLinkCard; private ManageGroupViewModel.CursorFactory cursorFactory; private View sharedMediaRow; @@ -150,14 +146,11 @@ public class ManageGroupFragment extends LoggingFragment { memberCountUnderAvatar = view.findViewById(R.id.member_count); memberCountAboveList = view.findViewById(R.id.member_count_2); groupMemberList = view.findViewById(R.id.group_members); - pendingMembersRow = view.findViewById(R.id.pending_members_row); - pendingMembersCount = view.findViewById(R.id.pending_members_count); pendingAndRequestingRow = view.findViewById(R.id.pending_and_requesting_members_row); pendingAndRequestingCount = view.findViewById(R.id.pending_and_requesting_members_count); threadPhotoRailView = view.findViewById(R.id.recent_photos); groupMediaCard = view.findViewById(R.id.group_media_card); accessControlCard = view.findViewById(R.id.group_access_control_card); - pendingMembersCard = view.findViewById(R.id.group_pending_card); groupLinkCard = view.findViewById(R.id.group_link_card); sharedMediaRow = view.findViewById(R.id.shared_media_row); editGroupAccessRow = view.findViewById(R.id.edit_group_access_row); @@ -210,34 +203,18 @@ public class ManageGroupFragment extends LoggingFragment { } }); - if (FeatureFlags.groupsV2manageGroupLinks()) { - viewModel.getPendingAndRequestingCount().observe(getViewLifecycleOwner(), - pendingAndRequestingCount -> { - pendingAndRequestingRow.setOnClickListener(v -> { - FragmentActivity activity = requireActivity(); - activity.startActivity(ManagePendingAndRequestingMembersActivity.newIntent(activity, groupId.requireV2())); - }); - if (pendingAndRequestingCount == 0) { - this.pendingAndRequestingCount.setVisibility(View.GONE); - } else { - this.pendingAndRequestingCount.setText(String.format(Locale.getDefault(), "%d", pendingAndRequestingCount)); - this.pendingAndRequestingCount.setVisibility(View.VISIBLE); - } - }); - } else { - viewModel.getPendingMemberCount().observe(getViewLifecycleOwner(), - pendingInviteCount -> { - pendingMembersRow.setOnClickListener(v -> { - FragmentActivity activity = requireActivity(); - activity.startActivity(PendingMemberInvitesActivity.newIntent(activity, groupId.requireV2())); - }); - if (pendingInviteCount == 0) { - pendingMembersCount.setText(R.string.ManageGroupActivity_none); - } else { - pendingMembersCount.setText(getResources().getQuantityString(R.plurals.ManageGroupActivity_invited, pendingInviteCount, pendingInviteCount)); - } - }); - } + viewModel.getPendingAndRequestingCount().observe(getViewLifecycleOwner(), pendingAndRequestingCount -> { + pendingAndRequestingRow.setOnClickListener(v -> { + FragmentActivity activity = requireActivity(); + activity.startActivity(ManagePendingAndRequestingMembersActivity.newIntent(activity, groupId.requireV2())); + }); + if (pendingAndRequestingCount == 0) { + this.pendingAndRequestingCount.setVisibility(View.GONE); + } else { + this.pendingAndRequestingCount.setText(String.format(Locale.getDefault(), "%d", pendingAndRequestingCount)); + this.pendingAndRequestingCount.setVisibility(View.VISIBLE); + } + }); avatar.setFallbackPhotoProvider(fallbackPhotoProvider); @@ -283,8 +260,7 @@ public class ManageGroupFragment extends LoggingFragment { ViewCompat.getLayoutDirection(threadPhotoRailView) == ViewCompat.LAYOUT_DIRECTION_LTR), RETURN_FROM_MEDIA)); - pendingMembersCard.setVisibility(!FeatureFlags.groupsV2manageGroupLinks() && vs.getGroupRecipient().requireGroupId().isV2() ? View.VISIBLE : View.GONE); - groupLinkCard .setVisibility( FeatureFlags.groupsV2manageGroupLinks() && vs.getGroupRecipient().requireGroupId().isV2() ? View.VISIBLE : View.GONE); + groupLinkCard.setVisibility(vs.getGroupRecipient().requireGroupId().isV2() ? View.VISIBLE : View.GONE); }); leaveGroup.setVisibility(groupId.isPush() ? View.VISIBLE : View.GONE); diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/pendingmemberinvites/PendingMemberInvitesActivity.java b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/pendingmemberinvites/PendingMemberInvitesActivity.java deleted file mode 100644 index 78d194c9ed..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/pendingmemberinvites/PendingMemberInvitesActivity.java +++ /dev/null @@ -1,72 +0,0 @@ -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.PassphraseRequiredActivity; -import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.groups.GroupId; -import org.thoughtcrime.securesms.groups.ui.invitesandrequests.ManagePendingAndRequestingMembersActivity; -import org.thoughtcrime.securesms.groups.ui.invitesandrequests.invited.PendingMemberInvitesFragment; -import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme; -import org.thoughtcrime.securesms.util.DynamicTheme; -import org.thoughtcrime.securesms.util.FeatureFlags; - -/** - * @deprecated With group links FF, this activity is replaced with {@link ManagePendingAndRequestingMembersActivity}. - */ -@Deprecated -public class PendingMemberInvitesActivity extends PassphraseRequiredActivity { - - 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); - - if (FeatureFlags.groupsV2manageGroupLinks()) { - throw new AssertionError(); - } - - setContentView(R.layout.group_pending_member_invites_activity); - - if (savedInstanceState == null) { - getSupportFragmentManager().beginTransaction() - .replace(R.id.container, PendingMemberInvitesFragment.newInstance(GroupId.parseOrThrow(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; - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java b/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java index 9135be59d7..8c1ab48ac2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java @@ -50,8 +50,6 @@ public final class FeatureFlags { private static final long FETCH_INTERVAL = TimeUnit.HOURS.toMillis(2); private static final String USERNAMES = "android.usernames"; - private static final String GROUPS_V2_JOIN_VERSION = "android.groupsv2.joinVersion"; - private static final String GROUPS_V2_LINKS_VERSION = "android.groupsv2.manageGroupLinksVersion"; private static final String GROUPS_V2_RECOMMENDED_LIMIT = "global.groupsv2.maxGroupSize"; private static final String GROUPS_V2_HARD_LIMIT = "global.groupsv2.groupSizeHardLimit"; private static final String INTERNAL_USER = "android.internalUser"; @@ -73,8 +71,6 @@ public final class FeatureFlags { private static final Set REMOTE_CAPABLE = SetUtil.newHashSet( GROUPS_V2_RECOMMENDED_LIMIT, GROUPS_V2_HARD_LIMIT, - GROUPS_V2_JOIN_VERSION, - GROUPS_V2_LINKS_VERSION, INTERNAL_USER, USERNAMES, VERIFY_V2, @@ -106,7 +102,6 @@ public final class FeatureFlags { * more burden on the reader to ensure that the app experience remains consistent. */ private static final Set HOT_SWAPPABLE = SetUtil.newHashSet( - GROUPS_V2_JOIN_VERSION, VERIFY_V2, CLIENT_EXPIRATION ); @@ -183,11 +178,6 @@ public final class FeatureFlags { return getBoolean(USERNAMES, false); } - /** Allow creation and managing of group links. */ - public static boolean groupsV2manageGroupLinks() { - return getVersionFlag(GROUPS_V2_LINKS_VERSION) == VersionFlag.ON; - } - /** * Maximum number of members allowed in a group. */ @@ -196,31 +186,6 @@ public final class FeatureFlags { getInteger(GROUPS_V2_HARD_LIMIT, 1001)); } - /** - * Ability of local client to join a GV2 group. - *

- * You must still check GV2 capabilities to respect linked devices. - */ - public static GroupJoinStatus clientLocalGroupJoinStatus() { - switch (getVersionFlag(GROUPS_V2_JOIN_VERSION)) { - case ON_IN_FUTURE_VERSION: return GroupJoinStatus.UPDATE_TO_JOIN; - case ON : return GroupJoinStatus.LOCAL_CAN_JOIN; - case OFF : - default : return GroupJoinStatus.COMING_SOON; - } - } - - public enum GroupJoinStatus { - /** No version of the client that can join V2 groups by link is in production. */ - COMING_SOON, - - /** A newer version of the client is in production that will allow joining via GV2 group links. */ - UPDATE_TO_JOIN, - - /** This version of the client allows joining via GV2 group links. */ - LOCAL_CAN_JOIN - } - /** Internal testing extensions. */ public static boolean internalUser() { return getBoolean(INTERNAL_USER, false); diff --git a/app/src/main/res/layout/group_manage_fragment.xml b/app/src/main/res/layout/group_manage_fragment.xml index 033a69a61c..ee9e8671e6 100644 --- a/app/src/main/res/layout/group_manage_fragment.xml +++ b/app/src/main/res/layout/group_manage_fragment.xml @@ -640,58 +640,13 @@ - - - - - - - - - - - - + app:layout_constraintTop_toBottomOf="@id/group_membership_card"> diff --git a/app/src/main/res/layout/group_pending_member_invites_activity.xml b/app/src/main/res/layout/group_pending_member_invites_activity.xml deleted file mode 100644 index 4581408e95..0000000000 --- a/app/src/main/res/layout/group_pending_member_invites_activity.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 87fea55bb5..2bf2501a26 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -653,7 +653,7 @@ - Pending group invites + Pending group invites Requests Invites People you invited @@ -728,7 +728,6 @@ Disappearing messages - Pending group invites Member requests & invites Add members Edit group info @@ -875,8 +874,7 @@ Group links coming soon Update Signal to use group links - Joining a group via a link is not yet supported by Signal. This feature will be released in an upcoming update. - The version of Signal you’re using does not support group links. Update to the latest version to join this group via link. + The version of Signal you’re using does not support this group link. Update to the latest version to join this group via link. Update Signal One or more of your linked devices are running a version of Signal that doesn\'t support group links. Update Signal on your linked device(s) to join this group. Group link is not valid