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 bef15ba0b..b8fe2fe4e 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 @@ -25,6 +25,7 @@ import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto; import org.thoughtcrime.securesms.groups.v2.GroupInviteLinkUrl; 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; @@ -91,15 +92,27 @@ public final class GroupJoinBottomSheetDialogFragment extends BottomSheetDialogF viewModel.getGroupDetails().observe(getViewLifecycleOwner(), details -> { groupName.setText(details.getGroupName()); groupDetails.setText(requireContext().getResources().getQuantityString(R.plurals.GroupJoinBottomSheetDialogFragment_group_dot_d_members, details.getGroupMembershipCount(), details.getGroupMembershipCount())); - groupJoinButton.setText(R.string.GroupJoinUpdateRequiredBottomSheetDialogFragment_update_signal); - groupJoinButton.setOnClickListener(v -> { - PlayStoreUtil.openPlayStoreOrOurApkDownloadPage(requireContext()); - dismiss(); - }); - groupJoinExplain.setText(R.string.GroupJoinUpdateRequiredBottomSheetDialogFragment_update_message); + + switch (FeatureFlags.clientLocalGroupJoinStatus()) { + 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: + case LOCAL_CAN_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; + } + avatar.setImageBytesForGroup(details.getAvatarBytes(), new FallbackPhotoProvider(), MaterialColor.STEEL); - groupJoinButton.setVisibility(View.VISIBLE); groupCancelButton.setVisibility(View.VISIBLE); }); 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 583a007f2..75884d1ea 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 @@ -4,6 +4,8 @@ import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -14,11 +16,16 @@ 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; public final class GroupJoinUpdateRequiredBottomSheetDialogFragment extends BottomSheetDialogFragment { + private TextView groupJoinTitle; + private TextView groupJoinExplain; + private Button groupJoinButton; + public static void show(@NonNull FragmentManager manager) { new GroupJoinUpdateRequiredBottomSheetDialogFragment().show(manager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG); } @@ -34,18 +41,37 @@ public final class GroupJoinUpdateRequiredBottomSheetDialogFragment extends Bott @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - return inflater.inflate(R.layout.group_join_update_needed_bottom_sheet, container, false); + View view = inflater.inflate(R.layout.group_join_update_needed_bottom_sheet, container, false); + + groupJoinTitle = view.findViewById(R.id.group_join_update_title); + groupJoinButton = view.findViewById(R.id.group_join_update_button); + groupJoinExplain = view.findViewById(R.id.group_join_update_explain); + + return view; } @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - view.findViewById(R.id.group_join_update_button) - .setOnClickListener(v -> { + 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; + } } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/CommunicationActions.java b/app/src/main/java/org/thoughtcrime/securesms/util/CommunicationActions.java index 1f9576d85..e8f91d21e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/CommunicationActions.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/CommunicationActions.java @@ -184,8 +184,12 @@ public class CommunicationActions { handleGroupLinkUrl(activity, groupInviteLinkUrl); return true; - } catch (GroupInviteLinkUrl.InvalidGroupLinkException | GroupInviteLinkUrl.UnknownGroupLinkVersionException e) { + } catch (GroupInviteLinkUrl.InvalidGroupLinkException e) { Log.w(TAG, "Could not parse group URL", e); + Toast.makeText(activity, R.string.GroupJoinUpdateRequiredBottomSheetDialogFragment_group_link_is_not_valid, Toast.LENGTH_SHORT).show(); + return true; + } catch (GroupInviteLinkUrl.UnknownGroupLinkVersionException e) { + Log.w(TAG, "Group link is for an advanced version", e); GroupJoinUpdateRequiredBottomSheetDialogFragment.show(activity.getSupportFragmentManager()); 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 4aaeb58e0..3fb563463 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java @@ -10,6 +10,7 @@ import com.google.android.collect.Sets; import org.json.JSONException; import org.json.JSONObject; +import org.thoughtcrime.securesms.BuildConfig; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.jobs.RefreshAttributesJob; import org.thoughtcrime.securesms.jobs.RefreshOwnProfileJob; @@ -55,6 +56,7 @@ public final class FeatureFlags { private static final String GROUPS_V2_OLD_2 = "android.groupsv2.2"; private static final String GROUPS_V2 = "android.groupsv2.3"; private static final String GROUPS_V2_CREATE = "android.groupsv2.create.3"; + private static final String GROUPS_V2_JOIN_VERSION = "android.groupsv2.joinVersion"; private static final String GROUPS_V2_CAPACITY = "global.groupsv2.maxGroupSize"; private static final String CDS = "android.cds.4"; private static final String INTERNAL_USER = "android.internalUser"; @@ -98,6 +100,7 @@ public final class FeatureFlags { private static final Set HOT_SWAPPABLE = Sets.newHashSet( ATTACHMENTS_V3, GROUPS_V2_CREATE, + GROUPS_V2_JOIN_VERSION, VERIFY_V2, CDS ); @@ -222,6 +225,30 @@ public final class FeatureFlags { return getInteger(GROUPS_V2_CAPACITY, 151); } + /** + * Ability of local client to join a GV2 group. + *

+ * You must still check GV2 capabilities to respect linked devices. + */ + public static GroupJoinStatus clientLocalGroupJoinStatus() { + int groupJoinVersion = getInteger(GROUPS_V2_JOIN_VERSION, 0); + + if (groupJoinVersion == 0) return GroupJoinStatus.COMING_SOON; + else if (groupJoinVersion > BuildConfig.CANONICAL_VERSION_CODE) return GroupJoinStatus.UPDATE_TO_JOIN; + else return GroupJoinStatus.LOCAL_CAN_JOIN; + } + + 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_join_bottom_sheet.xml b/app/src/main/res/layout/group_join_bottom_sheet.xml index e75482d3b..25d6bae2a 100644 --- a/app/src/main/res/layout/group_join_bottom_sheet.xml +++ b/app/src/main/res/layout/group_join_bottom_sheet.xml @@ -78,7 +78,7 @@ android:layout_marginEnd="16dp" android:layout_marginBottom="16dp" android:text="@android:string/cancel" - android:visibility="gone" + android:visibility="invisible" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/group_join_button" app:layout_constraintHorizontal_bias="0.5" diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 455cba4dd..9e5faae0c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -660,9 +660,12 @@ + 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 sharable group links. Update to the latest version to join this group via link. Update Signal + Group link is not valid Group avatar diff --git a/app/src/test/java/org/thoughtcrime/securesms/util/FeatureFlagsTest.java b/app/src/test/java/org/thoughtcrime/securesms/util/FeatureFlagsTest.java index 00ccf2c0a..13c1574b7 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/util/FeatureFlagsTest.java +++ b/app/src/test/java/org/thoughtcrime/securesms/util/FeatureFlagsTest.java @@ -64,6 +64,20 @@ public class FeatureFlagsTest extends BaseUnitTest { assertEquals(Change.ENABLED, result.getMemoryChanges().get(A)); } + @Test + public void updateInternal_newValue_hotSwap_integer() { + UpdateResult result = FeatureFlags.updateInternal(mapOf(A, 1), + mapOf(), + mapOf(), + setOf(A), + setOf(A), + setOf()); + + assertEquals(mapOf(A, 1), result.getMemory()); + assertEquals(mapOf(A, 1), result.getDisk()); + assertEquals(Change.CHANGED, result.getMemoryChanges().get(A)); + } + @Test public void updateInternal_newValue_sticky() { UpdateResult result = FeatureFlags.updateInternal(mapOf(A, true), @@ -106,6 +120,20 @@ public class FeatureFlagsTest extends BaseUnitTest { assertTrue(result.getMemoryChanges().isEmpty()); } + @Test + public void updateInternal_replaceValue_integer() { + UpdateResult result = FeatureFlags.updateInternal(mapOf(A, 2), + mapOf(A, 1), + mapOf(A, 1), + setOf(A), + setOf(), + setOf()); + + assertEquals(mapOf(A, 1), result.getMemory()); + assertEquals(mapOf(A, 2), result.getDisk()); + assertTrue(result.getMemoryChanges().isEmpty()); + } + @Test public void updateInternal_replaceValue_hotSwap() { UpdateResult result = FeatureFlags.updateInternal(mapOf(A, true), @@ -120,6 +148,20 @@ public class FeatureFlagsTest extends BaseUnitTest { assertEquals(Change.ENABLED, result.getMemoryChanges().get(A)); } + @Test + public void updateInternal_replaceValue_hotSwa_integer() { + UpdateResult result = FeatureFlags.updateInternal(mapOf(A, 2), + mapOf(A, 1), + mapOf(A, 1), + setOf(A), + setOf(A), + setOf()); + + assertEquals(mapOf(A, 2), result.getMemory()); + assertEquals(mapOf(A, 2), result.getDisk()); + assertEquals(Change.CHANGED, result.getMemoryChanges().get(A)); + } + @Test public void updateInternal_replaceValue_hotSwap_stickyChange() { UpdateResult result = FeatureFlags.updateInternal(mapOf(A, true),