Show a megaphone when notifications are disabled.
This commit is contained in:
parent
4f01bacb49
commit
8f6ff215aa
11 changed files with 120 additions and 6 deletions
|
@ -66,9 +66,10 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
public class ApplicationPreferencesActivity extends PassphraseRequiredActivity
|
public class ApplicationPreferencesActivity extends PassphraseRequiredActivity
|
||||||
implements SharedPreferences.OnSharedPreferenceChangeListener
|
implements SharedPreferences.OnSharedPreferenceChangeListener
|
||||||
{
|
{
|
||||||
public static final String LAUNCH_TO_BACKUPS_FRAGMENT = "launch.to.backups.fragment";
|
public static final String LAUNCH_TO_BACKUPS_FRAGMENT = "launch.to.backups.fragment";
|
||||||
public static final String LAUNCH_TO_HELP_FRAGMENT = "launch.to.help.fragment";
|
public static final String LAUNCH_TO_HELP_FRAGMENT = "launch.to.help.fragment";
|
||||||
public static final String LAUNCH_TO_PROXY_FRAGMENT = "launch.to.proxy.fragment";
|
public static final String LAUNCH_TO_PROXY_FRAGMENT = "launch.to.proxy.fragment";
|
||||||
|
public static final String LAUNCH_TO_NOTIFICATIONS_FRAGMENT = "launch.to.notifications.fragment";
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private static final String TAG = ApplicationPreferencesActivity.class.getSimpleName();
|
private static final String TAG = ApplicationPreferencesActivity.class.getSimpleName();
|
||||||
|
@ -112,6 +113,8 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActivity
|
||||||
initFragment(android.R.id.content, new HelpFragment());
|
initFragment(android.R.id.content, new HelpFragment());
|
||||||
} else if (getIntent() != null && getIntent().getBooleanExtra(LAUNCH_TO_PROXY_FRAGMENT, false)) {
|
} else if (getIntent() != null && getIntent().getBooleanExtra(LAUNCH_TO_PROXY_FRAGMENT, false)) {
|
||||||
initFragment(android.R.id.content, EditProxyFragment.newInstance());
|
initFragment(android.R.id.content, EditProxyFragment.newInstance());
|
||||||
|
} else if (getIntent() != null && getIntent().getBooleanExtra(LAUNCH_TO_NOTIFICATIONS_FRAGMENT, false)) {
|
||||||
|
initFragment(android.R.id.content, new NotificationsPreferenceFragment());
|
||||||
} else if (icicle == null) {
|
} else if (icicle == null) {
|
||||||
initFragment(android.R.id.content, new ApplicationPreferenceFragment());
|
initFragment(android.R.id.content, new ApplicationPreferenceFragment());
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -56,7 +56,10 @@ public class BasicMegaphoneView extends FrameLayout {
|
||||||
this.megaphone = megaphone;
|
this.megaphone = megaphone;
|
||||||
this.megaphoneListener = megaphoneListener;
|
this.megaphoneListener = megaphoneListener;
|
||||||
|
|
||||||
if (megaphone.getImageRequest() != null) {
|
if (megaphone.getImageRes() != 0) {
|
||||||
|
image.setVisibility(VISIBLE);
|
||||||
|
image.setImageResource(megaphone.getImageRes());
|
||||||
|
} else if (megaphone.getImageRequest() != null) {
|
||||||
image.setVisibility(VISIBLE);
|
image.setVisibility(VISIBLE);
|
||||||
megaphone.getImageRequest().into(image);
|
megaphone.getImageRequest().into(image);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -24,6 +24,7 @@ public class Megaphone {
|
||||||
private final boolean canSnooze;
|
private final boolean canSnooze;
|
||||||
private final int titleRes;
|
private final int titleRes;
|
||||||
private final int bodyRes;
|
private final int bodyRes;
|
||||||
|
private final int imageRes;
|
||||||
private final GlideRequest<Drawable> imageRequest;
|
private final GlideRequest<Drawable> imageRequest;
|
||||||
private final int buttonTextRes;
|
private final int buttonTextRes;
|
||||||
private final EventListener buttonListener;
|
private final EventListener buttonListener;
|
||||||
|
@ -39,6 +40,7 @@ public class Megaphone {
|
||||||
this.canSnooze = builder.canSnooze;
|
this.canSnooze = builder.canSnooze;
|
||||||
this.titleRes = builder.titleRes;
|
this.titleRes = builder.titleRes;
|
||||||
this.bodyRes = builder.bodyRes;
|
this.bodyRes = builder.bodyRes;
|
||||||
|
this.imageRes = builder.imageRes;
|
||||||
this.imageRequest = builder.imageRequest;
|
this.imageRequest = builder.imageRequest;
|
||||||
this.buttonTextRes = builder.buttonTextRes;
|
this.buttonTextRes = builder.buttonTextRes;
|
||||||
this.buttonListener = builder.buttonListener;
|
this.buttonListener = builder.buttonListener;
|
||||||
|
@ -72,6 +74,10 @@ public class Megaphone {
|
||||||
return bodyRes;
|
return bodyRes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public @DrawableRes int getImageRes() {
|
||||||
|
return imageRes;
|
||||||
|
}
|
||||||
|
|
||||||
public @Nullable GlideRequest<Drawable> getImageRequest() {
|
public @Nullable GlideRequest<Drawable> getImageRequest() {
|
||||||
return imageRequest;
|
return imageRequest;
|
||||||
}
|
}
|
||||||
|
@ -117,6 +123,7 @@ public class Megaphone {
|
||||||
private boolean canSnooze;
|
private boolean canSnooze;
|
||||||
private int titleRes;
|
private int titleRes;
|
||||||
private int bodyRes;
|
private int bodyRes;
|
||||||
|
private int imageRes;
|
||||||
private GlideRequest<Drawable> imageRequest;
|
private GlideRequest<Drawable> imageRequest;
|
||||||
private int buttonTextRes;
|
private int buttonTextRes;
|
||||||
private EventListener buttonListener;
|
private EventListener buttonListener;
|
||||||
|
@ -163,7 +170,8 @@ public class Megaphone {
|
||||||
}
|
}
|
||||||
|
|
||||||
public @NonNull Builder setImage(@DrawableRes int imageRes) {
|
public @NonNull Builder setImage(@DrawableRes int imageRes) {
|
||||||
return setImageRequest(GlideApp.with(ApplicationDependencies.getApplication()).load(imageRes));
|
this.imageRes = imageRes;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public @NonNull Builder setImageRequest(@Nullable GlideRequest<Drawable> imageRequest) {
|
public @NonNull Builder setImageRequest(@Nullable GlideRequest<Drawable> imageRequest) {
|
||||||
|
|
|
@ -2,6 +2,8 @@ package org.thoughtcrime.securesms.megaphone;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.provider.Settings;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
@ -9,6 +11,7 @@ import androidx.annotation.Nullable;
|
||||||
import com.annimon.stream.Stream;
|
import com.annimon.stream.Stream;
|
||||||
|
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
|
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.conversationlist.ConversationListFragment;
|
import org.thoughtcrime.securesms.conversationlist.ConversationListFragment;
|
||||||
import org.thoughtcrime.securesms.database.model.MegaphoneRecord;
|
import org.thoughtcrime.securesms.database.model.MegaphoneRecord;
|
||||||
|
@ -20,6 +23,7 @@ import org.thoughtcrime.securesms.lock.SignalPinReminders;
|
||||||
import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity;
|
import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity;
|
||||||
import org.thoughtcrime.securesms.lock.v2.KbsMigrationActivity;
|
import org.thoughtcrime.securesms.lock.v2.KbsMigrationActivity;
|
||||||
import org.thoughtcrime.securesms.messagerequests.MessageRequestMegaphoneActivity;
|
import org.thoughtcrime.securesms.messagerequests.MessageRequestMegaphoneActivity;
|
||||||
|
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||||
import org.thoughtcrime.securesms.profiles.ProfileName;
|
import org.thoughtcrime.securesms.profiles.ProfileName;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.util.CommunicationActions;
|
import org.thoughtcrime.securesms.util.CommunicationActions;
|
||||||
|
@ -32,6 +36,7 @@ import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creating a new megaphone:
|
* Creating a new megaphone:
|
||||||
|
@ -94,6 +99,7 @@ public final class Megaphones {
|
||||||
put(Event.DONATE, shouldShowDonateMegaphone(context) ? ShowForDurationSchedule.showForDays(7) : NEVER);
|
put(Event.DONATE, shouldShowDonateMegaphone(context) ? ShowForDurationSchedule.showForDays(7) : NEVER);
|
||||||
put(Event.GROUP_CALLING, shouldShowGroupCallingMegaphone() ? ALWAYS : NEVER);
|
put(Event.GROUP_CALLING, shouldShowGroupCallingMegaphone() ? ALWAYS : NEVER);
|
||||||
put(Event.ONBOARDING, shouldShowOnboardingMegaphone(context) ? ALWAYS : NEVER);
|
put(Event.ONBOARDING, shouldShowOnboardingMegaphone(context) ? ALWAYS : NEVER);
|
||||||
|
put(Event.NOTIFICATIONS, shouldShowNotificationsMegaphone(context) ? RecurringSchedule.every(TimeUnit.DAYS.toMillis(30)) : NEVER);
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,6 +125,8 @@ public final class Megaphones {
|
||||||
return buildGroupCallingMegaphone(context);
|
return buildGroupCallingMegaphone(context);
|
||||||
case ONBOARDING:
|
case ONBOARDING:
|
||||||
return buildOnboardingMegaphone();
|
return buildOnboardingMegaphone();
|
||||||
|
case NOTIFICATIONS:
|
||||||
|
return buildNotificationsMegaphone(context);
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Event not handled!");
|
throw new IllegalArgumentException("Event not handled!");
|
||||||
}
|
}
|
||||||
|
@ -264,6 +272,34 @@ public final class Megaphones {
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static @NonNull Megaphone buildNotificationsMegaphone(@NonNull Context context) {
|
||||||
|
return new Megaphone.Builder(Event.NOTIFICATIONS, Megaphone.Style.BASIC)
|
||||||
|
.setTitle(R.string.NotificationsMegaphone_turn_on_notifications)
|
||||||
|
.setBody(R.string.NotificationsMegaphone_never_miss_a_message)
|
||||||
|
.setImage(R.drawable.megaphone_notifications_64)
|
||||||
|
.setActionButton(R.string.NotificationsMegaphone_turn_on, (megaphone, controller) -> {
|
||||||
|
controller.onMegaphoneSnooze(Event.NOTIFICATIONS);
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= 26 && !NotificationChannels.isMessageChannelEnabled(context)) {
|
||||||
|
Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS);
|
||||||
|
intent.putExtra(Settings.EXTRA_CHANNEL_ID, NotificationChannels.getMessagesChannel(context));
|
||||||
|
intent.putExtra(Settings.EXTRA_APP_PACKAGE, context.getPackageName());
|
||||||
|
controller.onMegaphoneNavigationRequested(intent);
|
||||||
|
} else if (Build.VERSION.SDK_INT >= 26 && !NotificationChannels.areNotificationsEnabled(context)) {
|
||||||
|
Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
|
||||||
|
intent.putExtra(Settings.EXTRA_APP_PACKAGE, context.getPackageName());
|
||||||
|
controller.onMegaphoneNavigationRequested(intent);
|
||||||
|
} else {
|
||||||
|
Intent intent = new Intent(context, ApplicationPreferencesActivity.class);
|
||||||
|
intent.putExtra(ApplicationPreferencesActivity.LAUNCH_TO_NOTIFICATIONS_FRAGMENT, true);
|
||||||
|
controller.onMegaphoneNavigationRequested(intent);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setSecondaryButton(R.string.NotificationsMegaphone_not_now, (megaphone, controller) -> controller.onMegaphoneSnooze(Event.NOTIFICATIONS))
|
||||||
|
.setPriority(Megaphone.Priority.DEFAULT)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean shouldShowMessageRequestsMegaphone() {
|
private static boolean shouldShowMessageRequestsMegaphone() {
|
||||||
return Recipient.self().getProfileName() == ProfileName.EMPTY;
|
return Recipient.self().getProfileName() == ProfileName.EMPTY;
|
||||||
}
|
}
|
||||||
|
@ -288,6 +324,12 @@ public final class Megaphones {
|
||||||
return SignalStore.onboarding().hasOnboarding(context);
|
return SignalStore.onboarding().hasOnboarding(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean shouldShowNotificationsMegaphone(@NonNull Context context) {
|
||||||
|
return !TextSecurePreferences.isNotificationsEnabled(context) ||
|
||||||
|
!NotificationChannels.isMessageChannelEnabled(context) ||
|
||||||
|
!NotificationChannels.areNotificationsEnabled(context);
|
||||||
|
}
|
||||||
|
|
||||||
public enum Event {
|
public enum Event {
|
||||||
REACTIONS("reactions"),
|
REACTIONS("reactions"),
|
||||||
PINS_FOR_ALL("pins_for_all"),
|
PINS_FOR_ALL("pins_for_all"),
|
||||||
|
@ -298,7 +340,8 @@ public final class Megaphones {
|
||||||
RESEARCH("research"),
|
RESEARCH("research"),
|
||||||
DONATE("donate"),
|
DONATE("donate"),
|
||||||
GROUP_CALLING("group_calling"),
|
GROUP_CALLING("group_calling"),
|
||||||
ONBOARDING("onboarding");
|
ONBOARDING("onboarding"),
|
||||||
|
NOTIFICATIONS("notifications");
|
||||||
|
|
||||||
private final String key;
|
private final String key;
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,34 @@
|
||||||
package org.thoughtcrime.securesms.megaphone;
|
package org.thoughtcrime.securesms.megaphone;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A schedule that provides a high level of control, allowing you to specify an amount of time to
|
||||||
|
* wait based on how many times a user has seen the megaphone.
|
||||||
|
*/
|
||||||
class RecurringSchedule implements MegaphoneSchedule {
|
class RecurringSchedule implements MegaphoneSchedule {
|
||||||
|
|
||||||
private final long[] gaps;
|
private final long[] gaps;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How long to wait after each time a user has seen the megaphone. Index 0 corresponds to how long
|
||||||
|
* to wait to show it again after the user has seen it once, index 1 is for after the user has
|
||||||
|
* seen it twice, etc. If the seen count is greater than the number of provided intervals, it will
|
||||||
|
* continue to use the last interval provided indefinitely.
|
||||||
|
*
|
||||||
|
* The schedule will always show the megaphone if the user has never seen it.
|
||||||
|
*/
|
||||||
RecurringSchedule(long... durationGaps) {
|
RecurringSchedule(long... durationGaps) {
|
||||||
this.gaps = durationGaps;
|
this.gaps = durationGaps;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shortcut for a recurring schedule with a single interval.
|
||||||
|
*/
|
||||||
|
public static @NonNull MegaphoneSchedule every(long interval) {
|
||||||
|
return new RecurringSchedule(interval);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean shouldDisplay(int seenCount, long lastSeen, long firstVisible, long currentTime) {
|
public boolean shouldDisplay(int seenCount, long lastSeen, long firstVisible, long currentTime) {
|
||||||
if (seenCount == 0) {
|
if (seenCount == 0) {
|
||||||
|
|
|
@ -376,6 +376,36 @@ public class NotificationChannels {
|
||||||
ensureCustomChannelConsistency(context);
|
ensureCustomChannelConsistency(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the default messages notification channel is enabled. Note that "enabled" just
|
||||||
|
* means receiving notifications in some capacity -- a user could have it enabled, but set it to a
|
||||||
|
* lower importance.
|
||||||
|
*
|
||||||
|
* This could also return true if the specific channnel is enabled, but notifications *overall*
|
||||||
|
* are disabled. Check {@link #areNotificationsEnabled(Context)} to be safe.
|
||||||
|
*/
|
||||||
|
public static synchronized boolean isMessageChannelEnabled(@NonNull Context context) {
|
||||||
|
if (!supported()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationManager notificationManager = ServiceUtil.getNotificationManager(context);
|
||||||
|
NotificationChannel channel = notificationManager.getNotificationChannel(getMessagesChannel(context));
|
||||||
|
|
||||||
|
return channel != null && channel.getImportance() != NotificationManager.IMPORTANCE_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not notifications for the entire app are enabled.
|
||||||
|
*/
|
||||||
|
public static synchronized boolean areNotificationsEnabled(@NonNull Context context) {
|
||||||
|
if (Build.VERSION.SDK_INT >= 24) {
|
||||||
|
return ServiceUtil.getNotificationManager(context).areNotificationsEnabled();
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the name of an existing channel to match the recipient's current name. Will have no
|
* Updates the name of an existing channel to match the recipient's current name. Will have no
|
||||||
* effect if the recipient doesn't have an existing valid channel.
|
* effect if the recipient doesn't have an existing valid channel.
|
||||||
|
|
BIN
app/src/main/res/drawable-hdpi/megaphone_notifications_64.webp
Normal file
BIN
app/src/main/res/drawable-hdpi/megaphone_notifications_64.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.5 KiB |
BIN
app/src/main/res/drawable-mdpi/megaphone_notifications_64.webp
Normal file
BIN
app/src/main/res/drawable-mdpi/megaphone_notifications_64.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.3 KiB |
BIN
app/src/main/res/drawable-xhdpi/megaphone_notifications_64.webp
Normal file
BIN
app/src/main/res/drawable-xhdpi/megaphone_notifications_64.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.9 KiB |
BIN
app/src/main/res/drawable-xxhdpi/megaphone_notifications_64.webp
Normal file
BIN
app/src/main/res/drawable-xxhdpi/megaphone_notifications_64.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
|
@ -973,6 +973,12 @@
|
||||||
<string name="NotificationBarManager__end_call">End call</string>
|
<string name="NotificationBarManager__end_call">End call</string>
|
||||||
<string name="NotificationBarManager__cancel_call">Cancel call</string>
|
<string name="NotificationBarManager__cancel_call">Cancel call</string>
|
||||||
|
|
||||||
|
<!-- NotificationsMegaphone -->
|
||||||
|
<string name="NotificationsMegaphone_turn_on_notifications">Turn on Notifications?</string>
|
||||||
|
<string name="NotificationsMegaphone_never_miss_a_message">Never miss a message from your contacts and groups.</string>
|
||||||
|
<string name="NotificationsMegaphone_turn_on">Turn on</string>
|
||||||
|
<string name="NotificationsMegaphone_not_now">Not now</string>
|
||||||
|
|
||||||
<!-- NotificationMmsMessageRecord -->
|
<!-- NotificationMmsMessageRecord -->
|
||||||
<string name="NotificationMmsMessageRecord_multimedia_message">Multimedia message</string>
|
<string name="NotificationMmsMessageRecord_multimedia_message">Multimedia message</string>
|
||||||
<string name="NotificationMmsMessageRecord_downloading_mms_message">Downloading MMS message</string>
|
<string name="NotificationMmsMessageRecord_downloading_mms_message">Downloading MMS message</string>
|
||||||
|
|
Loading…
Add table
Reference in a new issue