mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-23 02:10:44 +01:00
Implement new PIN UX.
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
package org.thoughtcrime.securesms.registration.fragments;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
||||
public class AccountLockedFragment extends Fragment {
|
||||
|
||||
@Override
|
||||
public @Nullable View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.account_locked_fragment, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
AccountLockedFragmentArgs args = AccountLockedFragmentArgs.fromBundle(requireArguments());
|
||||
|
||||
TextView description = view.findViewById(R.id.account_locked_description);
|
||||
|
||||
description.setText(getString(R.string.AccountLockedFragment__your_account_has_been_locked_to_protect_your_privacy, args.getTimeRemaining()));
|
||||
|
||||
view.findViewById(R.id.account_locked_next).setOnClickListener(this::onNextClicked);
|
||||
view.findViewById(R.id.account_locked_learn_more).setOnClickListener(this::onLearnMoreClicked);
|
||||
|
||||
requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
onNext();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void onNextClicked(@NonNull View unused) {
|
||||
onNext();
|
||||
}
|
||||
|
||||
private void onLearnMoreClicked(@NonNull View unused) {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
|
||||
intent.setData(Uri.parse(getString(R.string.AccountLockedFragment__learn_more_url)));
|
||||
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
private void onNext() {
|
||||
requireActivity().finish();
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ import org.thoughtcrime.securesms.registration.service.CodeVerificationRequest;
|
||||
import org.thoughtcrime.securesms.registration.service.RegistrationCodeRequest;
|
||||
import org.thoughtcrime.securesms.registration.service.RegistrationService;
|
||||
import org.thoughtcrime.securesms.registration.viewmodel.RegistrationViewModel;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.concurrent.AssertedSuccessListener;
|
||||
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
|
||||
|
||||
@@ -126,8 +127,13 @@ public final class EnterCodeFragment extends BaseRegistrationFragment {
|
||||
keyboard.displayLocked().addListener(new AssertedSuccessListener<Boolean>() {
|
||||
@Override
|
||||
public void onSuccess(Boolean r) {
|
||||
Navigation.findNavController(requireView())
|
||||
.navigate(EnterCodeFragmentDirections.actionRequireRegistrationLockPin(timeRemaining));
|
||||
if (FeatureFlags.pinsForAll()) {
|
||||
Navigation.findNavController(requireView())
|
||||
.navigate(EnterCodeFragmentDirections.actionRequireKbsLockPin(timeRemaining));
|
||||
} else {
|
||||
Navigation.findNavController(requireView())
|
||||
.navigate(EnterCodeFragmentDirections.actionRequireRegistrationLockPin(timeRemaining));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
package org.thoughtcrime.securesms.registration.fragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.text.InputType;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.dd.CircularProgressButton;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.lock.v2.KbsKeyboardType;
|
||||
import org.thoughtcrime.securesms.registration.service.CodeVerificationRequest;
|
||||
import org.thoughtcrime.securesms.registration.service.RegistrationService;
|
||||
import org.thoughtcrime.securesms.registration.viewmodel.RegistrationViewModel;
|
||||
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public final class KbsLockFragment extends BaseRegistrationFragment {
|
||||
|
||||
private EditText pinEntry;
|
||||
private CircularProgressButton pinButton;
|
||||
private TextView errorLabel;
|
||||
private TextView keyboardToggle;
|
||||
private long timeRemaining;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.kbs_lock_fragment, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
setDebugLogSubmitMultiTapView(view.findViewById(R.id.kbs_lock_pin_title));
|
||||
|
||||
pinEntry = view.findViewById(R.id.kbs_lock_pin_input);
|
||||
pinButton = view.findViewById(R.id.kbs_lock_pin_confirm);
|
||||
errorLabel = view.findViewById(R.id.kbs_lock_pin_input_label);
|
||||
keyboardToggle = view.findViewById(R.id.kbs_lock_keyboard_toggle);
|
||||
|
||||
View pinForgotButton = view.findViewById(R.id.kbs_lock_forgot_pin);
|
||||
|
||||
timeRemaining = KbsLockFragmentArgs.fromBundle(requireArguments()).getTimeRemaining();
|
||||
|
||||
pinForgotButton.setOnClickListener(v -> handleForgottenPin(timeRemaining));
|
||||
|
||||
pinEntry.setImeOptions(EditorInfo.IME_ACTION_DONE);
|
||||
pinEntry.setOnEditorActionListener((v, actionId, event) -> {
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
hideKeyboard(requireContext(), v);
|
||||
handlePinEntry();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
pinButton.setOnClickListener((v) -> {
|
||||
hideKeyboard(requireContext(), pinEntry);
|
||||
handlePinEntry();
|
||||
});
|
||||
|
||||
keyboardToggle.setOnClickListener((v) -> {
|
||||
KbsKeyboardType keyboardType = getPinEntryKeyboardType();
|
||||
|
||||
updateKeyboard(keyboardType);
|
||||
keyboardToggle.setText(resolveKeyboardToggleText(keyboardType));
|
||||
});
|
||||
|
||||
RegistrationViewModel model = getModel();
|
||||
model.getTokenResponseCredentialsPair().observe(getViewLifecycleOwner(), pair -> {
|
||||
if (pair.first().getTries() == 0) {
|
||||
lockAccount();
|
||||
}
|
||||
});
|
||||
|
||||
model.onRegistrationLockFragmentCreate();
|
||||
}
|
||||
|
||||
private KbsKeyboardType getPinEntryKeyboardType() {
|
||||
boolean isNumeric = (pinEntry.getImeOptions() & InputType.TYPE_MASK_CLASS) == InputType.TYPE_CLASS_NUMBER;
|
||||
|
||||
return isNumeric ? KbsKeyboardType.NUMERIC : KbsKeyboardType.ALPHA_NUMERIC;
|
||||
}
|
||||
|
||||
private void handlePinEntry() {
|
||||
final String pin = pinEntry.getText().toString();
|
||||
|
||||
if (TextUtils.isEmpty(pin) || TextUtils.isEmpty(pin.replace(" ", ""))) {
|
||||
Toast.makeText(requireContext(), R.string.RegistrationActivity_you_must_enter_your_registration_lock_PIN, Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
RegistrationViewModel model = getModel();
|
||||
RegistrationService registrationService = RegistrationService.getInstance(model.getNumber().getE164Number(), model.getRegistrationSecret());
|
||||
String storageCredentials = model.getBasicStorageCredentials();
|
||||
TokenResponse tokenResponse = model.getKeyBackupCurrentToken();
|
||||
|
||||
setSpinning(pinButton);
|
||||
|
||||
registrationService.verifyAccount(requireActivity(),
|
||||
model.getFcmToken(),
|
||||
model.getTextCodeEntered(),
|
||||
pin, storageCredentials, tokenResponse,
|
||||
|
||||
new CodeVerificationRequest.VerifyCallback() {
|
||||
|
||||
@Override
|
||||
public void onSuccessfulRegistration() {
|
||||
cancelSpinning(pinButton);
|
||||
SignalStore.kbsValues().setKeyboardType(getPinEntryKeyboardType());
|
||||
|
||||
Navigation.findNavController(requireView()).navigate(KbsLockFragmentDirections.actionSuccessfulRegistration());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onIncorrectRegistrationLockPin(long timeRemaining, String storageCredentials) {
|
||||
model.setStorageCredentials(storageCredentials);
|
||||
cancelSpinning(pinButton);
|
||||
|
||||
pinEntry.setText("");
|
||||
errorLabel.setText(R.string.KbsLockFragment__incorrect_pin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onIncorrectKbsRegistrationLockPin(@NonNull TokenResponse tokenResponse) {
|
||||
cancelSpinning(pinButton);
|
||||
|
||||
model.setKeyBackupCurrentToken(tokenResponse);
|
||||
|
||||
int triesRemaining = tokenResponse.getTries();
|
||||
|
||||
if (triesRemaining == 0) {
|
||||
lockAccount();
|
||||
return;
|
||||
}
|
||||
|
||||
if (triesRemaining == 3) {
|
||||
long daysRemaining = getLockoutDays(timeRemaining);
|
||||
|
||||
new AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.KbsLockFragment__incorrect_pin)
|
||||
.setMessage(getString(R.string.KbsLockFragment__you_have_d_attempts_remaining, triesRemaining, daysRemaining, daysRemaining))
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
if (triesRemaining > 5) {
|
||||
errorLabel.setText(R.string.KbsLockFragment__incorrect_pin_try_again);
|
||||
} else {
|
||||
errorLabel.setText(getString(R.string.KbsLockFragment__incorrect_pin_d_attempts_remaining, triesRemaining));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTooManyAttempts() {
|
||||
cancelSpinning(pinButton);
|
||||
|
||||
new AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.RegistrationActivity_too_many_attempts)
|
||||
.setMessage(R.string.RegistrationActivity_you_have_made_too_many_incorrect_registration_lock_pin_attempts_please_try_again_in_a_day)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError() {
|
||||
cancelSpinning(pinButton);
|
||||
|
||||
Toast.makeText(requireContext(), R.string.RegistrationActivity_error_connecting_to_service, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void handleForgottenPin(long timeRemainingMs) {
|
||||
new AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.KbsLockFragment__forgot_your_pin)
|
||||
.setMessage(getString(R.string.KbsLockFragment__for_your_privacy_and_security_there_is_no_way_to_recover, getLockoutDays(timeRemainingMs)))
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
private long getLockoutDays(long timeRemainingMs) {
|
||||
return TimeUnit.MILLISECONDS.toDays(timeRemainingMs) + 1;
|
||||
}
|
||||
|
||||
private void lockAccount() {
|
||||
KbsLockFragmentDirections.ActionAccountLocked action = KbsLockFragmentDirections.actionAccountLocked(timeRemaining);
|
||||
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
}
|
||||
|
||||
private void updateKeyboard(@NonNull KbsKeyboardType keyboard) {
|
||||
boolean isAlphaNumeric = keyboard == KbsKeyboardType.ALPHA_NUMERIC;
|
||||
|
||||
pinEntry.setInputType(isAlphaNumeric ? InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD
|
||||
: InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD);
|
||||
}
|
||||
|
||||
private @StringRes int resolveKeyboardToggleText(@NonNull KbsKeyboardType keyboard) {
|
||||
if (keyboard == KbsKeyboardType.ALPHA_NUMERIC) {
|
||||
return R.string.KbsLockFragment__enter_alphanumeric_pin;
|
||||
} else {
|
||||
return R.string.KbsLockFragment__enter_numeric_pin;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,11 @@ import androidx.navigation.ActivityNavigator;
|
||||
|
||||
import org.thoughtcrime.securesms.MainActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity;
|
||||
import org.thoughtcrime.securesms.lock.v2.PinUtil;
|
||||
import org.thoughtcrime.securesms.profiles.edit.EditProfileActivity;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
|
||||
public final class RegistrationCompleteFragment extends BaseRegistrationFragment {
|
||||
|
||||
@@ -30,12 +34,19 @@ public final class RegistrationCompleteFragment extends BaseRegistrationFragment
|
||||
|
||||
FragmentActivity activity = requireActivity();
|
||||
|
||||
|
||||
if (!isReregister()) {
|
||||
Intent setProfileNameIntent = getRoutedIntent(activity, EditProfileActivity.class, new Intent(activity, MainActivity.class));
|
||||
final Intent main = new Intent(activity, MainActivity.class);
|
||||
final Intent next = getRoutedIntent(activity, EditProfileActivity.class, main);
|
||||
|
||||
setProfileNameIntent.putExtra(EditProfileActivity.SHOW_TOOLBAR, false);
|
||||
next.putExtra(EditProfileActivity.SHOW_TOOLBAR, false);
|
||||
|
||||
activity.startActivity(setProfileNameIntent);
|
||||
Context context = requireContext();
|
||||
if (FeatureFlags.pinsForAll() && !PinUtil.userHasPin(context)) {
|
||||
activity.startActivity(getRoutedIntent(activity, CreateKbsPinActivity.class, next));
|
||||
} else {
|
||||
activity.startActivity(next);
|
||||
}
|
||||
}
|
||||
|
||||
activity.finish();
|
||||
|
||||
Reference in New Issue
Block a user