During registration, persist time that call me is available.

Fixes #9926
master
Alan Evans 2020-08-19 12:40:26 -03:00 committed by Greyson Parrelli
parent d0681a5592
commit 220ebf93c7
6 changed files with 68 additions and 42 deletions

View File

@ -8,9 +8,11 @@ import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import java.util.concurrent.TimeUnit;
public class CallMeCountDownView extends androidx.appcompat.widget.AppCompatButton { public class CallMeCountDownView extends androidx.appcompat.widget.AppCompatButton {
private int countDown; private long countDownToTime;
@Nullable @Nullable
private Listener listener; private Listener listener;
@ -26,9 +28,14 @@ public class CallMeCountDownView extends androidx.appcompat.widget.AppCompatButt
super(context, attrs, defStyleAttr); super(context, attrs, defStyleAttr);
} }
public void startCountDown(int countDown) { /**
this.countDown = countDown; * Starts a count down to the specified {@param time}.
updateCountDown(); */
public void startCountDownTo(long time) {
if (time > 0) {
this.countDownToTime = time;
updateCountDown();
}
} }
public void setCallEnabled() { public void setCallEnabled() {
@ -38,23 +45,24 @@ public class CallMeCountDownView extends androidx.appcompat.widget.AppCompatButt
} }
private void updateCountDown() { private void updateCountDown() {
if (countDown > 0) { final long remainingMillis = countDownToTime - System.currentTimeMillis();
if (remainingMillis > 0) {
setEnabled(false); setEnabled(false);
setAlpha(0.5f); setAlpha(0.5f);
countDown--; int totalRemainingSeconds = (int) TimeUnit.MILLISECONDS.toSeconds(remainingMillis);
int minutesRemaining = totalRemainingSeconds / 60;
int minutesRemaining = countDown / 60; int secondsRemaining = totalRemainingSeconds % 60;
int secondsRemaining = countDown % 60;
setText(getResources().getString(R.string.RegistrationActivity_call_me_instead_available_in, minutesRemaining, secondsRemaining)); setText(getResources().getString(R.string.RegistrationActivity_call_me_instead_available_in, minutesRemaining, secondsRemaining));
if (listener != null) { if (listener != null) {
listener.onRemaining(this, countDown); listener.onRemaining(this, totalRemainingSeconds);
} }
postDelayed(this::updateCountDown, 1000); postDelayed(this::updateCountDown, 250);
} else if (countDown == 0) { } else {
setCallEnabled(); setCallEnabled();
} }
} }
@ -64,6 +72,6 @@ public class CallMeCountDownView extends androidx.appcompat.widget.AppCompatButt
} }
public interface Listener { public interface Listener {
void onRemaining(@NonNull CallMeCountDownView view, int remaining); void onRemaining(@NonNull CallMeCountDownView view, int secondsRemaining);
} }
} }

View File

@ -29,7 +29,7 @@ public class AccountLockedFragment extends BaseRegistrationFragment {
TextView description = view.findViewById(R.id.account_locked_description); TextView description = view.findViewById(R.id.account_locked_description);
getModel().getTimeRemaining().observe(getViewLifecycleOwner(), getModel().getLockedTimeRemaining().observe(getViewLifecycleOwner(),
t -> description.setText(getString(R.string.AccountLockedFragment__your_account_has_been_locked_to_protect_your_privacy, durationToDays(t))) t -> description.setText(getString(R.string.AccountLockedFragment__your_account_has_been_locked_to_protect_your_privacy, durationToDays(t)))
); );

View File

@ -85,12 +85,15 @@ public final class EnterCodeFragment extends BaseRegistrationFragment {
noCodeReceivedHelp.setOnClickListener(v -> sendEmailToSupport()); noCodeReceivedHelp.setOnClickListener(v -> sendEmailToSupport());
getModel().getSuccessfulCodeRequestAttempts().observe(this, (attempts) -> { RegistrationViewModel model = getModel();
model.getSuccessfulCodeRequestAttempts().observe(this, (attempts) -> {
if (attempts >= 3) { if (attempts >= 3) {
noCodeReceivedHelp.setVisibility(View.VISIBLE); noCodeReceivedHelp.setVisibility(View.VISIBLE);
scrollView.postDelayed(() -> scrollView.smoothScrollTo(0, noCodeReceivedHelp.getBottom()), 15000); scrollView.postDelayed(() -> scrollView.smoothScrollTo(0, noCodeReceivedHelp.getBottom()), 15000);
} }
}); });
model.onStartEnterCode();
} }
private void setOnCodeFullyEnteredListener(VerificationCodeView verificationCodeView) { private void setOnCodeFullyEnteredListener(VerificationCodeView verificationCodeView) {
@ -119,7 +122,7 @@ public final class EnterCodeFragment extends BaseRegistrationFragment {
@Override @Override
public void onV1RegistrationLockPinRequiredOrIncorrect(long timeRemaining) { public void onV1RegistrationLockPinRequiredOrIncorrect(long timeRemaining) {
model.setTimeRemaining(timeRemaining); model.setLockedTimeRemaining(timeRemaining);
keyboard.displayLocked().addListener(new AssertedSuccessListener<Boolean>() { keyboard.displayLocked().addListener(new AssertedSuccessListener<Boolean>() {
@Override @Override
public void onSuccess(Boolean r) { public void onSuccess(Boolean r) {
@ -131,7 +134,7 @@ public final class EnterCodeFragment extends BaseRegistrationFragment {
@Override @Override
public void onKbsRegistrationLockPinRequired(long timeRemaining, @NonNull TokenResponse tokenResponse, @NonNull String kbsStorageCredentials) { public void onKbsRegistrationLockPinRequired(long timeRemaining, @NonNull TokenResponse tokenResponse, @NonNull String kbsStorageCredentials) {
model.setTimeRemaining(timeRemaining); model.setLockedTimeRemaining(timeRemaining);
model.setStorageCredentials(kbsStorageCredentials); model.setStorageCredentials(kbsStorageCredentials);
model.setKeyBackupCurrentToken(tokenResponse); model.setKeyBackupCurrentToken(tokenResponse);
keyboard.displayLocked().addListener(new AssertedSuccessListener<Boolean>() { keyboard.displayLocked().addListener(new AssertedSuccessListener<Boolean>() {
@ -170,7 +173,7 @@ public final class EnterCodeFragment extends BaseRegistrationFragment {
@Override @Override
public void onKbsAccountLocked(@Nullable Long timeRemaining) { public void onKbsAccountLocked(@Nullable Long timeRemaining) {
if (timeRemaining != null) { if (timeRemaining != null) {
model.setTimeRemaining(timeRemaining); model.setLockedTimeRemaining(timeRemaining);
} }
Navigation.findNavController(requireView()).navigate(RegistrationLockFragmentDirections.actionAccountLocked()); Navigation.findNavController(requireView()).navigate(RegistrationLockFragmentDirections.actionAccountLocked());
} }
@ -249,12 +252,12 @@ public final class EnterCodeFragment extends BaseRegistrationFragment {
} }
private void handlePhoneCallRequest() { private void handlePhoneCallRequest() {
callMeCountDown.startCountDown(RegistrationConstants.SUBSEQUENT_CALL_AVAILABLE_AFTER);
RegistrationViewModel model = getModel(); RegistrationViewModel model = getModel();
String captcha = model.getCaptchaToken(); String captcha = model.getCaptchaToken();
model.clearCaptchaResponse(); model.clearCaptchaResponse();
model.onCallRequested();
NavController navController = Navigation.findNavController(callMeCountDown); NavController navController = Navigation.findNavController(callMeCountDown);
RegistrationService registrationService = RegistrationService.getInstance(model.getNumber().getE164Number(), model.getRegistrationSecret()); RegistrationService registrationService = RegistrationService.getInstance(model.getNumber().getE164Number(), model.getRegistrationSecret());
@ -301,9 +304,10 @@ public final class EnterCodeFragment extends BaseRegistrationFragment {
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
getModel().getLiveNumber().observe(this, (s) -> header.setText(requireContext().getString(R.string.RegistrationActivity_enter_the_code_we_sent_to_s, s.getFullFormattedNumber()))); RegistrationViewModel model = getModel();
model.getLiveNumber().observe(this, (s) -> header.setText(requireContext().getString(R.string.RegistrationActivity_enter_the_code_we_sent_to_s, s.getFullFormattedNumber())));
callMeCountDown.startCountDown(RegistrationConstants.FIRST_CALL_AVAILABLE_AFTER); model.getCanCallAtTime().observe(this, callAtTime -> callMeCountDown.startCountDownTo(callAtTime));
} }
private void sendEmailToSupport() { private void sendEmailToSupport() {

View File

@ -5,11 +5,8 @@ final class RegistrationConstants {
private RegistrationConstants() { private RegistrationConstants() {
} }
static final int FIRST_CALL_AVAILABLE_AFTER = 64; static final String TERMS_AND_CONDITIONS_URL = "https://signal.org/legal";
static final int SUBSEQUENT_CALL_AVAILABLE_AFTER = 300; static final String SIGNAL_CAPTCHA_URL = "https://signalcaptchas.org/registration/generate.html";
static final String TERMS_AND_CONDITIONS_URL = "https://signal.org/legal"; static final String SIGNAL_CAPTCHA_SCHEME = "signalcaptcha://";
static final String SIGNAL_CAPTCHA_URL = "https://signalcaptchas.org/registration/generate.html";
static final String SIGNAL_CAPTCHA_SCHEME = "signalcaptcha://";
} }

View File

@ -28,7 +28,6 @@ import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.registration.service.CodeVerificationRequest; import org.thoughtcrime.securesms.registration.service.CodeVerificationRequest;
import org.thoughtcrime.securesms.registration.service.RegistrationService; import org.thoughtcrime.securesms.registration.service.RegistrationService;
import org.thoughtcrime.securesms.registration.viewmodel.RegistrationViewModel; import org.thoughtcrime.securesms.registration.viewmodel.RegistrationViewModel;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.ServiceUtil; import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask; import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse; import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
@ -104,7 +103,7 @@ public final class RegistrationLockFragment extends BaseRegistrationFragment {
PinKeyboardType keyboardType = getPinEntryKeyboardType().getOther(); PinKeyboardType keyboardType = getPinEntryKeyboardType().getOther();
keyboardToggle.setText(resolveKeyboardToggleText(keyboardType)); keyboardToggle.setText(resolveKeyboardToggleText(keyboardType));
getModel().getTimeRemaining() getModel().getLockedTimeRemaining()
.observe(getViewLifecycleOwner(), t -> timeRemaining = t); .observe(getViewLifecycleOwner(), t -> timeRemaining = t);
TokenResponse keyBackupCurrentToken = getModel().getKeyBackupCurrentToken(); TokenResponse keyBackupCurrentToken = getModel().getKeyBackupCurrentToken();
@ -180,7 +179,7 @@ public final class RegistrationLockFragment extends BaseRegistrationFragment {
@Override @Override
public void onV1RegistrationLockPinRequiredOrIncorrect(long timeRemaining) { public void onV1RegistrationLockPinRequiredOrIncorrect(long timeRemaining) {
getModel().setTimeRemaining(timeRemaining); getModel().setLockedTimeRemaining(timeRemaining);
cancelSpinning(pinButton); cancelSpinning(pinButton);
pinEntry.getText().clear(); pinEntry.getText().clear();
@ -243,7 +242,7 @@ public final class RegistrationLockFragment extends BaseRegistrationFragment {
@Override @Override
public void onKbsAccountLocked(@Nullable Long timeRemaining) { public void onKbsAccountLocked(@Nullable Long timeRemaining) {
if (timeRemaining != null) { if (timeRemaining != null) {
model.setTimeRemaining(timeRemaining); model.setLockedTimeRemaining(timeRemaining);
} }
onAccountLocked(); onAccountLocked();

View File

@ -14,11 +14,15 @@ import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse
import org.whispersystems.signalservice.internal.util.JsonUtil; import org.whispersystems.signalservice.internal.util.JsonUtil;
import java.io.IOException; import java.io.IOException;
import java.util.concurrent.TimeUnit;
public final class RegistrationViewModel extends ViewModel { public final class RegistrationViewModel extends ViewModel {
private static final String TAG = Log.tag(RegistrationViewModel.class); private static final String TAG = Log.tag(RegistrationViewModel.class);
private static final long FIRST_CALL_AVAILABLE_AFTER_MS = TimeUnit.SECONDS.toMillis(64);
private static final long SUBSEQUENT_CALL_AVAILABLE_AFTER_MS = TimeUnit.SECONDS.toMillis(300);
private final String secret; private final String secret;
private final MutableLiveData<NumberViewState> number; private final MutableLiveData<NumberViewState> number;
private final MutableLiveData<String> textCodeEntered; private final MutableLiveData<String> textCodeEntered;
@ -28,8 +32,9 @@ public final class RegistrationViewModel extends ViewModel {
private final MutableLiveData<Boolean> restoreFlowShown; private final MutableLiveData<Boolean> restoreFlowShown;
private final MutableLiveData<Integer> successfulCodeRequestAttempts; private final MutableLiveData<Integer> successfulCodeRequestAttempts;
private final MutableLiveData<LocalCodeRequestRateLimiter> requestLimiter; private final MutableLiveData<LocalCodeRequestRateLimiter> requestLimiter;
private final MutableLiveData<String> keyBackupcurrentTokenJson; private final MutableLiveData<String> keyBackupCurrentTokenJson;
private final MutableLiveData<Long> timeRemaining; private final MutableLiveData<Long> lockedTimeRemaining;
private final MutableLiveData<Long> canCallAtTime;
public RegistrationViewModel(@NonNull SavedStateHandle savedStateHandle) { public RegistrationViewModel(@NonNull SavedStateHandle savedStateHandle) {
secret = loadValue(savedStateHandle, "REGISTRATION_SECRET", Util.getSecret(18)); secret = loadValue(savedStateHandle, "REGISTRATION_SECRET", Util.getSecret(18));
@ -42,8 +47,9 @@ public final class RegistrationViewModel extends ViewModel {
restoreFlowShown = savedStateHandle.getLiveData("RESTORE_FLOW_SHOWN", false); restoreFlowShown = savedStateHandle.getLiveData("RESTORE_FLOW_SHOWN", false);
successfulCodeRequestAttempts = savedStateHandle.getLiveData("SUCCESSFUL_CODE_REQUEST_ATTEMPTS", 0); successfulCodeRequestAttempts = savedStateHandle.getLiveData("SUCCESSFUL_CODE_REQUEST_ATTEMPTS", 0);
requestLimiter = savedStateHandle.getLiveData("REQUEST_RATE_LIMITER", new LocalCodeRequestRateLimiter(60_000)); requestLimiter = savedStateHandle.getLiveData("REQUEST_RATE_LIMITER", new LocalCodeRequestRateLimiter(60_000));
keyBackupcurrentTokenJson = savedStateHandle.getLiveData("KBS_TOKEN"); keyBackupCurrentTokenJson = savedStateHandle.getLiveData("KBS_TOKEN");
timeRemaining = savedStateHandle.getLiveData("TIME_REMAINING", 0L); lockedTimeRemaining = savedStateHandle.getLiveData("TIME_REMAINING", 0L);
canCallAtTime = savedStateHandle.getLiveData("CAN_CALL_AT_TIME", 0L);
} }
private static <T> T loadValue(@NonNull SavedStateHandle savedStateHandle, @NonNull String key, @NonNull T initialValue) { private static <T> T loadValue(@NonNull SavedStateHandle savedStateHandle, @NonNull String key, @NonNull T initialValue) {
@ -161,7 +167,7 @@ public final class RegistrationViewModel extends ViewModel {
} }
public @Nullable TokenResponse getKeyBackupCurrentToken() { public @Nullable TokenResponse getKeyBackupCurrentToken() {
String json = keyBackupcurrentTokenJson.getValue(); String json = keyBackupCurrentTokenJson.getValue();
if (json == null) return null; if (json == null) return null;
try { try {
return JsonUtil.fromJson(json, TokenResponse.class); return JsonUtil.fromJson(json, TokenResponse.class);
@ -173,14 +179,26 @@ public final class RegistrationViewModel extends ViewModel {
public void setKeyBackupCurrentToken(TokenResponse tokenResponse) { public void setKeyBackupCurrentToken(TokenResponse tokenResponse) {
String json = tokenResponse == null ? null : JsonUtil.toJson(tokenResponse); String json = tokenResponse == null ? null : JsonUtil.toJson(tokenResponse);
keyBackupcurrentTokenJson.setValue(json); keyBackupCurrentTokenJson.setValue(json);
} }
public LiveData<Long> getTimeRemaining() { public LiveData<Long> getLockedTimeRemaining() {
return timeRemaining; return lockedTimeRemaining;
} }
public void setTimeRemaining(long timeRemaining) { public LiveData<Long> getCanCallAtTime() {
this.timeRemaining.setValue(timeRemaining); return canCallAtTime;
}
public void setLockedTimeRemaining(long lockedTimeRemaining) {
this.lockedTimeRemaining.setValue(lockedTimeRemaining);
}
public void onStartEnterCode() {
canCallAtTime.setValue(System.currentTimeMillis() + FIRST_CALL_AVAILABLE_AFTER_MS);
}
public void onCallRequested() {
canCallAtTime.setValue(System.currentTimeMillis() + SUBSEQUENT_CALL_AVAILABLE_AFTER_MS);
} }
} }