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 3fca08669a..a97ef7323d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -374,6 +374,7 @@ public class ConversationActivity extends PassphraseRequiredActivity private View cancelJoinRequest; private Stub mentionsSuggestions; private MaterialButton joinGroupCallButton; + private boolean callingTooltipShown; private LinkPreviewViewModel linkPreviewViewModel; private ConversationSearchViewModel searchViewModel; @@ -830,6 +831,7 @@ public class ConversationActivity extends PassphraseRequiredActivity if (groupCallViewModel != null && Boolean.TRUE.equals(groupCallViewModel.hasActiveGroupCall().getValue())) { hideMenuItem(menu, R.id.menu_video_secure); } + showGroupCallingTooltip(); } inflater.inflate(R.menu.conversation_group_options, menu); @@ -2137,6 +2139,28 @@ public class ConversationActivity extends PassphraseRequiredActivity groupCallViewModel.groupCallHasCapacity().observe(this, hasCapacity -> joinGroupCallButton.setText(hasCapacity ? R.string.ConversationActivity_join : R.string.ConversationActivity_full)); } + private void showGroupCallingTooltip() { + if (!FeatureFlags.groupCalling() || !SignalStore.tooltips().shouldShowGroupCallingTooltip() || callingTooltipShown) { + return; + } + + View anchor = findViewById(R.id.menu_video_secure); + if (anchor == null) { + Log.w(TAG, "Video Call tooltip anchor is null. Skipping tooltip..."); + return; + } + + callingTooltipShown = true; + + SignalStore.tooltips().markGroupCallSpeakerViewSeen(); + TooltipPopup.forTarget(anchor) + .setBackgroundTint(ContextCompat.getColor(this, R.color.signal_accent_green)) + .setTextColor(getResources().getColor(R.color.core_white)) + .setText(R.string.ConversationActivity__tap_here_to_start_a_group_call) + .setOnDismissListener(() -> SignalStore.tooltips().markGroupCallingTooltipSeen()) + .show(TooltipPopup.POSITION_BELOW); + } + private void showStickerIntroductionTooltip() { TextSecurePreferences.setMediaKeyboardMode(this, MediaKeyboardMode.STICKER); inputPanel.setMediaKeyboardToggleMode(true); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java index 47be5f279a..efc6652659 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java @@ -103,6 +103,7 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.events.ReminderUpdateEvent; import org.thoughtcrime.securesms.insights.InsightsLauncher; import org.thoughtcrime.securesms.jobs.ServiceOutageDetectionJob; +import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity; import org.thoughtcrime.securesms.mediasend.MediaSendActivity; import org.thoughtcrime.securesms.megaphone.Megaphone; diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/MiscellaneousValues.java b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/MiscellaneousValues.java index b64475d4a2..c0bb184ff3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/MiscellaneousValues.java +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/MiscellaneousValues.java @@ -4,12 +4,12 @@ import androidx.annotation.NonNull; public final class MiscellaneousValues extends SignalStoreValues { - private static final String LAST_PREKEY_REFRESH_TIME = "last_prekey_refresh_time"; - private static final String MESSAGE_REQUEST_ENABLE_TIME = "message_request_enable_time"; - private static final String LAST_PROFILE_REFRESH_TIME = "misc.last_profile_refresh_time"; - private static final String LAST_GV1_ROUTINE_MIGRATION_TIME = "misc.last_gv1_routine_migration_time"; - private static final String USERNAME_SHOW_REMINDER = "username.show.reminder"; - private static final String CLIENT_DEPRECATED = "misc.client_deprecated"; + private static final String LAST_PREKEY_REFRESH_TIME = "last_prekey_refresh_time"; + private static final String MESSAGE_REQUEST_ENABLE_TIME = "message_request_enable_time"; + private static final String LAST_PROFILE_REFRESH_TIME = "misc.last_profile_refresh_time"; + private static final String LAST_GV1_ROUTINE_MIGRATION_TIME = "misc.last_gv1_routine_migration_time"; + private static final String USERNAME_SHOW_REMINDER = "username.show.reminder"; + private static final String CLIENT_DEPRECATED = "misc.client_deprecated"; MiscellaneousValues(@NonNull KeyValueStore store) { super(store); diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/TooltipValues.java b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/TooltipValues.java index 6c0b578a09..33a2fc6b4c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/TooltipValues.java +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/TooltipValues.java @@ -4,8 +4,12 @@ import androidx.annotation.NonNull; public class TooltipValues extends SignalStoreValues { - private static final String BLUR_HUD_ICON = "tooltip.blur_hud_icon"; - private static final String GROUP_CALL_SPEAKER_VIEW = "tooltip.group_call_speaker_view"; + private static final int GROUP_CALLING_MAX_TOOLTIP_DISPLAY_COUNT = 3; + + private static final String BLUR_HUD_ICON = "tooltip.blur_hud_icon"; + private static final String GROUP_CALL_SPEAKER_VIEW = "tooltip.group_call_speaker_view"; + private static final String GROUP_CALL_TOOLTIP_DISPLAY_COUNT = "tooltip.group_call_tooltip_display_count"; + TooltipValues(@NonNull KeyValueStore store) { super(store); @@ -30,4 +34,16 @@ public class TooltipValues extends SignalStoreValues { public void markGroupCallSpeakerViewSeen() { putBoolean(GROUP_CALL_SPEAKER_VIEW, true); } + + public boolean shouldShowGroupCallingTooltip() { + return getInteger(GROUP_CALL_TOOLTIP_DISPLAY_COUNT, 0) < GROUP_CALLING_MAX_TOOLTIP_DISPLAY_COUNT; + } + + public void markGroupCallingTooltipSeen() { + putInteger(GROUP_CALL_TOOLTIP_DISPLAY_COUNT, getInteger(GROUP_CALL_TOOLTIP_DISPLAY_COUNT, 0) + 1); + } + + public void markGroupCallingLobbyEntered() { + putInteger(GROUP_CALL_TOOLTIP_DISPLAY_COUNT, Integer.MAX_VALUE); + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/megaphone/MegaphoneRepository.java b/app/src/main/java/org/thoughtcrime/securesms/megaphone/MegaphoneRepository.java index fbf124ddfe..67c905597a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/megaphone/MegaphoneRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/megaphone/MegaphoneRepository.java @@ -54,6 +54,7 @@ public class MegaphoneRepository { database.markFinished(Event.MESSAGE_REQUESTS); database.markFinished(Event.LINK_PREVIEWS); database.markFinished(Event.RESEARCH); + database.markFinished(Event.GROUP_CALLING); resetDatabaseCache(); }); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/megaphone/Megaphones.java b/app/src/main/java/org/thoughtcrime/securesms/megaphone/Megaphones.java index 54a7a2e85d..cdb48bf941 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/megaphone/Megaphones.java +++ b/app/src/main/java/org/thoughtcrime/securesms/megaphone/Megaphones.java @@ -92,6 +92,7 @@ public final class Megaphones { put(Event.CLIENT_DEPRECATED, SignalStore.misc().isClientDeprecated() ? ALWAYS : NEVER); put(Event.RESEARCH, shouldShowResearchMegaphone(context) ? ShowForDurationSchedule.showForDays(7) : NEVER); put(Event.DONATE, shouldShowDonateMegaphone(context) ? ShowForDurationSchedule.showForDays(7) : NEVER); + put(Event.GROUP_CALLING, shouldShowGroupCallingMegaphone() ? ALWAYS : NEVER); }}; } @@ -113,6 +114,8 @@ public final class Megaphones { return buildResearchMegaphone(context); case DONATE: return buildDonateMegaphone(context); + case GROUP_CALLING: + return buildGroupCallingMegaphone(context); default: throw new IllegalArgumentException("Event not handled!"); } @@ -239,6 +242,19 @@ public final class Megaphones { .build(); } + private static @NonNull Megaphone buildGroupCallingMegaphone(@NonNull Context context) { + return new Megaphone.Builder(Event.GROUP_CALLING, Megaphone.Style.BASIC) + .disableSnooze() + .setTitle(R.string.GroupCallingMegaphone__introducing_group_calls) + .setBody(R.string.GroupCallingMegaphone__open_a_new_group_to_start) + .setImage(R.drawable.ic_group_calls_megaphone) + .setActionButton(android.R.string.ok, (megaphone, controller) -> { + controller.onMegaphoneCompleted(megaphone.getEvent()); + }) + .setPriority(Megaphone.Priority.DEFAULT) + .build(); + } + private static boolean shouldShowMessageRequestsMegaphone() { return Recipient.self().getProfileName() == ProfileName.EMPTY; } @@ -255,6 +271,10 @@ public final class Megaphones { return TextSecurePreferences.wereLinkPreviewsEnabled(context) && !SignalStore.settings().isLinkPreviewsEnabled(); } + private static boolean shouldShowGroupCallingMegaphone() { + return FeatureFlags.groupCalling(); + } + public enum Event { REACTIONS("reactions"), PINS_FOR_ALL("pins_for_all"), @@ -263,7 +283,8 @@ public final class Megaphones { LINK_PREVIEWS("link_previews"), CLIENT_DEPRECATED("client_deprecated"), RESEARCH("research"), - DONATE("donate"); + DONATE("donate"), + GROUP_CALLING("group_calling"); private final String key; diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupPreJoinActionProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupPreJoinActionProcessor.java index c7025442d4..7bcde5a6b1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupPreJoinActionProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupPreJoinActionProcessor.java @@ -15,6 +15,7 @@ import org.thoughtcrime.securesms.components.webrtc.BroadcastVideoSink; import org.thoughtcrime.securesms.events.CallParticipant; import org.thoughtcrime.securesms.events.CallParticipantId; import org.thoughtcrime.securesms.events.WebRtcViewModel; +import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.ringrtc.RemotePeer; import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState; @@ -57,6 +58,7 @@ public class GroupPreJoinActionProcessor extends GroupActionProcessor { return groupCallFailure(currentState, "Unable to connect to group call", e); } + SignalStore.tooltips().markGroupCallingLobbyEntered(); return currentState.builder() .changeCallInfoState() .groupCall(groupCall) diff --git a/app/src/main/res/drawable-mdpi/ic_group_calls_megaphone.webp b/app/src/main/res/drawable-mdpi/ic_group_calls_megaphone.webp new file mode 100644 index 0000000000..5f185d35e8 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_group_calls_megaphone.webp differ diff --git a/app/src/main/res/drawable-xhdpi/ic_group_calls_megaphone.webp b/app/src/main/res/drawable-xhdpi/ic_group_calls_megaphone.webp new file mode 100644 index 0000000000..ec7c15bfb0 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_group_calls_megaphone.webp differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_group_calls_megaphone.webp b/app/src/main/res/drawable-xxhdpi/ic_group_calls_megaphone.webp new file mode 100644 index 0000000000..c48fae3ee4 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_group_calls_megaphone.webp differ diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 56bc1be2b3..2f01abc2c9 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -3,6 +3,8 @@ @color/core_white @color/core_black + #4CAF50 + #0D000000 #18000000 #26000000 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5786554bf8..4ca1e8c594 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -243,6 +243,7 @@ Recipient is not a valid SMS or email address! Message is empty! Group members + Tap here to start a group call Invalid recipient! Added to home screen @@ -484,6 +485,10 @@ Donate No Thanks + + Introducing Group Calls + Open a New Group to start a free encrypted group call + Optimize for missing Play Services This device does not support Play Services. Tap to disable system battery optimizations that prevent Signal from retrieving messages while inactive.