Add phased SMS removal UX.

This commit is contained in:
Cody Henthorne
2022-10-13 11:33:13 -04:00
committed by Greyson Parrelli
parent 8a238a66e7
commit b6db7e7af6
68 changed files with 1214 additions and 187 deletions

View File

@@ -1,5 +1,8 @@
package org.thoughtcrime.securesms.megaphone;
import androidx.annotation.WorkerThread;
public interface MegaphoneSchedule {
@WorkerThread
boolean shouldDisplay(int seenCount, long lastSeen, long firstVisible, long currentTime);
}

View File

@@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.megaphone;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.provider.Settings;
@@ -22,8 +21,10 @@ import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity;
import org.thoughtcrime.securesms.database.model.MegaphoneRecord;
import org.thoughtcrime.securesms.database.model.RemoteMegaphoneRecord;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.exporter.flow.SmsExportActivity;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.keyvalue.SmsExportPhase;
import org.thoughtcrime.securesms.lock.SignalPinReminderDialog;
import org.thoughtcrime.securesms.lock.SignalPinReminders;
import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity;
@@ -39,7 +40,6 @@ import org.thoughtcrime.securesms.util.PlayServicesUtil;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.VersionTracker;
import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageContextWrapper;
import org.thoughtcrime.securesms.wallpaper.ChatWallpaperActivity;
import java.util.LinkedHashMap;
import java.util.List;
@@ -108,6 +108,7 @@ public final class Megaphones {
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.SMS_EXPORT, new SmsExportReminderSchedule(context));
put(Event.BACKUP_SCHEDULE_PERMISSION, shouldShowBackupSchedulePermissionMegaphone(context) ? RecurringSchedule.every(TimeUnit.DAYS.toMillis(3)) : NEVER);
put(Event.ONBOARDING, shouldShowOnboardingMegaphone(context) ? ALWAYS : NEVER);
put(Event.TURN_OFF_CENSORSHIP_CIRCUMVENTION, shouldShowTurnOffCircumventionMegaphone() ? RecurringSchedule.every(TimeUnit.DAYS.toMillis(7)) : NEVER);
@@ -144,6 +145,8 @@ public final class Megaphones {
return buildRemoteMegaphone(context);
case BACKUP_SCHEDULE_PERMISSION:
return buildBackupPermissionMegaphone(context);
case SMS_EXPORT:
return buildSmsExportMegaphone(context);
default:
throw new IllegalArgumentException("Event not handled!");
}
@@ -356,6 +359,35 @@ public final class Megaphones {
.build();
}
private static @NonNull Megaphone buildSmsExportMegaphone(@NonNull Context context) {
SmsExportPhase phase = SignalStore.misc().getSmsExportPhase();
if (phase == SmsExportPhase.PHASE_1) {
return new Megaphone.Builder(Event.SMS_EXPORT, Megaphone.Style.BASIC)
.setTitle(R.string.SmsExportMegaphone__sms_support_going_away)
.setImage(R.drawable.sms_megaphone)
.setBody(R.string.SmsExportMegaphone__sms_support_will_be_removed_soon_to_focus_on_encrypted_messaging)
.setActionButton(R.string.SmsExportMegaphone__export_sms, (megaphone, controller) -> controller.onMegaphoneNavigationRequested(SmsExportActivity.createIntent(context), SmsExportMegaphoneActivity.REQUEST_CODE))
.setSecondaryButton(R.string.Megaphones_remind_me_later, (megaphone, controller) -> controller.onMegaphoneSnooze(Event.SMS_EXPORT))
.setOnVisibleListener((megaphone, controller) -> SignalStore.misc().setHasSeenSmsExportMegaphone())
.build();
} else {
Megaphone.Builder builder = new Megaphone.Builder(Event.SMS_EXPORT, Megaphone.Style.FULLSCREEN)
.setOnVisibleListener((megaphone, controller) -> {
if (phase.isBlockingUi()) {
SmsExportReminderSchedule.setShowPhase3Megaphone(false);
}
controller.onMegaphoneNavigationRequested(new Intent(context, SmsExportMegaphoneActivity.class), SmsExportMegaphoneActivity.REQUEST_CODE);
});
if (phase.isBlockingUi()) {
builder.disableSnooze();
}
return builder.build();
}
}
private static boolean shouldShowDonateMegaphone(@NonNull Context context, @NonNull Event event, @NonNull Map<Event, MegaphoneRecord> records) {
long timeSinceLastDonatePrompt = timeSinceLastDonatePrompt(event, records);
@@ -452,7 +484,8 @@ public final class Megaphones {
DONATE_Q2_2022("donate_q2_2022"),
TURN_OFF_CENSORSHIP_CIRCUMVENTION("turn_off_censorship_circumvention"),
REMOTE_MEGAPHONE("remote_megaphone"),
BACKUP_SCHEDULE_PERMISSION("backup_schedule_permission");
BACKUP_SCHEDULE_PERMISSION("backup_schedule_permission"),
SMS_EXPORT("sms_export");
private final String key;

View File

@@ -0,0 +1,80 @@
package org.thoughtcrime.securesms.megaphone
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import org.thoughtcrime.securesms.PassphraseRequiredActivity
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.databinding.SmsExportMegaphoneActivityBinding
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.exporter.flow.SmsExportActivity
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.util.CommunicationActions
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme
import org.thoughtcrime.securesms.util.DynamicTheme
class SmsExportMegaphoneActivity : PassphraseRequiredActivity() {
companion object {
const val REQUEST_CODE: Short = 5343
}
private val theme: DynamicTheme = DynamicNoActionBarTheme()
private lateinit var binding: SmsExportMegaphoneActivityBinding
private lateinit var smsExportLauncher: ActivityResultLauncher<Intent>
override fun onPreCreate() {
theme.onCreate(this)
}
override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
binding = SmsExportMegaphoneActivityBinding.inflate(layoutInflater)
setContentView(binding.root)
smsExportLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == Activity.RESULT_OK) {
ApplicationDependencies.getMegaphoneRepository().markSeen(Megaphones.Event.SMS_EXPORT)
setResult(Activity.RESULT_OK)
finish()
}
}
binding.toolbar.setNavigationOnClickListener { onBackPressed() }
if (SignalStore.misc().smsExportPhase.isBlockingUi()) {
binding.headline.setText(R.string.SmsExportMegaphoneActivity__signal_no_longer_supports_sms)
binding.description.setText(R.string.SmsExportMegaphoneActivity__signal_has_removed_support_for_sending_sms_messages)
binding.description.setLearnMoreVisible(false)
binding.laterButton.setText(R.string.SmsExportMegaphoneActivity__learn_more)
binding.laterButton.setOnClickListener {
CommunicationActions.openBrowserLink(this, getString(R.string.sms_export_url))
}
} else {
binding.headline.setText(R.string.SmsExportMegaphoneActivity__signal_will_no_longer_support_sms)
binding.description.setText(R.string.SmsExportMegaphoneActivity__signal_will_soon_remove_support_for_sending_sms_messages)
binding.description.setLearnMoreVisible(true)
binding.description.setLink(getString(R.string.sms_export_url))
binding.laterButton.setText(R.string.SmsExportMegaphoneActivity__remind_me_later)
binding.laterButton.setOnClickListener {
onBackPressed()
}
}
binding.exportButton.setOnClickListener {
smsExportLauncher.launch(SmsExportActivity.createIntent(this))
}
}
override fun onBackPressed() {
ApplicationDependencies.getMegaphoneRepository().markSeen(Megaphones.Event.SMS_EXPORT)
setResult(Activity.RESULT_CANCELED)
super.onBackPressed()
}
override fun onResume() {
super.onResume()
theme.onResume(this)
}
}

View File

@@ -0,0 +1,39 @@
package org.thoughtcrime.securesms.megaphone
import android.content.Context
import androidx.annotation.WorkerThread
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.mmsSms
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.keyvalue.SmsExportPhase
import org.thoughtcrime.securesms.util.FeatureFlags
import org.thoughtcrime.securesms.util.Util
import kotlin.time.Duration.Companion.days
class SmsExportReminderSchedule(private val context: Context) : MegaphoneSchedule {
companion object {
@JvmStatic
var showPhase3Megaphone = true
}
private val basicMegaphoneSchedule = RecurringSchedule(3.days.inWholeMilliseconds)
private val fullScreenSchedule = RecurringSchedule(1.days.inWholeMilliseconds)
@WorkerThread
override fun shouldDisplay(seenCount: Int, lastSeen: Long, firstVisible: Long, currentTime: Long): Boolean {
return if (shouldShowMegaphone()) {
when (SignalStore.misc().smsExportPhase) {
SmsExportPhase.PHASE_1 -> basicMegaphoneSchedule.shouldDisplay(seenCount, lastSeen, firstVisible, currentTime)
SmsExportPhase.PHASE_2 -> fullScreenSchedule.shouldDisplay(seenCount, lastSeen, firstVisible, currentTime)
SmsExportPhase.PHASE_3 -> showPhase3Megaphone
}
} else {
false
}
}
@WorkerThread
fun shouldShowMegaphone(): Boolean {
return FeatureFlags.smsExporter() && (Util.isDefaultSmsProvider(context) || mmsSms.unexportedInsecureMessagesCount > 0)
}
}