mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-21 09:20:19 +01:00
Add Research Megaphone.
This commit is contained in:
committed by
Greyson Parrelli
parent
9dbb77c10a
commit
ca442970a3
@@ -14,11 +14,11 @@ import org.thoughtcrime.securesms.R;
|
||||
|
||||
public class BasicMegaphoneView extends FrameLayout {
|
||||
|
||||
private ImageView image;
|
||||
private TextView titleText;
|
||||
private TextView bodyText;
|
||||
private Button actionButton;
|
||||
private Button snoozeButton;
|
||||
private ImageView image;
|
||||
private TextView titleText;
|
||||
private TextView bodyText;
|
||||
private Button actionButton;
|
||||
private Button secondaryButton;
|
||||
|
||||
private Megaphone megaphone;
|
||||
private MegaphoneActionController megaphoneListener;
|
||||
@@ -36,11 +36,11 @@ public class BasicMegaphoneView extends FrameLayout {
|
||||
private void init(@NonNull Context context) {
|
||||
inflate(context, R.layout.basic_megaphone_view, this);
|
||||
|
||||
this.image = findViewById(R.id.basic_megaphone_image);
|
||||
this.titleText = findViewById(R.id.basic_megaphone_title);
|
||||
this.bodyText = findViewById(R.id.basic_megaphone_body);
|
||||
this.actionButton = findViewById(R.id.basic_megaphone_action);
|
||||
this.snoozeButton = findViewById(R.id.basic_megaphone_snooze);
|
||||
this.image = findViewById(R.id.basic_megaphone_image);
|
||||
this.titleText = findViewById(R.id.basic_megaphone_title);
|
||||
this.bodyText = findViewById(R.id.basic_megaphone_body);
|
||||
this.actionButton = findViewById(R.id.basic_megaphone_action);
|
||||
this.secondaryButton = findViewById(R.id.basic_megaphone_secondary);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -89,17 +89,27 @@ public class BasicMegaphoneView extends FrameLayout {
|
||||
actionButton.setVisibility(GONE);
|
||||
}
|
||||
|
||||
if (megaphone.canSnooze()) {
|
||||
snoozeButton.setVisibility(VISIBLE);
|
||||
snoozeButton.setOnClickListener(v -> {
|
||||
megaphoneListener.onMegaphoneSnooze(megaphone.getEvent());
|
||||
if (megaphone.canSnooze() || megaphone.hasSecondaryButton()) {
|
||||
secondaryButton.setVisibility(VISIBLE);
|
||||
|
||||
if (megaphone.getSnoozeListener() != null) {
|
||||
megaphone.getSnoozeListener().onEvent(megaphone, megaphoneListener);
|
||||
}
|
||||
});
|
||||
if (megaphone.canSnooze()) {
|
||||
secondaryButton.setOnClickListener(v -> {
|
||||
megaphoneListener.onMegaphoneSnooze(megaphone.getEvent());
|
||||
|
||||
if (megaphone.getSnoozeListener() != null) {
|
||||
megaphone.getSnoozeListener().onEvent(megaphone, megaphoneListener);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
secondaryButton.setText(megaphone.getSecondaryButtonText());
|
||||
secondaryButton.setOnClickListener(v -> {
|
||||
if (megaphone.getSecondaryButtonClickListener() != null) {
|
||||
megaphone.getSecondaryButtonClickListener().onEvent(megaphone, megaphoneListener);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
snoozeButton.setVisibility(GONE);
|
||||
secondaryButton.setVisibility(GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,20 +28,24 @@ public class Megaphone {
|
||||
private final int buttonTextRes;
|
||||
private final EventListener buttonListener;
|
||||
private final EventListener snoozeListener;
|
||||
private final int secondaryButtonTextRes;
|
||||
private final EventListener secondaryButtonListener;
|
||||
private final EventListener onVisibleListener;
|
||||
|
||||
private Megaphone(@NonNull Builder builder) {
|
||||
this.event = builder.event;
|
||||
this.style = builder.style;
|
||||
this.priority = builder.priority;
|
||||
this.canSnooze = builder.canSnooze;
|
||||
this.titleRes = builder.titleRes;
|
||||
this.bodyRes = builder.bodyRes;
|
||||
this.imageRequest = builder.imageRequest;
|
||||
this.buttonTextRes = builder.buttonTextRes;
|
||||
this.buttonListener = builder.buttonListener;
|
||||
this.snoozeListener = builder.snoozeListener;
|
||||
this.onVisibleListener = builder.onVisibleListener;
|
||||
this.event = builder.event;
|
||||
this.style = builder.style;
|
||||
this.priority = builder.priority;
|
||||
this.canSnooze = builder.canSnooze;
|
||||
this.titleRes = builder.titleRes;
|
||||
this.bodyRes = builder.bodyRes;
|
||||
this.imageRequest = builder.imageRequest;
|
||||
this.buttonTextRes = builder.buttonTextRes;
|
||||
this.buttonListener = builder.buttonListener;
|
||||
this.snoozeListener = builder.snoozeListener;
|
||||
this.secondaryButtonTextRes = builder.secondaryButtonTextRes;
|
||||
this.secondaryButtonListener = builder.secondaryButtonListener;
|
||||
this.onVisibleListener = builder.onVisibleListener;
|
||||
}
|
||||
|
||||
public @NonNull Event getEvent() {
|
||||
@@ -88,6 +92,18 @@ public class Megaphone {
|
||||
return snoozeListener;
|
||||
}
|
||||
|
||||
public @StringRes int getSecondaryButtonText() {
|
||||
return secondaryButtonTextRes;
|
||||
}
|
||||
|
||||
public boolean hasSecondaryButton() {
|
||||
return secondaryButtonTextRes != 0;
|
||||
}
|
||||
|
||||
public @Nullable EventListener getSecondaryButtonClickListener() {
|
||||
return secondaryButtonListener;
|
||||
}
|
||||
|
||||
public @Nullable EventListener getOnVisibleListener() {
|
||||
return onVisibleListener;
|
||||
}
|
||||
@@ -105,6 +121,8 @@ public class Megaphone {
|
||||
private int buttonTextRes;
|
||||
private EventListener buttonListener;
|
||||
private EventListener snoozeListener;
|
||||
private int secondaryButtonTextRes;
|
||||
private EventListener secondaryButtonListener;
|
||||
private EventListener onVisibleListener;
|
||||
|
||||
|
||||
@@ -159,6 +177,12 @@ public class Megaphone {
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull Builder setSecondaryButton(@StringRes int secondaryButtonTextRes, @NonNull EventListener listener) {
|
||||
this.secondaryButtonTextRes = secondaryButtonTextRes;
|
||||
this.secondaryButtonListener = listener;
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull Builder setOnVisibleListener(@Nullable EventListener listener) {
|
||||
this.onVisibleListener = listener;
|
||||
return this;
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.content.Intent;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
|
||||
public interface MegaphoneActionController {
|
||||
/**
|
||||
@@ -36,4 +37,9 @@ public interface MegaphoneActionController {
|
||||
* Called when a megaphone completed its goal.
|
||||
*/
|
||||
void onMegaphoneCompleted(@NonNull Megaphones.Event event);
|
||||
|
||||
/**
|
||||
* When a megaphone wnats to show a dialog fragment.
|
||||
*/
|
||||
void onMegaphoneDialogFragmentRequested(@NonNull DialogFragment dialogFragment);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.megaphone;
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.AnyThread;
|
||||
import androidx.annotation.MainThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
@@ -53,7 +52,7 @@ public class MegaphoneRepository {
|
||||
executor.execute(() -> {
|
||||
database.markFinished(Event.REACTIONS);
|
||||
database.markFinished(Event.MESSAGE_REQUESTS);
|
||||
database.markFinished(Event.MENTIONS);
|
||||
database.markFinished(Event.RESEARCH);
|
||||
resetDatabaseCache();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import org.thoughtcrime.securesms.messagerequests.MessageRequestMegaphoneActivit
|
||||
import org.thoughtcrime.securesms.profiles.ProfileName;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.ResearchMegaphone;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
@@ -85,9 +86,9 @@ public final class Megaphones {
|
||||
put(Event.PINS_FOR_ALL, new PinsForAllSchedule());
|
||||
put(Event.PIN_REMINDER, new SignalPinReminderSchedule());
|
||||
put(Event.MESSAGE_REQUESTS, shouldShowMessageRequestsMegaphone() ? ALWAYS : NEVER);
|
||||
put(Event.MENTIONS, shouldShowMentionsMegaphone() ? ALWAYS : NEVER);
|
||||
put(Event.LINK_PREVIEWS, shouldShowLinkPreviewsMegaphone(context) ? ALWAYS : NEVER);
|
||||
put(Event.CLIENT_DEPRECATED, SignalStore.misc().isClientDeprecated() ? ALWAYS : NEVER);
|
||||
put(Event.RESEARCH, shouldShowResearchMegaphone() ? ShowForDurationSchedule.showForDays(7) : NEVER);
|
||||
}};
|
||||
}
|
||||
|
||||
@@ -101,12 +102,12 @@ public final class Megaphones {
|
||||
return buildPinReminderMegaphone(context);
|
||||
case MESSAGE_REQUESTS:
|
||||
return buildMessageRequestsMegaphone(context);
|
||||
case MENTIONS:
|
||||
return buildMentionsMegaphone();
|
||||
case LINK_PREVIEWS:
|
||||
return buildLinkPreviewsMegaphone();
|
||||
case CLIENT_DEPRECATED:
|
||||
return buildClientDeprecatedMegaphone(context);
|
||||
case RESEARCH:
|
||||
return buildResearchMegaphone(context);
|
||||
default:
|
||||
throw new IllegalArgumentException("Event not handled!");
|
||||
}
|
||||
@@ -189,14 +190,6 @@ public final class Megaphones {
|
||||
.build();
|
||||
}
|
||||
|
||||
private static Megaphone buildMentionsMegaphone() {
|
||||
return new Megaphone.Builder(Event.MENTIONS, Megaphone.Style.POPUP)
|
||||
.setTitle(R.string.MentionsMegaphone__introducing_mentions)
|
||||
.setBody(R.string.MentionsMegaphone__get_someones_attention_in_a_group_by_typing)
|
||||
.setImage(R.drawable.mention_megaphone)
|
||||
.build();
|
||||
}
|
||||
|
||||
private static @NonNull Megaphone buildLinkPreviewsMegaphone() {
|
||||
return new Megaphone.Builder(Event.LINK_PREVIEWS, Megaphone.Style.LINK_PREVIEWS)
|
||||
.setPriority(Megaphone.Priority.HIGH)
|
||||
@@ -207,9 +200,22 @@ public final class Megaphones {
|
||||
return new Megaphone.Builder(Event.CLIENT_DEPRECATED, Megaphone.Style.FULLSCREEN)
|
||||
.disableSnooze()
|
||||
.setPriority(Megaphone.Priority.HIGH)
|
||||
.setOnVisibleListener((megaphone, listener) -> {
|
||||
listener.onMegaphoneNavigationRequested(new Intent(context, ClientDeprecatedActivity.class));
|
||||
.setOnVisibleListener((megaphone, listener) -> listener.onMegaphoneNavigationRequested(new Intent(context, ClientDeprecatedActivity.class)))
|
||||
.build();
|
||||
}
|
||||
|
||||
private static @NonNull Megaphone buildResearchMegaphone(@NonNull Context context) {
|
||||
return new Megaphone.Builder(Event.RESEARCH, Megaphone.Style.BASIC)
|
||||
.disableSnooze()
|
||||
.setTitle(R.string.ResearchMegaphone_tell_signal_what_you_think)
|
||||
.setBody(R.string.ResearchMegaphone_to_make_signal_the_best_messaging_app_on_the_planet)
|
||||
.setImage(R.drawable.ic_research_megaphone)
|
||||
.setActionButton(R.string.ResearchMegaphone_learn_more, (megaphone, controller) -> {
|
||||
controller.onMegaphoneCompleted(megaphone.getEvent());
|
||||
controller.onMegaphoneDialogFragmentRequested(new ResearchMegaphoneDialog());
|
||||
})
|
||||
.setSecondaryButton(R.string.ResearchMegaphone_dismiss, (megaphone, controller) -> controller.onMegaphoneCompleted(megaphone.getEvent()))
|
||||
.setPriority(Megaphone.Priority.DEFAULT)
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -217,9 +223,8 @@ public final class Megaphones {
|
||||
return Recipient.self().getProfileName() == ProfileName.EMPTY;
|
||||
}
|
||||
|
||||
private static boolean shouldShowMentionsMegaphone() {
|
||||
return false;
|
||||
// return FeatureFlags.mentions();
|
||||
private static boolean shouldShowResearchMegaphone() {
|
||||
return ResearchMegaphone.isInResearchMegaphone();
|
||||
}
|
||||
|
||||
private static boolean shouldShowLinkPreviewsMegaphone(@NonNull Context context) {
|
||||
@@ -231,9 +236,9 @@ public final class Megaphones {
|
||||
PINS_FOR_ALL("pins_for_all"),
|
||||
PIN_REMINDER("pin_reminder"),
|
||||
MESSAGE_REQUESTS("message_requests"),
|
||||
MENTIONS("mentions"),
|
||||
LINK_PREVIEWS("link_previews"),
|
||||
CLIENT_DEPRECATED("client_deprecated");
|
||||
CLIENT_DEPRECATED("client_deprecated"),
|
||||
RESEARCH("research");
|
||||
|
||||
private final String key;
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package org.thoughtcrime.securesms.megaphone;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.text.Html;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.FullScreenDialogFragment;
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions;
|
||||
|
||||
public class ResearchMegaphoneDialog extends FullScreenDialogFragment {
|
||||
|
||||
private static final String SURVEY_URL = "https://surveys.signalusers.org/s3";
|
||||
|
||||
@Override
|
||||
public @NonNull View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = super.onCreateView(inflater, container, savedInstanceState);
|
||||
|
||||
TextView content = view.findViewById(R.id.research_megaphone_content);
|
||||
content.setText(Html.fromHtml(requireContext().getString(R.string.ResearchMegaphoneDialog_we_believe_in_privacy)));
|
||||
|
||||
view.findViewById(R.id.research_megaphone_dialog_take_the_survey)
|
||||
.setOnClickListener(v -> CommunicationActions.openBrowserLink(requireContext(), SURVEY_URL));
|
||||
|
||||
view.findViewById(R.id.research_megaphone_dialog_no_thanks)
|
||||
.setOnClickListener(v -> dismissAllowingStateLoss());
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @StringRes int getTitle() {
|
||||
return R.string.ResearchMegaphoneDialog_signal_research;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getDialogLayoutResource() {
|
||||
return R.layout.research_megaphone_dialog;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.thoughtcrime.securesms.megaphone;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Megaphone schedule that will always show for some duration after the first
|
||||
* time the user sees it.
|
||||
*/
|
||||
public class ShowForDurationSchedule implements MegaphoneSchedule {
|
||||
|
||||
private final long duration;
|
||||
|
||||
public static MegaphoneSchedule showForDays(int days) {
|
||||
return new ShowForDurationSchedule(TimeUnit.DAYS.toMillis(days));
|
||||
}
|
||||
|
||||
public ShowForDurationSchedule(long duration) {
|
||||
this.duration = duration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldDisplay(int seenCount, long lastSeen, long firstVisible, long currentTime) {
|
||||
return firstVisible == 0 || currentTime < firstVisible + duration;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user