mirror of
https://github.com/oxen-io/session-android.git
synced 2025-06-09 12:38:33 +00:00
Use megaphones for PIN reminders.
This commit is contained in:
parent
38e4733433
commit
ddc01b539f
@ -18,6 +18,7 @@ package org.thoughtcrime.securesms.conversationlist;
|
|||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
|
import android.app.Activity;
|
||||||
import android.app.ProgressDialog;
|
import android.app.ProgressDialog;
|
||||||
import android.content.ActivityNotFoundException;
|
import android.content.ActivityNotFoundException;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@ -107,7 +108,7 @@ import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity;
|
|||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.mediasend.MediaSendActivity;
|
import org.thoughtcrime.securesms.mediasend.MediaSendActivity;
|
||||||
import org.thoughtcrime.securesms.megaphone.Megaphone;
|
import org.thoughtcrime.securesms.megaphone.Megaphone;
|
||||||
import org.thoughtcrime.securesms.megaphone.MegaphoneListener;
|
import org.thoughtcrime.securesms.megaphone.MegaphoneActionController;
|
||||||
import org.thoughtcrime.securesms.megaphone.MegaphoneViewBuilder;
|
import org.thoughtcrime.securesms.megaphone.MegaphoneViewBuilder;
|
||||||
import org.thoughtcrime.securesms.megaphone.Megaphones;
|
import org.thoughtcrime.securesms.megaphone.Megaphones;
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||||
@ -138,7 +139,7 @@ public class ConversationListFragment extends MainFragment implements LoaderMana
|
|||||||
ItemClickListener,
|
ItemClickListener,
|
||||||
ConversationListSearchAdapter.EventListener,
|
ConversationListSearchAdapter.EventListener,
|
||||||
MainNavigator.BackHandler,
|
MainNavigator.BackHandler,
|
||||||
MegaphoneListener
|
MegaphoneActionController
|
||||||
{
|
{
|
||||||
private static final String TAG = Log.tag(ConversationListFragment.class);
|
private static final String TAG = Log.tag(ConversationListFragment.class);
|
||||||
|
|
||||||
@ -367,13 +368,18 @@ public class ConversationListFragment extends MainFragment implements LoaderMana
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMegaphoneSnooze(@NonNull Megaphone megaphone) {
|
public @NonNull Activity getMegaphoneActivity() {
|
||||||
viewModel.onMegaphoneSnoozed(megaphone);
|
return requireActivity();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMegaphoneCompleted(@NonNull Megaphone megaphone) {
|
public void onMegaphoneSnooze(@NonNull Megaphones.Event event) {
|
||||||
viewModel.onMegaphoneCompleted(megaphone.getEvent());
|
viewModel.onMegaphoneSnoozed(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMegaphoneCompleted(@NonNull Megaphones.Event event) {
|
||||||
|
viewModel.onMegaphoneCompleted(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeProfileIcon(@NonNull Recipient recipient) {
|
private void initializeProfileIcon(@NonNull Recipient recipient) {
|
||||||
@ -713,7 +719,7 @@ public class ConversationListFragment extends MainFragment implements LoaderMana
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onItemLongClick(ConversationListItem item) {
|
public void onItemLongClick(ConversationListItem item) {
|
||||||
actionMode = ((AppCompatActivity)getActivity()).startSupportActionMode(ConversationListFragment.this);
|
actionMode = ((AppCompatActivity) getActivity()).startSupportActionMode(ConversationListFragment.this);
|
||||||
|
|
||||||
defaultAdapter.initializeBatchMode(true);
|
defaultAdapter.initializeBatchMode(true);
|
||||||
defaultAdapter.toggleThreadInBatchSet(item.getThreadId());
|
defaultAdapter.toggleThreadInBatchSet(item.getThreadId());
|
||||||
|
@ -69,8 +69,8 @@ class ConversationListViewModel extends ViewModel {
|
|||||||
megaphoneRepository.markFinished(event);
|
megaphoneRepository.markFinished(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
void onMegaphoneSnoozed(@NonNull Megaphone snoozed) {
|
void onMegaphoneSnoozed(@NonNull Megaphones.Event event) {
|
||||||
megaphoneRepository.markSeen(snoozed.getEvent());
|
megaphoneRepository.markSeen(event);
|
||||||
megaphone.postValue(null);
|
megaphone.postValue(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,57 @@
|
|||||||
|
package org.thoughtcrime.securesms.keyvalue;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
|
import org.thoughtcrime.securesms.lock.SignalPinReminders;
|
||||||
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
|
|
||||||
|
public final class PinValues {
|
||||||
|
|
||||||
|
private static final String TAG = Log.tag(PinValues.class);
|
||||||
|
|
||||||
|
private static final String LAST_SUCCESSFUL_ENTRY = "pin.last_successful_entry";
|
||||||
|
private static final String NEXT_INTERVAL = "pin.interval_index";
|
||||||
|
|
||||||
|
private final KeyValueStore store;
|
||||||
|
|
||||||
|
PinValues(KeyValueStore store) {
|
||||||
|
this.store = store;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onEntrySuccess() {
|
||||||
|
long nextInterval = SignalPinReminders.getNextInterval(getCurrentInterval());
|
||||||
|
Log.w(TAG, "onEntrySuccess() nextInterval: " + nextInterval);
|
||||||
|
|
||||||
|
store.beginWrite()
|
||||||
|
.putLong(LAST_SUCCESSFUL_ENTRY, System.currentTimeMillis())
|
||||||
|
.putLong(NEXT_INTERVAL, nextInterval)
|
||||||
|
.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onEntryFailure() {
|
||||||
|
long nextInterval = SignalPinReminders.getPreviousInterval(getCurrentInterval());
|
||||||
|
Log.w(TAG, "onEntryFailure() nextInterval: " + nextInterval);
|
||||||
|
|
||||||
|
store.beginWrite()
|
||||||
|
.putLong(NEXT_INTERVAL, nextInterval)
|
||||||
|
.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onPinChange() {
|
||||||
|
long nextInterval = SignalPinReminders.INITIAL_INTERVAL;
|
||||||
|
Log.w(TAG, "onPinChange() nextInterval: " + nextInterval);
|
||||||
|
|
||||||
|
store.beginWrite()
|
||||||
|
.putLong(NEXT_INTERVAL, nextInterval)
|
||||||
|
.putLong(LAST_SUCCESSFUL_ENTRY, System.currentTimeMillis())
|
||||||
|
.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getCurrentInterval() {
|
||||||
|
return store.getLong(NEXT_INTERVAL, TextSecurePreferences.getRegistrationLockNextReminderInterval(ApplicationDependencies.getApplication()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getLastSuccessfulEntryTime() {
|
||||||
|
return store.getLong(LAST_SUCCESSFUL_ENTRY, TextSecurePreferences.getRegistrationLockLastReminderTime(ApplicationDependencies.getApplication()));
|
||||||
|
}
|
||||||
|
}
|
@ -17,14 +17,18 @@ public final class SignalStore {
|
|||||||
|
|
||||||
private SignalStore() {}
|
private SignalStore() {}
|
||||||
|
|
||||||
public static KbsValues kbsValues() {
|
public static @NonNull KbsValues kbsValues() {
|
||||||
return new KbsValues(getStore());
|
return new KbsValues(getStore());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RegistrationValues registrationValues() {
|
public static @NonNull RegistrationValues registrationValues() {
|
||||||
return new RegistrationValues(getStore());
|
return new RegistrationValues(getStore());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static @NonNull PinValues pinValues() {
|
||||||
|
return new PinValues(getStore());
|
||||||
|
}
|
||||||
|
|
||||||
public static String getRemoteConfig() {
|
public static String getRemoteConfig() {
|
||||||
return getStore().getString(REMOTE_CONFIG, null);
|
return getStore().getString(REMOTE_CONFIG, null);
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@ import android.graphics.Typeface;
|
|||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.text.InputType;
|
|
||||||
import android.text.SpannableString;
|
import android.text.SpannableString;
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
@ -27,19 +26,15 @@ import android.widget.TextView;
|
|||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.core.app.DialogCompat;
|
import androidx.core.app.DialogCompat;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
import com.google.android.material.textfield.TextInputLayout;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.components.SwitchPreferenceCompat;
|
import org.thoughtcrime.securesms.components.SwitchPreferenceCompat;
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.keyvalue.KbsValues;
|
import org.thoughtcrime.securesms.keyvalue.KbsValues;
|
||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity;
|
|
||||||
import org.thoughtcrime.securesms.lock.v2.KbsConstants;
|
import org.thoughtcrime.securesms.lock.v2.KbsConstants;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.migrations.RegistrationPinV2MigrationJob;
|
import org.thoughtcrime.securesms.migrations.RegistrationPinV2MigrationJob;
|
||||||
@ -66,98 +61,25 @@ public final class RegistrationLockDialog {
|
|||||||
public static void showReminderIfNecessary(@NonNull Fragment fragment) {
|
public static void showReminderIfNecessary(@NonNull Fragment fragment) {
|
||||||
final Context context = fragment.requireContext();
|
final Context context = fragment.requireContext();
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return;
|
if (!TextSecurePreferences.isV1RegistrationLockEnabled(context) && !SignalStore.kbsValues().isV2RegistrationLockEnabled()) {
|
||||||
if (!RegistrationLockReminders.needsReminder(context)) return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!TextSecurePreferences.isV1RegistrationLockEnabled(context) &&
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||||
!SignalStore.kbsValues().isV2RegistrationLockEnabled()) {
|
return;
|
||||||
// Neither v1 or v2 to check against
|
}
|
||||||
Log.w(TAG, "Reg lock enabled, but no pin stored to verify against");
|
|
||||||
|
if (!RegistrationLockReminders.needsReminder(context)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FeatureFlags.pinsForAll()) {
|
if (FeatureFlags.pinsForAll()) {
|
||||||
showReminder(context, fragment);
|
return;
|
||||||
} else {
|
}
|
||||||
|
|
||||||
showLegacyPinReminder(context);
|
showLegacyPinReminder(context);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private static void showReminder(@NonNull Context context, @NonNull Fragment fragment) {
|
|
||||||
AlertDialog dialog = new AlertDialog.Builder(context, ThemeUtil.isDarkTheme(context) ? R.style.RationaleDialogDark_SignalAccent : R.style.RationaleDialogLight_SignalAccent)
|
|
||||||
.setView(R.layout.kbs_pin_reminder_view)
|
|
||||||
.setCancelable(false)
|
|
||||||
.setOnCancelListener(d -> RegistrationLockReminders.scheduleReminder(context, false))
|
|
||||||
.create();
|
|
||||||
|
|
||||||
WindowManager windowManager = ServiceUtil.getWindowManager(context);
|
|
||||||
Display display = windowManager.getDefaultDisplay();
|
|
||||||
DisplayMetrics metrics = new DisplayMetrics();
|
|
||||||
display.getMetrics(metrics);
|
|
||||||
|
|
||||||
dialog.show();
|
|
||||||
dialog.getWindow().setLayout((int)(metrics.widthPixels * .80), ViewGroup.LayoutParams.WRAP_CONTENT);
|
|
||||||
|
|
||||||
TextInputLayout pinWrapper = (TextInputLayout) DialogCompat.requireViewById(dialog, R.id.pin_wrapper);
|
|
||||||
EditText pinEditText = (EditText) DialogCompat.requireViewById(dialog, R.id.pin);
|
|
||||||
TextView reminder = (TextView) DialogCompat.requireViewById(dialog, R.id.reminder);
|
|
||||||
View skip = DialogCompat.requireViewById(dialog, R.id.skip);
|
|
||||||
View submit = DialogCompat.requireViewById(dialog, R.id.submit);
|
|
||||||
|
|
||||||
SpannableString reminderText = new SpannableString(context.getString(R.string.KbsReminderDialog__to_help_you_memorize_your_pin));
|
|
||||||
SpannableString forgotText = new SpannableString(context.getString(R.string.KbsReminderDialog__forgot_pin));
|
|
||||||
|
|
||||||
pinEditText.post(() -> {
|
|
||||||
if (pinEditText.requestFocus()) {
|
|
||||||
ServiceUtil.getInputMethodManager(pinEditText.getContext()).showSoftInput(pinEditText, 0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
switch (SignalStore.kbsValues().getKeyboardType()) {
|
|
||||||
case NUMERIC:
|
|
||||||
pinEditText.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD);
|
|
||||||
break;
|
|
||||||
case ALPHA_NUMERIC:
|
|
||||||
pinEditText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
ClickableSpan clickableSpan = new ClickableSpan() {
|
|
||||||
@Override
|
|
||||||
public void onClick(@NonNull View widget) {
|
|
||||||
dialog.dismiss();
|
|
||||||
RegistrationLockReminders.scheduleReminder(context, true);
|
|
||||||
|
|
||||||
fragment.startActivityForResult(CreateKbsPinActivity.getIntentForPinChangeFromForgotPin(context), CreateKbsPinActivity.REQUEST_NEW_PIN);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
forgotText.setSpan(clickableSpan, 0, forgotText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
||||||
|
|
||||||
reminder.setText(new SpannableStringBuilder(reminderText).append(" ").append(forgotText));
|
|
||||||
reminder.setMovementMethod(LinkMovementMethod.getInstance());
|
|
||||||
|
|
||||||
skip.setOnClickListener(v -> {
|
|
||||||
dialog.dismiss();
|
|
||||||
RegistrationLockReminders.scheduleReminder(context, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
PinVerifier.Callback callback = getPinWatcherCallback(context, dialog, pinWrapper);
|
|
||||||
PinVerifier verifier = SignalStore.kbsValues().isV2RegistrationLockEnabled()
|
|
||||||
? new V2PinVerifier()
|
|
||||||
: new V1PinVerifier(context);
|
|
||||||
|
|
||||||
submit.setOnClickListener(v -> {
|
|
||||||
Editable pinEditable = pinEditText.getText();
|
|
||||||
|
|
||||||
verifier.verifyPin(pinEditable == null ? null : pinEditable.toString(), callback);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated TODO [alex]: Remove after pins for all live.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
private static void showLegacyPinReminder(@NonNull Context context) {
|
private static void showLegacyPinReminder(@NonNull Context context) {
|
||||||
AlertDialog dialog = new AlertDialog.Builder(context, ThemeUtil.isDarkTheme(context) ? R.style.RationaleDialogDark : R.style.RationaleDialogLight)
|
AlertDialog dialog = new AlertDialog.Builder(context, ThemeUtil.isDarkTheme(context) ? R.style.RationaleDialogDark : R.style.RationaleDialogLight)
|
||||||
.setView(R.layout.registration_lock_reminder_view)
|
.setView(R.layout.registration_lock_reminder_view)
|
||||||
@ -403,79 +325,4 @@ public final class RegistrationLockDialog {
|
|||||||
|
|
||||||
dialog.show();
|
dialog.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static PinVerifier.Callback getPinWatcherCallback(@NonNull Context context,
|
|
||||||
@NonNull AlertDialog dialog,
|
|
||||||
@NonNull TextInputLayout inputWrapper)
|
|
||||||
{
|
|
||||||
return new PinVerifier.Callback() {
|
|
||||||
@Override
|
|
||||||
public void onPinCorrect() {
|
|
||||||
dialog.dismiss();
|
|
||||||
RegistrationLockReminders.scheduleReminder(context, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPinWrong() {
|
|
||||||
inputWrapper.setError(context.getString(R.string.KbsReminderDialog__incorrect_pin_try_again));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class V1PinVerifier implements PinVerifier {
|
|
||||||
|
|
||||||
private final String pinInPreferences;
|
|
||||||
|
|
||||||
private V1PinVerifier(@NonNull Context context) {
|
|
||||||
//noinspection deprecation Acceptable to check the old pin in a reminder on a non-migrated system.
|
|
||||||
this.pinInPreferences = TextSecurePreferences.getDeprecatedV1RegistrationLockPin(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void verifyPin(@Nullable String pin, @NonNull Callback callback) {
|
|
||||||
if (pin != null && pin.replace(" ", "").equals(pinInPreferences)) {
|
|
||||||
callback.onPinCorrect();
|
|
||||||
|
|
||||||
Log.i(TAG, "Pin V1 successfully remembered, scheduling a migration to V2");
|
|
||||||
ApplicationDependencies.getJobManager().add(new RegistrationPinV2MigrationJob());
|
|
||||||
} else {
|
|
||||||
callback.onPinWrong();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class V2PinVerifier implements PinVerifier {
|
|
||||||
|
|
||||||
private final String localPinHash;
|
|
||||||
|
|
||||||
V2PinVerifier() {
|
|
||||||
localPinHash = SignalStore.kbsValues().getLocalPinHash();
|
|
||||||
|
|
||||||
if (localPinHash == null) throw new AssertionError("No local pin hash set at time of reminder");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void verifyPin(@Nullable String pin, @NonNull Callback callback) {
|
|
||||||
if (pin == null) return;
|
|
||||||
if (TextUtils.isEmpty(pin)) return;
|
|
||||||
|
|
||||||
if (pin.length() < KbsConstants.MINIMUM_POSSIBLE_PIN_LENGTH) return;
|
|
||||||
|
|
||||||
if (PinHashing.verifyLocalPinHash(localPinHash, pin)) {
|
|
||||||
callback.onPinCorrect();
|
|
||||||
} else {
|
|
||||||
callback.onPinWrong();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private interface PinVerifier {
|
|
||||||
|
|
||||||
void verifyPin(@Nullable String pin, @NonNull PinVerifier.Callback callback);
|
|
||||||
|
|
||||||
interface Callback {
|
|
||||||
void onPinCorrect();
|
|
||||||
void onPinWrong();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -25,9 +25,6 @@ public class RegistrationLockReminders {
|
|||||||
public static final long INITIAL_INTERVAL = INTERVALS.first();
|
public static final long INITIAL_INTERVAL = INTERVALS.first();
|
||||||
|
|
||||||
public static boolean needsReminder(@NonNull Context context) {
|
public static boolean needsReminder(@NonNull Context context) {
|
||||||
if (!TextSecurePreferences.isV1RegistrationLockEnabled(context) &&
|
|
||||||
!SignalStore.kbsValues().isV2RegistrationLockEnabled()) return false;
|
|
||||||
|
|
||||||
long lastReminderTime = TextSecurePreferences.getRegistrationLockLastReminderTime(context);
|
long lastReminderTime = TextSecurePreferences.getRegistrationLockLastReminderTime(context);
|
||||||
long nextIntervalTime = TextSecurePreferences.getRegistrationLockNextReminderInterval(context);
|
long nextIntervalTime = TextSecurePreferences.getRegistrationLockNextReminderInterval(context);
|
||||||
|
|
||||||
|
@ -0,0 +1,205 @@
|
|||||||
|
package org.thoughtcrime.securesms.lock;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.text.Editable;
|
||||||
|
import android.text.InputType;
|
||||||
|
import android.text.SpannableString;
|
||||||
|
import android.text.SpannableStringBuilder;
|
||||||
|
import android.text.Spanned;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.text.method.LinkMovementMethod;
|
||||||
|
import android.text.style.ClickableSpan;
|
||||||
|
import android.util.DisplayMetrics;
|
||||||
|
import android.view.Display;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.core.app.DialogCompat;
|
||||||
|
|
||||||
|
import com.google.android.material.textfield.TextInputLayout;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.R;
|
||||||
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
|
import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity;
|
||||||
|
import org.thoughtcrime.securesms.lock.v2.KbsConstants;
|
||||||
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
import org.thoughtcrime.securesms.migrations.RegistrationPinV2MigrationJob;
|
||||||
|
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||||
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
|
import org.thoughtcrime.securesms.util.ThemeUtil;
|
||||||
|
|
||||||
|
public final class SignalPinReminderDialog {
|
||||||
|
|
||||||
|
private static final String TAG = Log.tag(SignalPinReminderDialog.class);
|
||||||
|
|
||||||
|
public static void show(@NonNull Context context, @NonNull Launcher launcher, @NonNull Callback mainCallback) {
|
||||||
|
AlertDialog dialog = new AlertDialog.Builder(context, ThemeUtil.isDarkTheme(context) ? R.style.RationaleDialogDark_SignalAccent : R.style.RationaleDialogLight_SignalAccent)
|
||||||
|
.setView(R.layout.kbs_pin_reminder_view)
|
||||||
|
.setCancelable(false)
|
||||||
|
.setOnCancelListener(d -> RegistrationLockReminders.scheduleReminder(context, false))
|
||||||
|
.create();
|
||||||
|
|
||||||
|
WindowManager windowManager = ServiceUtil.getWindowManager(context);
|
||||||
|
Display display = windowManager.getDefaultDisplay();
|
||||||
|
DisplayMetrics metrics = new DisplayMetrics();
|
||||||
|
display.getMetrics(metrics);
|
||||||
|
|
||||||
|
dialog.show();
|
||||||
|
dialog.getWindow().setLayout((int)(metrics.widthPixels * .80), ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||||
|
|
||||||
|
TextInputLayout pinWrapper = (TextInputLayout) DialogCompat.requireViewById(dialog, R.id.pin_wrapper);
|
||||||
|
EditText pinEditText = (EditText) DialogCompat.requireViewById(dialog, R.id.pin);
|
||||||
|
TextView reminder = (TextView) DialogCompat.requireViewById(dialog, R.id.reminder);
|
||||||
|
View skip = DialogCompat.requireViewById(dialog, R.id.skip);
|
||||||
|
View submit = DialogCompat.requireViewById(dialog, R.id.submit);
|
||||||
|
|
||||||
|
SpannableString reminderText = new SpannableString(context.getString(R.string.KbsReminderDialog__to_help_you_memorize_your_pin));
|
||||||
|
SpannableString forgotText = new SpannableString(context.getString(R.string.KbsReminderDialog__forgot_pin));
|
||||||
|
|
||||||
|
pinEditText.post(() -> {
|
||||||
|
if (pinEditText.requestFocus()) {
|
||||||
|
ServiceUtil.getInputMethodManager(pinEditText.getContext()).showSoftInput(pinEditText, 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
switch (SignalStore.kbsValues().getKeyboardType()) {
|
||||||
|
case NUMERIC:
|
||||||
|
pinEditText.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD);
|
||||||
|
break;
|
||||||
|
case ALPHA_NUMERIC:
|
||||||
|
pinEditText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClickableSpan clickableSpan = new ClickableSpan() {
|
||||||
|
@Override
|
||||||
|
public void onClick(@NonNull View widget) {
|
||||||
|
dialog.dismiss();
|
||||||
|
launcher.launch(CreateKbsPinActivity.getIntentForPinChangeFromForgotPin(context), CreateKbsPinActivity.REQUEST_NEW_PIN);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
forgotText.setSpan(clickableSpan, 0, forgotText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
|
||||||
|
reminder.setText(new SpannableStringBuilder(reminderText).append(" ").append(forgotText));
|
||||||
|
reminder.setMovementMethod(LinkMovementMethod.getInstance());
|
||||||
|
|
||||||
|
PinVerifier.Callback callback = getPinWatcherCallback(context, dialog, pinWrapper, mainCallback);
|
||||||
|
PinVerifier verifier = SignalStore.kbsValues().isV2RegistrationLockEnabled()
|
||||||
|
? new V2PinVerifier()
|
||||||
|
: new V1PinVerifier(context);
|
||||||
|
|
||||||
|
skip.setOnClickListener(v -> {
|
||||||
|
dialog.dismiss();
|
||||||
|
mainCallback.onReminderDismissed(callback.hadWrongGuess());
|
||||||
|
});
|
||||||
|
|
||||||
|
submit.setOnClickListener(v -> {
|
||||||
|
Editable pinEditable = pinEditText.getText();
|
||||||
|
|
||||||
|
verifier.verifyPin(pinEditable == null ? null : pinEditable.toString(), callback);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PinVerifier.Callback getPinWatcherCallback(@NonNull Context context,
|
||||||
|
@NonNull AlertDialog dialog,
|
||||||
|
@NonNull TextInputLayout inputWrapper,
|
||||||
|
@NonNull Callback mainCallback)
|
||||||
|
{
|
||||||
|
return new PinVerifier.Callback() {
|
||||||
|
boolean hadWrongGuess = false;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPinCorrect() {
|
||||||
|
dialog.dismiss();
|
||||||
|
mainCallback.onReminderCompleted(hadWrongGuess);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPinWrong() {
|
||||||
|
hadWrongGuess = true;
|
||||||
|
inputWrapper.setError(context.getString(R.string.KbsReminderDialog__incorrect_pin_try_again));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hadWrongGuess() {
|
||||||
|
return hadWrongGuess;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class V1PinVerifier implements PinVerifier {
|
||||||
|
|
||||||
|
private final String pinInPreferences;
|
||||||
|
|
||||||
|
private V1PinVerifier(@NonNull Context context) {
|
||||||
|
//noinspection deprecation Acceptable to check the old pin in a reminder on a non-migrated system.
|
||||||
|
this.pinInPreferences = TextSecurePreferences.getDeprecatedV1RegistrationLockPin(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void verifyPin(@Nullable String pin, @NonNull Callback callback) {
|
||||||
|
if (pin != null && pin.replace(" ", "").equals(pinInPreferences)) {
|
||||||
|
callback.onPinCorrect();
|
||||||
|
|
||||||
|
Log.i(TAG, "Pin V1 successfully remembered, scheduling a migration to V2");
|
||||||
|
ApplicationDependencies.getJobManager().add(new RegistrationPinV2MigrationJob());
|
||||||
|
} else {
|
||||||
|
callback.onPinWrong();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class V2PinVerifier implements PinVerifier {
|
||||||
|
|
||||||
|
private final String localPinHash;
|
||||||
|
|
||||||
|
V2PinVerifier() {
|
||||||
|
localPinHash = SignalStore.kbsValues().getLocalPinHash();
|
||||||
|
|
||||||
|
if (localPinHash == null) throw new AssertionError("No local pin hash set at time of reminder");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void verifyPin(@Nullable String pin, @NonNull Callback callback) {
|
||||||
|
if (pin == null) return;
|
||||||
|
if (TextUtils.isEmpty(pin)) return;
|
||||||
|
|
||||||
|
if (pin.length() < KbsConstants.MINIMUM_POSSIBLE_PIN_LENGTH) return;
|
||||||
|
|
||||||
|
if (PinHashing.verifyLocalPinHash(localPinHash, pin)) {
|
||||||
|
callback.onPinCorrect();
|
||||||
|
} else {
|
||||||
|
callback.onPinWrong();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface PinVerifier {
|
||||||
|
|
||||||
|
void verifyPin(@Nullable String pin, @NonNull PinVerifier.Callback callback);
|
||||||
|
|
||||||
|
interface Callback {
|
||||||
|
void onPinCorrect();
|
||||||
|
void onPinWrong();
|
||||||
|
boolean hadWrongGuess();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Launcher {
|
||||||
|
void launch(@NonNull Intent intent, int requestCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Callback {
|
||||||
|
void onReminderDismissed(boolean includedFailure);
|
||||||
|
void onReminderCompleted(boolean includedFailure);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,62 @@
|
|||||||
|
package org.thoughtcrime.securesms.lock;
|
||||||
|
|
||||||
|
import androidx.annotation.StringRes;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.R;
|
||||||
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.NavigableSet;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reminder intervals for Signal PINs.
|
||||||
|
*/
|
||||||
|
public class SignalPinReminders {
|
||||||
|
|
||||||
|
private static final String TAG = Log.tag(SignalPinReminders.class);
|
||||||
|
|
||||||
|
private static final long ONE_DAY = TimeUnit.DAYS.toMillis(1);
|
||||||
|
private static final long THREE_DAYS = TimeUnit.DAYS.toMillis(3);
|
||||||
|
private static final long ONE_WEEK = TimeUnit.DAYS.toMillis(7);
|
||||||
|
private static final long TWO_WEEKS = TimeUnit.DAYS.toMillis(14);
|
||||||
|
|
||||||
|
private static final NavigableSet<Long> INTERVALS = new TreeSet<Long>() {{
|
||||||
|
add(ONE_DAY);
|
||||||
|
add(THREE_DAYS);
|
||||||
|
add(ONE_WEEK);
|
||||||
|
add(TWO_WEEKS);
|
||||||
|
}};
|
||||||
|
|
||||||
|
private static final Map<Long, Integer> STRINGS = new HashMap<Long, Integer>() {{
|
||||||
|
put(ONE_DAY, R.string.SignalPinReminders_well_remind_you_again_tomorrow);
|
||||||
|
put(THREE_DAYS, R.string.SignalPinReminders_well_remind_you_again_in_a_few_days);
|
||||||
|
put(ONE_WEEK, R.string.SignalPinReminders_well_remind_you_again_in_a_week);
|
||||||
|
put(TWO_WEEKS, R.string.SignalPinReminders_well_remind_you_again_in_a_couple_weeks);
|
||||||
|
}};
|
||||||
|
|
||||||
|
public static final long INITIAL_INTERVAL = INTERVALS.first();
|
||||||
|
|
||||||
|
public static long getNextInterval(long currentInterval) {
|
||||||
|
Long next = INTERVALS.higher(currentInterval);
|
||||||
|
return next != null ? next : INTERVALS.last();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long getPreviousInterval(long currentInterval) {
|
||||||
|
Long previous = INTERVALS.lower(currentInterval);
|
||||||
|
return previous != null ? previous : INTERVALS.first();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static @StringRes int getReminderString(long interval) {
|
||||||
|
Integer stringRes = STRINGS.get(interval);
|
||||||
|
|
||||||
|
if (stringRes != null) {
|
||||||
|
return stringRes;
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "Couldn't find a string for interval " + interval);
|
||||||
|
return R.string.SignalPinReminders_well_remind_you_again_later;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -108,6 +108,7 @@ public class ConfirmKbsPinFragment extends BaseKbsPinFragment<ConfirmKbsPinViewM
|
|||||||
requireActivity().setResult(Activity.RESULT_OK);
|
requireActivity().setResult(Activity.RESULT_OK);
|
||||||
closeNavGraphBranch();
|
closeNavGraphBranch();
|
||||||
SignalStore.registrationValues().setRegistrationComplete();
|
SignalStore.registrationValues().setRegistrationComplete();
|
||||||
|
SignalStore.pinValues().onPinChange();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
@ -21,7 +21,7 @@ public class BasicMegaphoneView extends FrameLayout {
|
|||||||
private Button snoozeButton;
|
private Button snoozeButton;
|
||||||
|
|
||||||
private Megaphone megaphone;
|
private Megaphone megaphone;
|
||||||
private MegaphoneListener megaphoneListener;
|
private MegaphoneActionController megaphoneListener;
|
||||||
|
|
||||||
public BasicMegaphoneView(@NonNull Context context) {
|
public BasicMegaphoneView(@NonNull Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
@ -52,7 +52,7 @@ public class BasicMegaphoneView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void present(@NonNull Megaphone megaphone, @NonNull MegaphoneListener megaphoneListener) {
|
public void present(@NonNull Megaphone megaphone, @NonNull MegaphoneActionController megaphoneListener) {
|
||||||
this.megaphone = megaphone;
|
this.megaphone = megaphone;
|
||||||
this.megaphoneListener = megaphoneListener;
|
this.megaphoneListener = megaphoneListener;
|
||||||
|
|
||||||
@ -92,7 +92,7 @@ public class BasicMegaphoneView extends FrameLayout {
|
|||||||
if (megaphone.canSnooze()) {
|
if (megaphone.canSnooze()) {
|
||||||
snoozeButton.setVisibility(VISIBLE);
|
snoozeButton.setVisibility(VISIBLE);
|
||||||
snoozeButton.setOnClickListener(v -> {
|
snoozeButton.setOnClickListener(v -> {
|
||||||
megaphoneListener.onMegaphoneSnooze(megaphone);
|
megaphoneListener.onMegaphoneSnooze(megaphone.getEvent());
|
||||||
|
|
||||||
if (megaphone.getSnoozeListener() != null) {
|
if (megaphone.getSnoozeListener() != null) {
|
||||||
megaphone.getSnoozeListener().onEvent(megaphone, megaphoneListener);
|
megaphone.getSnoozeListener().onEvent(megaphone, megaphoneListener);
|
||||||
|
@ -139,7 +139,7 @@ public class Megaphone {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public @NonNull Builder setButtonText(@StringRes int buttonTextRes, @NonNull EventListener listener) {
|
public @NonNull Builder setActionButton(@StringRes int buttonTextRes, @NonNull EventListener listener) {
|
||||||
this.buttonTextRes = buttonTextRes;
|
this.buttonTextRes = buttonTextRes;
|
||||||
this.buttonListener = listener;
|
this.buttonListener = listener;
|
||||||
return this;
|
return this;
|
||||||
@ -160,6 +160,6 @@ public class Megaphone {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public interface EventListener {
|
public interface EventListener {
|
||||||
void onEvent(@NonNull Megaphone megaphone, @NonNull MegaphoneListener listener);
|
void onEvent(@NonNull Megaphone megaphone, @NonNull MegaphoneActionController listener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
package org.thoughtcrime.securesms.megaphone;
|
package org.thoughtcrime.securesms.megaphone;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.StringRes;
|
import androidx.annotation.StringRes;
|
||||||
|
|
||||||
public interface MegaphoneListener {
|
public interface MegaphoneActionController {
|
||||||
/**
|
/**
|
||||||
* When a megaphone wants to navigate to a specific intent.
|
* When a megaphone wants to navigate to a specific intent.
|
||||||
*/
|
*/
|
||||||
@ -21,13 +22,18 @@ public interface MegaphoneListener {
|
|||||||
*/
|
*/
|
||||||
void onMegaphoneToastRequested(@NonNull String string);
|
void onMegaphoneToastRequested(@NonNull String string);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When a megaphone needs a raw activity reference. Favor more specific methods when possible.
|
||||||
|
*/
|
||||||
|
@NonNull Activity getMegaphoneActivity();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When a megaphone has been snoozed via "remind me later" or a similar option.
|
* When a megaphone has been snoozed via "remind me later" or a similar option.
|
||||||
*/
|
*/
|
||||||
void onMegaphoneSnooze(@NonNull Megaphone megaphone);
|
void onMegaphoneSnooze(@NonNull Megaphones.Event event);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a megaphone completed its goal.
|
* Called when a megaphone completed its goal.
|
||||||
*/
|
*/
|
||||||
void onMegaphoneCompleted(@NonNull Megaphone megaphone);
|
void onMegaphoneCompleted(@NonNull Megaphones.Event event);
|
||||||
}
|
}
|
@ -12,7 +12,7 @@ public class MegaphoneViewBuilder {
|
|||||||
|
|
||||||
public static @Nullable View build(@NonNull Context context,
|
public static @Nullable View build(@NonNull Context context,
|
||||||
@NonNull Megaphone megaphone,
|
@NonNull Megaphone megaphone,
|
||||||
@NonNull MegaphoneListener listener)
|
@NonNull MegaphoneActionController listener)
|
||||||
{
|
{
|
||||||
switch (megaphone.getStyle()) {
|
switch (megaphone.getStyle()) {
|
||||||
case BASIC:
|
case BASIC:
|
||||||
@ -28,7 +28,7 @@ public class MegaphoneViewBuilder {
|
|||||||
|
|
||||||
private static @NonNull View buildBasicMegaphone(@NonNull Context context,
|
private static @NonNull View buildBasicMegaphone(@NonNull Context context,
|
||||||
@NonNull Megaphone megaphone,
|
@NonNull Megaphone megaphone,
|
||||||
@NonNull MegaphoneListener listener)
|
@NonNull MegaphoneActionController listener)
|
||||||
{
|
{
|
||||||
BasicMegaphoneView view = new BasicMegaphoneView(context);
|
BasicMegaphoneView view = new BasicMegaphoneView(context);
|
||||||
view.present(megaphone, listener);
|
view.present(megaphone, listener);
|
||||||
@ -37,7 +37,7 @@ public class MegaphoneViewBuilder {
|
|||||||
|
|
||||||
private static @NonNull View buildReactionsMegaphone(@NonNull Context context,
|
private static @NonNull View buildReactionsMegaphone(@NonNull Context context,
|
||||||
@NonNull Megaphone megaphone,
|
@NonNull Megaphone megaphone,
|
||||||
@NonNull MegaphoneListener listener)
|
@NonNull MegaphoneActionController listener)
|
||||||
{
|
{
|
||||||
ReactionsMegaphoneView view = new ReactionsMegaphoneView(context);
|
ReactionsMegaphoneView view = new ReactionsMegaphoneView(context);
|
||||||
view.present(megaphone, listener);
|
view.present(megaphone, listener);
|
||||||
|
@ -12,6 +12,11 @@ import org.thoughtcrime.securesms.R;
|
|||||||
import org.thoughtcrime.securesms.database.model.MegaphoneRecord;
|
import org.thoughtcrime.securesms.database.model.MegaphoneRecord;
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
|
import org.thoughtcrime.securesms.lock.RegistrationLockDialog;
|
||||||
|
import org.thoughtcrime.securesms.lock.RegistrationLockReminders;
|
||||||
|
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.CreateKbsPinActivity;
|
||||||
import org.thoughtcrime.securesms.lock.v2.KbsMigrationActivity;
|
import org.thoughtcrime.securesms.lock.v2.KbsMigrationActivity;
|
||||||
import org.thoughtcrime.securesms.lock.v2.PinUtil;
|
import org.thoughtcrime.securesms.lock.v2.PinUtil;
|
||||||
@ -76,6 +81,7 @@ public final class Megaphones {
|
|||||||
return new LinkedHashMap<Event, MegaphoneSchedule>() {{
|
return new LinkedHashMap<Event, MegaphoneSchedule>() {{
|
||||||
put(Event.REACTIONS, new ForeverSchedule(true));
|
put(Event.REACTIONS, new ForeverSchedule(true));
|
||||||
put(Event.PINS_FOR_ALL, new PinsForAllSchedule());
|
put(Event.PINS_FOR_ALL, new PinsForAllSchedule());
|
||||||
|
put(Event.PIN_REMINDER, new PinReminderSchedule());
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,7 +90,9 @@ public final class Megaphones {
|
|||||||
case REACTIONS:
|
case REACTIONS:
|
||||||
return buildReactionsMegaphone();
|
return buildReactionsMegaphone();
|
||||||
case PINS_FOR_ALL:
|
case PINS_FOR_ALL:
|
||||||
return buildPinsForAllMegaphone(context, record);
|
return buildPinsForAllMegaphone(record);
|
||||||
|
case PIN_REMINDER:
|
||||||
|
return buildPinReminderMegaphone(context);
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Event not handled!");
|
throw new IllegalArgumentException("Event not handled!");
|
||||||
}
|
}
|
||||||
@ -96,7 +104,7 @@ public final class Megaphones {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static @NonNull Megaphone buildPinsForAllMegaphone(@NonNull Context context, @NonNull MegaphoneRecord record) {
|
private static @NonNull Megaphone buildPinsForAllMegaphone(@NonNull MegaphoneRecord record) {
|
||||||
if (PinsForAllSchedule.shouldDisplayFullScreen(record.getFirstVisible(), System.currentTimeMillis())) {
|
if (PinsForAllSchedule.shouldDisplayFullScreen(record.getFirstVisible(), System.currentTimeMillis())) {
|
||||||
return new Megaphone.Builder(Event.PINS_FOR_ALL, Megaphone.Style.FULLSCREEN)
|
return new Megaphone.Builder(Event.PINS_FOR_ALL, Megaphone.Style.FULLSCREEN)
|
||||||
.setMandatory(true)
|
.setMandatory(true)
|
||||||
@ -125,7 +133,7 @@ public final class Megaphones {
|
|||||||
private static @NonNull Megaphone buildPinsForAllMegaphoneForUserWithPin(@NonNull Megaphone.Builder builder) {
|
private static @NonNull Megaphone buildPinsForAllMegaphoneForUserWithPin(@NonNull Megaphone.Builder builder) {
|
||||||
return builder.setTitle(R.string.KbsMegaphone__introducing_pins)
|
return builder.setTitle(R.string.KbsMegaphone__introducing_pins)
|
||||||
.setBody(R.string.KbsMegaphone__your_registration_lock_is_now_called_a_pin)
|
.setBody(R.string.KbsMegaphone__your_registration_lock_is_now_called_a_pin)
|
||||||
.setButtonText(R.string.KbsMegaphone__update_pin, (megaphone, listener) -> {
|
.setActionButton(R.string.KbsMegaphone__update_pin, (megaphone, listener) -> {
|
||||||
Intent intent = CreateKbsPinActivity.getIntentForPinChangeFromSettings(ApplicationDependencies.getApplication());
|
Intent intent = CreateKbsPinActivity.getIntentForPinChangeFromSettings(ApplicationDependencies.getApplication());
|
||||||
|
|
||||||
listener.onMegaphoneNavigationRequested(intent, CreateKbsPinActivity.REQUEST_NEW_PIN);
|
listener.onMegaphoneNavigationRequested(intent, CreateKbsPinActivity.REQUEST_NEW_PIN);
|
||||||
@ -136,7 +144,7 @@ public final class Megaphones {
|
|||||||
private static @NonNull Megaphone buildPinsForAllMegaphoneForUserWithoutPin(@NonNull Megaphone.Builder builder) {
|
private static @NonNull Megaphone buildPinsForAllMegaphoneForUserWithoutPin(@NonNull Megaphone.Builder builder) {
|
||||||
return builder.setTitle(R.string.KbsMegaphone__create_a_pin)
|
return builder.setTitle(R.string.KbsMegaphone__create_a_pin)
|
||||||
.setBody(R.string.KbsMegaphone__pins_add_another_layer_of_security_to_your_signal_account)
|
.setBody(R.string.KbsMegaphone__pins_add_another_layer_of_security_to_your_signal_account)
|
||||||
.setButtonText(R.string.KbsMegaphone__create_pin, (megaphone, listener) -> {
|
.setActionButton(R.string.KbsMegaphone__create_pin, (megaphone, listener) -> {
|
||||||
Intent intent = CreateKbsPinActivity.getIntentForPinCreate(ApplicationDependencies.getApplication());
|
Intent intent = CreateKbsPinActivity.getIntentForPinCreate(ApplicationDependencies.getApplication());
|
||||||
|
|
||||||
listener.onMegaphoneNavigationRequested(intent, CreateKbsPinActivity.REQUEST_NEW_PIN);
|
listener.onMegaphoneNavigationRequested(intent, CreateKbsPinActivity.REQUEST_NEW_PIN);
|
||||||
@ -144,9 +152,40 @@ public final class Megaphones {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static @NonNull Megaphone buildPinReminderMegaphone(@NonNull Context context) {
|
||||||
|
return new Megaphone.Builder(Event.PIN_REMINDER, Megaphone.Style.BASIC)
|
||||||
|
.setTitle(R.string.Megaphones_verify_your_signal_pin)
|
||||||
|
.setBody(R.string.Megaphones_well_occasionally_ask_you_to_verify_your_pin)
|
||||||
|
.setImage(R.drawable.kbs_pin_megaphone)
|
||||||
|
.setActionButton(R.string.Megaphones_verify_pin, (megaphone, controller) -> {
|
||||||
|
SignalPinReminderDialog.show(controller.getMegaphoneActivity(), controller::onMegaphoneNavigationRequested, new SignalPinReminderDialog.Callback() {
|
||||||
|
@Override
|
||||||
|
public void onReminderDismissed(boolean includedFailure) {
|
||||||
|
if (includedFailure) {
|
||||||
|
SignalStore.pinValues().onEntryFailure();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReminderCompleted(boolean includedFailure) {
|
||||||
|
if (includedFailure) {
|
||||||
|
SignalStore.pinValues().onEntryFailure();
|
||||||
|
} else {
|
||||||
|
SignalStore.pinValues().onEntrySuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
controller.onMegaphoneSnooze(Event.PIN_REMINDER);
|
||||||
|
controller.onMegaphoneToastRequested(context.getString(SignalPinReminders.getReminderString(SignalStore.pinValues().getCurrentInterval())));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
public enum Event {
|
public enum Event {
|
||||||
REACTIONS("reactions"),
|
REACTIONS("reactions"),
|
||||||
PINS_FOR_ALL("pins_for_all");
|
PINS_FOR_ALL("pins_for_all"),
|
||||||
|
PIN_REMINDER("pin_reminder");
|
||||||
|
|
||||||
private final String key;
|
private final String key;
|
||||||
|
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
package org.thoughtcrime.securesms.megaphone;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
|
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||||
|
|
||||||
|
final class PinReminderSchedule implements MegaphoneSchedule {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldDisplay(int seenCount, long lastSeen, long firstVisible, long currentTime) {
|
||||||
|
if (!SignalStore.kbsValues().isV2RegistrationLockEnabled()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!FeatureFlags.pinsForAll()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
long lastSuccessTime = SignalStore.pinValues().getLastSuccessfulEntryTime();
|
||||||
|
long interval = SignalStore.pinValues().getCurrentInterval();
|
||||||
|
|
||||||
|
return currentTime - lastSuccessTime >= interval;
|
||||||
|
}
|
||||||
|
}
|
@ -9,7 +9,7 @@ import androidx.annotation.NonNull;
|
|||||||
|
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.megaphone.Megaphone;
|
import org.thoughtcrime.securesms.megaphone.Megaphone;
|
||||||
import org.thoughtcrime.securesms.megaphone.MegaphoneListener;
|
import org.thoughtcrime.securesms.megaphone.MegaphoneActionController;
|
||||||
|
|
||||||
public class ReactionsMegaphoneView extends FrameLayout {
|
public class ReactionsMegaphoneView extends FrameLayout {
|
||||||
|
|
||||||
@ -31,7 +31,7 @@ public class ReactionsMegaphoneView extends FrameLayout {
|
|||||||
this.closeButton = findViewById(R.id.reactions_megaphone_x);
|
this.closeButton = findViewById(R.id.reactions_megaphone_x);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void present(@NonNull Megaphone megaphone, @NonNull MegaphoneListener listener) {
|
public void present(@NonNull Megaphone megaphone, @NonNull MegaphoneActionController listener) {
|
||||||
this.closeButton.setOnClickListener(v -> listener.onMegaphoneCompleted(megaphone));
|
this.closeButton.setOnClickListener(v -> listener.onMegaphoneCompleted(megaphone.getEvent()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,8 @@
|
|||||||
android:paddingStart="8dp"
|
android:paddingStart="8dp"
|
||||||
android:paddingEnd="8dp"
|
android:paddingEnd="8dp"
|
||||||
android:paddingBottom="8dp"
|
android:paddingBottom="8dp"
|
||||||
android:background="?megaphone_background">
|
android:background="?megaphone_background"
|
||||||
|
android:clickable="true">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/basic_megaphone_image"
|
android:id="@+id/basic_megaphone_image"
|
||||||
|
@ -1,36 +1,34 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical">
|
android:padding="20dp">
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/header_container"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="@color/signal_primary"
|
|
||||||
android:gravity="center"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:padding="40dp">
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:id="@+id/pin_reminder_title"
|
||||||
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center_horizontal"
|
android:gravity="center"
|
||||||
android:text="@string/KbsReminderDialog__enter_your_signal_pin"
|
android:text="@string/KbsReminderDialog__enter_your_signal_pin"
|
||||||
android:textColor="@color/white"
|
style="@style/Signal.Text.Body"
|
||||||
android:textSize="18sp" />
|
android:fontFamily="sans-serif-medium"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
</LinearLayout>
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent" />
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/pin_wrapper"
|
android:id="@+id/pin_wrapper"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingStart="80dp"
|
android:paddingStart="80dp"
|
||||||
android:paddingTop="40dp"
|
android:paddingTop="40dp"
|
||||||
android:paddingEnd="80dp">
|
android:paddingEnd="80dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/pin_reminder_title"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent">
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
android:id="@+id/pin"
|
android:id="@+id/pin"
|
||||||
@ -43,37 +41,33 @@
|
|||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/reminder"
|
android:id="@+id/reminder"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:lineSpacingMultiplier="1.3"
|
android:lineSpacingMultiplier="1.3"
|
||||||
android:paddingStart="20dp"
|
|
||||||
android:paddingTop="40dp"
|
android:paddingTop="40dp"
|
||||||
android:paddingEnd="20dp"
|
|
||||||
android:paddingBottom="40dp"
|
android:paddingBottom="40dp"
|
||||||
android:textSize="15sp"
|
style="@style/Signal.Text.Preview"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/pin_wrapper"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
tools:text="@string/KbsReminderDialog__to_help_you_memorize_your_pin" />
|
tools:text="@string/KbsReminderDialog__to_help_you_memorize_your_pin" />
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="end"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:paddingEnd="20dp"
|
|
||||||
android:paddingBottom="20dp">
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/skip"
|
android:id="@+id/skip"
|
||||||
style="@style/Button.Borderless"
|
style="@style/Button.Borderless"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/KbsReminderDialog__skip" />
|
android:text="@string/KbsReminderDialog__skip"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/submit"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/submit"/>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/submit"
|
android:id="@+id/submit"
|
||||||
style="@style/Button.Primary"
|
style="@style/Button.Primary"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/KbsReminderDialog__submit" />
|
android:text="@string/KbsReminderDialog__submit"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/reminder"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
</LinearLayout>
|
|
@ -545,6 +545,9 @@
|
|||||||
<string name="Megaphones_introducing_reactions">Introducing Reactions</string>
|
<string name="Megaphones_introducing_reactions">Introducing Reactions</string>
|
||||||
<string name="Megaphones_tap_and_hold_any_message_to_quicky_share_how_you_feel">Tap and hold any message to quickly share how you feel.</string>
|
<string name="Megaphones_tap_and_hold_any_message_to_quicky_share_how_you_feel">Tap and hold any message to quickly share how you feel.</string>
|
||||||
<string name="Megaphones_remind_me_later">Remind me later</string>
|
<string name="Megaphones_remind_me_later">Remind me later</string>
|
||||||
|
<string name="Megaphones_verify_your_signal_pin">Verify your Signal PIN</string>
|
||||||
|
<string name="Megaphones_well_occasionally_ask_you_to_verify_your_pin">We\'ll occasionally ask you to verify your PIN so that you remember it.</string>
|
||||||
|
<string name="Megaphones_verify_pin">Verify PIN</string>
|
||||||
|
|
||||||
<!-- NotificationBarManager -->
|
<!-- NotificationBarManager -->
|
||||||
<string name="NotificationBarManager_signal_call_in_progress">Signal call in progress</string>
|
<string name="NotificationBarManager_signal_call_in_progress">Signal call in progress</string>
|
||||||
@ -775,6 +778,13 @@
|
|||||||
<string name="SharedContactView_invite_to_signal">Invite to Signal</string>
|
<string name="SharedContactView_invite_to_signal">Invite to Signal</string>
|
||||||
<string name="SharedContactView_message">Signal Message</string>
|
<string name="SharedContactView_message">Signal Message</string>
|
||||||
|
|
||||||
|
<!-- SignalPinReminders -->
|
||||||
|
<string name="SignalPinReminders_well_remind_you_again_later">We\'ll remind you again later.</string>
|
||||||
|
<string name="SignalPinReminders_well_remind_you_again_tomorrow">We\'ll remind you again tomorrow.</string>
|
||||||
|
<string name="SignalPinReminders_well_remind_you_again_in_a_few_days">We\'ll remind you again in a few days.</string>
|
||||||
|
<string name="SignalPinReminders_well_remind_you_again_in_a_week">We\'ll remind you again in a week.</string>
|
||||||
|
<string name="SignalPinReminders_well_remind_you_again_in_a_couple_weeks">We\'ll remind you again in a couple weeks.</string>
|
||||||
|
|
||||||
<!-- Slide -->
|
<!-- Slide -->
|
||||||
<string name="Slide_image">Image</string>
|
<string name="Slide_image">Image</string>
|
||||||
<string name="Slide_sticker">Sticker</string>
|
<string name="Slide_sticker">Sticker</string>
|
||||||
|
@ -321,7 +321,7 @@
|
|||||||
|
|
||||||
<item name="media_keyboard_button_color">@color/core_grey_60</item>
|
<item name="media_keyboard_button_color">@color/core_grey_60</item>
|
||||||
|
|
||||||
<item name="megaphone_background">@color/core_white</item>
|
<item name="megaphone_background">@color/core_grey_05</item>
|
||||||
<item name="megaphone_background_shadow">@drawable/megaphone_background_shadow</item>
|
<item name="megaphone_background_shadow">@drawable/megaphone_background_shadow</item>
|
||||||
<item name="megaphone_body_text_color">@color/core_grey_65</item>
|
<item name="megaphone_body_text_color">@color/core_grey_65</item>
|
||||||
<item name="megaphone_reactions_shade">@color/core_grey_02</item>
|
<item name="megaphone_reactions_shade">@color/core_grey_02</item>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user