Add a megaphone to celebrate Valentine's Day.
This commit is contained in:
parent
65af5f0849
commit
597cf3f576
5 changed files with 142 additions and 18 deletions
|
@ -7,6 +7,7 @@ import android.provider.Settings;
|
|||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
|
@ -15,7 +16,6 @@ import org.signal.core.util.logging.Log;
|
|||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.badges.models.Badge;
|
||||
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity;
|
||||
import org.thoughtcrime.securesms.conversationlist.ConversationListFragment;
|
||||
import org.thoughtcrime.securesms.database.model.MegaphoneRecord;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
|
@ -24,37 +24,38 @@ import org.thoughtcrime.securesms.lock.SignalPinReminderDialog;
|
|||
import org.thoughtcrime.securesms.lock.SignalPinReminders;
|
||||
import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity;
|
||||
import org.thoughtcrime.securesms.lock.v2.KbsMigrationActivity;
|
||||
import org.thoughtcrime.securesms.messagerequests.MessageRequestMegaphoneActivity;
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||
import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
||||
import org.thoughtcrime.securesms.profiles.ProfileName;
|
||||
import org.thoughtcrime.securesms.profiles.manage.ManageProfileActivity;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.LocaleFeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.PlayServicesUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.SetUtil;
|
||||
import org.thoughtcrime.securesms.util.VersionTracker;
|
||||
import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageContextWrapper;
|
||||
import org.thoughtcrime.securesms.wallpaper.ChatWallpaperActivity;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.Month;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Creating a new megaphone:
|
||||
* - Add an enum to {@link Event}
|
||||
* - Return a megaphone in {@link #forRecord(Context, MegaphoneRecord)}
|
||||
* - Include the event in {@link #buildDisplayOrder(Context)}
|
||||
* - Include the event in {@link #buildDisplayOrder(Context, Map)}
|
||||
*
|
||||
* Common patterns:
|
||||
* - For events that have a snooze-able recurring display schedule, use a {@link RecurringSchedule}.
|
||||
* - For events guarded by feature flags, set a {@link ForeverSchedule} with false in
|
||||
* {@link #buildDisplayOrder(Context)}.
|
||||
* {@link #buildDisplayOrder(Context, Map)}.
|
||||
* - For events that change, return different megaphones in {@link #forRecord(Context, MegaphoneRecord)}
|
||||
* based on whatever properties you're interested in.
|
||||
*/
|
||||
|
@ -65,12 +66,16 @@ public final class Megaphones {
|
|||
private static final MegaphoneSchedule ALWAYS = new ForeverSchedule(true);
|
||||
private static final MegaphoneSchedule NEVER = new ForeverSchedule(false);
|
||||
|
||||
private static final Set<Event> DONATE_EVENTS = SetUtil.newHashSet(Event.VALENTINES_DONATIONS_2022, Event.BECOME_A_SUSTAINER);
|
||||
private static final long MIN_TIME_BETWEEN_DONATE_MEGAPHONES = TimeUnit.DAYS.toMillis(30);
|
||||
|
||||
private Megaphones() {}
|
||||
|
||||
@WorkerThread
|
||||
static @Nullable Megaphone getNextMegaphone(@NonNull Context context, @NonNull Map<Event, MegaphoneRecord> records) {
|
||||
long currentTime = System.currentTimeMillis();
|
||||
|
||||
List<Megaphone> megaphones = Stream.of(buildDisplayOrder(context))
|
||||
List<Megaphone> megaphones = Stream.of(buildDisplayOrder(context, records))
|
||||
.filter(e -> {
|
||||
MegaphoneRecord record = Objects.requireNonNull(records.get(e.getKey()));
|
||||
MegaphoneSchedule schedule = e.getValue();
|
||||
|
@ -95,13 +100,14 @@ public final class Megaphones {
|
|||
*
|
||||
* This is also when you would hide certain megaphones based on things like {@link FeatureFlags}.
|
||||
*/
|
||||
private static Map<Event, MegaphoneSchedule> buildDisplayOrder(@NonNull Context context) {
|
||||
private static Map<Event, MegaphoneSchedule> buildDisplayOrder(@NonNull Context context, @NonNull Map<Event, MegaphoneRecord> records) {
|
||||
return new LinkedHashMap<Event, MegaphoneSchedule>() {{
|
||||
put(Event.PINS_FOR_ALL, new PinsForAllSchedule());
|
||||
put(Event.CLIENT_DEPRECATED, SignalStore.misc().isClientDeprecated() ? ALWAYS : NEVER);
|
||||
put(Event.NOTIFICATIONS, shouldShowNotificationsMegaphone(context) ? RecurringSchedule.every(TimeUnit.DAYS.toMillis(30)) : NEVER);
|
||||
put(Event.ONBOARDING, shouldShowOnboardingMegaphone(context) ? ALWAYS : NEVER);
|
||||
put(Event.BECOME_A_SUSTAINER, shouldShowDonateMegaphone(context) ? ShowForDurationSchedule.showForDays(7) : NEVER);
|
||||
put(Event.BECOME_A_SUSTAINER, shouldShowDonateMegaphone(context, records) ? ShowForDurationSchedule.showForDays(7) : NEVER);
|
||||
put(Event.VALENTINES_DONATIONS_2022, shouldShowValentinesDonationsMegaphone(context, records) ? ShowForDurationSchedule.showForDays(1) : NEVER);
|
||||
put(Event.PIN_REMINDER, new SignalPinReminderSchedule());
|
||||
|
||||
// Feature-introduction megaphones should *probably* be added below this divider
|
||||
|
@ -129,6 +135,8 @@ public final class Megaphones {
|
|||
return buildAddAProfilePhotoMegaphone(context);
|
||||
case BECOME_A_SUSTAINER:
|
||||
return buildBecomeASustainerMegaphone(context);
|
||||
case VALENTINES_DONATIONS_2022:
|
||||
return buildValentinesDonationsMegaphone(context);
|
||||
case NOTIFICATION_PROFILES:
|
||||
return buildNotificationProfilesMegaphone(context);
|
||||
default:
|
||||
|
@ -275,6 +283,21 @@ public final class Megaphones {
|
|||
.build();
|
||||
}
|
||||
|
||||
private static @NonNull Megaphone buildValentinesDonationsMegaphone(@NonNull Context context) {
|
||||
return new Megaphone.Builder(Event.VALENTINES_DONATIONS_2022, Megaphone.Style.BASIC)
|
||||
.setTitle(R.string.ValentinesDayMegaphone_happy_heart_day)
|
||||
.setImage(R.drawable.ic_valentines_donor_megaphone_64)
|
||||
.setBody(R.string.ValentinesDayMegaphone_show_your_affection)
|
||||
.setActionButton(R.string.BecomeASustainerMegaphone__contribute, (megaphone, listener) -> {
|
||||
listener.onMegaphoneNavigationRequested(AppSettingsActivity.subscriptions(context));
|
||||
listener.onMegaphoneCompleted(Event.VALENTINES_DONATIONS_2022);
|
||||
})
|
||||
.setSecondaryButton(R.string.BecomeASustainerMegaphone__no_thanks, (megaphone, listener) -> {
|
||||
listener.onMegaphoneCompleted(Event.VALENTINES_DONATIONS_2022);
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
private static @NonNull Megaphone buildNotificationProfilesMegaphone(@NonNull Context context) {
|
||||
return new Megaphone.Builder(Event.NOTIFICATION_PROFILES, Megaphone.Style.BASIC)
|
||||
.setTitle(R.string.NotificationProfilesMegaphone__notification_profiles)
|
||||
|
@ -290,15 +313,36 @@ public final class Megaphones {
|
|||
.build();
|
||||
}
|
||||
|
||||
private static boolean shouldShowDonateMegaphone(@NonNull Context context) {
|
||||
return VersionTracker.getDaysSinceFirstInstalled(context) >= 7 &&
|
||||
private static boolean shouldShowDonateMegaphone(@NonNull Context context, @NonNull Map<Event, MegaphoneRecord> records) {
|
||||
long timeSinceLastDonatePrompt = timeSinceLastDonatePrompt(records);
|
||||
|
||||
return timeSinceLastDonatePrompt > MIN_TIME_BETWEEN_DONATE_MEGAPHONES &&
|
||||
VersionTracker.getDaysSinceFirstInstalled(context) >= 7 &&
|
||||
LocaleFeatureFlags.isInDonateMegaphone() &&
|
||||
PlayServicesUtil.getPlayServicesStatus(context) == PlayServicesUtil.PlayServicesStatus.SUCCESS &&
|
||||
Recipient.self()
|
||||
.getBadges()
|
||||
.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.noneMatch(badge -> badge.getCategory() == Badge.Category.Donor);
|
||||
.getBadges()
|
||||
.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.noneMatch(badge -> badge.getCategory() == Badge.Category.Donor);
|
||||
}
|
||||
|
||||
private static boolean shouldShowValentinesDonationsMegaphone(@NonNull Context context, @NonNull Map<Event, MegaphoneRecord> records) {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
long timeSinceLastDonatePrompt = timeSinceLastDonatePrompt(records);
|
||||
|
||||
return timeSinceLastDonatePrompt > MIN_TIME_BETWEEN_DONATE_MEGAPHONES &&
|
||||
VersionTracker.getDaysSinceFirstInstalled(context) >= 7 &&
|
||||
LocaleFeatureFlags.isInValentinesDonateMegaphone() &&
|
||||
now.getMonth() == Month.FEBRUARY &&
|
||||
now.getDayOfMonth() == 14 &&
|
||||
now.getYear() == 2022 &&
|
||||
PlayServicesUtil.getPlayServicesStatus(context) == PlayServicesUtil.PlayServicesStatus.SUCCESS &&
|
||||
Recipient.self()
|
||||
.getBadges()
|
||||
.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.noneMatch(badge -> badge.getCategory() == Badge.Category.Donor);
|
||||
}
|
||||
|
||||
private static boolean shouldShowOnboardingMegaphone(@NonNull Context context) {
|
||||
|
@ -316,7 +360,8 @@ public final class Megaphones {
|
|||
.textExistsInUsersLanguage(R.string.NotificationsMegaphone_turn_on_notifications,
|
||||
R.string.NotificationsMegaphone_never_miss_a_message,
|
||||
R.string.NotificationsMegaphone_turn_on,
|
||||
R.string.NotificationsMegaphone_not_now)) {
|
||||
R.string.NotificationsMegaphone_not_now))
|
||||
{
|
||||
Log.i(TAG, "Would show NotificationsMegaphone but is not yet translated in " + locale);
|
||||
return false;
|
||||
}
|
||||
|
@ -338,6 +383,23 @@ public final class Megaphones {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unfortunately lastSeen is only set today upon snoozing, which never happens to donate prompts.
|
||||
* So we use firstVisible as a proxy.
|
||||
*/
|
||||
private static long timeSinceLastDonatePrompt(@NonNull Map<Event, MegaphoneRecord> records) {
|
||||
long lastSeenDonatePrompt = records.entrySet()
|
||||
.stream()
|
||||
.filter(e -> DONATE_EVENTS.contains(e.getKey()))
|
||||
.map(e -> e.getValue().getFirstVisible())
|
||||
.filter(t -> t > 0)
|
||||
.sorted()
|
||||
.findFirst()
|
||||
.orElse(0L);
|
||||
return System.currentTimeMillis() - lastSeenDonatePrompt;
|
||||
}
|
||||
|
||||
|
||||
public enum Event {
|
||||
PINS_FOR_ALL("pins_for_all"),
|
||||
PIN_REMINDER("pin_reminder"),
|
||||
|
@ -347,6 +409,7 @@ public final class Megaphones {
|
|||
CHAT_COLORS("chat_colors"),
|
||||
ADD_A_PROFILE_PHOTO("add_a_profile_photo"),
|
||||
BECOME_A_SUSTAINER("become_a_sustainer"),
|
||||
VALENTINES_DONATIONS_2022("valentines_donations_2022"),
|
||||
NOTIFICATION_PROFILES("notification_profiles");
|
||||
|
||||
private final String key;
|
||||
|
|
|
@ -63,6 +63,7 @@ public final class FeatureFlags {
|
|||
private static final String PHONE_NUMBER_PRIVACY_VERSION = "android.phoneNumberPrivacyVersion";
|
||||
private static final String CLIENT_EXPIRATION = "android.clientExpiration";
|
||||
public static final String DONATE_MEGAPHONE = "android.donate.2";
|
||||
public static final String VALENTINES_DONATE_MEGAPHONE = "android.donate.valentines.2022";
|
||||
private static final String CUSTOM_VIDEO_MUXER = "android.customVideoMuxer";
|
||||
private static final String CDS_REFRESH_INTERVAL = "cds.syncInterval.seconds";
|
||||
private static final String AUTOMATIC_SESSION_RESET = "android.automaticSessionReset.2";
|
||||
|
@ -132,7 +133,8 @@ public final class FeatureFlags {
|
|||
DONOR_BADGES_DISPLAY,
|
||||
CHANGE_NUMBER_ENABLED,
|
||||
HARDWARE_AEC_MODELS,
|
||||
FORCE_DEFAULT_AEC
|
||||
FORCE_DEFAULT_AEC,
|
||||
VALENTINES_DONATE_MEGAPHONE
|
||||
);
|
||||
|
||||
@VisibleForTesting
|
||||
|
@ -187,7 +189,8 @@ public final class FeatureFlags {
|
|||
SENDER_KEY_MAX_AGE,
|
||||
DONOR_BADGES_DISPLAY,
|
||||
DONATE_MEGAPHONE,
|
||||
FORCE_DEFAULT_AEC
|
||||
FORCE_DEFAULT_AEC,
|
||||
VALENTINES_DONATE_MEGAPHONE
|
||||
);
|
||||
|
||||
/**
|
||||
|
@ -303,6 +306,11 @@ public final class FeatureFlags {
|
|||
return getString(DONATE_MEGAPHONE, "");
|
||||
}
|
||||
|
||||
/** The raw valentine's day donate megaphone CSV string */
|
||||
public static String valentinesDonateMegaphone() {
|
||||
return getString(VALENTINES_DONATE_MEGAPHONE, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the user can choose phone number privacy settings, and;
|
||||
* Whether to fetch and store the secondary certificate
|
||||
|
|
|
@ -37,6 +37,13 @@ public final class LocaleFeatureFlags {
|
|||
return isEnabled(FeatureFlags.DONATE_MEGAPHONE, FeatureFlags.donateMegaphone());
|
||||
}
|
||||
|
||||
/**
|
||||
* In valentines donation megaphone group for given country code
|
||||
*/
|
||||
public static boolean isInValentinesDonateMegaphone() {
|
||||
return isEnabled(FeatureFlags.VALENTINES_DONATE_MEGAPHONE, FeatureFlags.valentinesDonateMegaphone());
|
||||
}
|
||||
|
||||
public static @NonNull Optional<PushMediaConstraints.MediaConfig> getMediaQualityLevel() {
|
||||
Map<String, Integer> countryValues = parseCountryValues(FeatureFlags.getMediaQualityLevels(), NOT_FOUND);
|
||||
int level = getCountryValue(countryValues, Recipient.self().getE164().or(""), NOT_FOUND);
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1445,6 +1445,12 @@
|
|||
<string name="RedPhone_the_number_you_dialed_does_not_support_secure_voice">The number you dialed does not support secure voice!</string>
|
||||
<string name="RedPhone_got_it">Got it</string>
|
||||
|
||||
<!-- Valentine's Day Megaphone -->
|
||||
<!-- Title text for the Valentine's Day donation megaphone. The placeholder will always be a heart emoji. Needs to be a placeholder for Android reasons. -->
|
||||
<string name="ValentinesDayMegaphone_happy_heart_day">Happy 💜 Day!</string>
|
||||
<!-- Body text for the Valentine's Day donation megaphone. -->
|
||||
<string name="ValentinesDayMegaphone_show_your_affection">Show your affection by becoming a Signal sustainer.</string>
|
||||
|
||||
<!-- WebRtcCallActivity -->
|
||||
<string name="WebRtcCallActivity__tap_here_to_turn_on_your_video">Tap here to turn on your video</string>
|
||||
<string name="WebRtcCallActivity__to_call_s_signal_needs_access_to_your_camera">To call %1$s, Signal needs access to your camera</string>
|
||||
|
|
Loading…
Add table
Reference in a new issue