mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-21 09:20:19 +01:00
Redesign FTUX to use Material Design 3.
This commit is contained in:
committed by
Greyson Parrelli
parent
0303467c91
commit
150bbf181d
@@ -17,10 +17,6 @@ import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
import com.airbnb.lottie.LottieAnimationView;
|
||||
|
||||
import org.thoughtcrime.securesms.LoggingFragment;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
@@ -29,19 +25,18 @@ import org.thoughtcrime.securesms.pin.PinOptOutDialog;
|
||||
import org.thoughtcrime.securesms.registration.RegistrationUtil;
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions;
|
||||
import org.thoughtcrime.securesms.util.text.AfterTextChanged;
|
||||
import org.thoughtcrime.securesms.util.views.CircularProgressMaterialButton;
|
||||
import org.thoughtcrime.securesms.util.views.LearnMoreTextView;
|
||||
|
||||
abstract class BaseKbsPinFragment<ViewModel extends BaseKbsPinViewModel> extends LoggingFragment {
|
||||
public abstract class BaseKbsPinFragment<ViewModel extends BaseKbsPinViewModel> extends LoggingFragment {
|
||||
|
||||
private TextView title;
|
||||
private LearnMoreTextView description;
|
||||
private EditText input;
|
||||
private TextView label;
|
||||
private TextView keyboardToggle;
|
||||
private TextView confirm;
|
||||
private LottieAnimationView lottieProgress;
|
||||
private LottieAnimationView lottieEnd;
|
||||
private ViewModel viewModel;
|
||||
private TextView title;
|
||||
private LearnMoreTextView description;
|
||||
private EditText input;
|
||||
private TextView label;
|
||||
private TextView keyboardToggle;
|
||||
private CircularProgressMaterialButton confirm;
|
||||
private ViewModel viewModel;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
@@ -78,10 +73,6 @@ abstract class BaseKbsPinFragment<ViewModel extends BaseKbsPinViewModel> extends
|
||||
CommunicationActions.openBrowserLink(requireContext(), getString(R.string.BaseKbsPinFragment__learn_more_url));
|
||||
});
|
||||
|
||||
Toolbar toolbar = view.findViewById(R.id.kbs_pin_toolbar);
|
||||
((AppCompatActivity) requireActivity()).setSupportActionBar(toolbar);
|
||||
((AppCompatActivity) requireActivity()).getSupportActionBar().setTitle(null);
|
||||
|
||||
initializeListeners();
|
||||
}
|
||||
|
||||
@@ -137,13 +128,6 @@ abstract class BaseKbsPinFragment<ViewModel extends BaseKbsPinViewModel> extends
|
||||
return input;
|
||||
}
|
||||
|
||||
protected LottieAnimationView getLottieProgress() {
|
||||
return lottieProgress;
|
||||
}
|
||||
|
||||
protected LottieAnimationView getLottieEnd() {
|
||||
return lottieEnd;
|
||||
}
|
||||
|
||||
protected TextView getLabel() {
|
||||
return label;
|
||||
@@ -153,7 +137,7 @@ abstract class BaseKbsPinFragment<ViewModel extends BaseKbsPinViewModel> extends
|
||||
return keyboardToggle;
|
||||
}
|
||||
|
||||
protected TextView getConfirm() {
|
||||
protected CircularProgressMaterialButton getConfirm() {
|
||||
return confirm;
|
||||
}
|
||||
|
||||
@@ -173,8 +157,6 @@ abstract class BaseKbsPinFragment<ViewModel extends BaseKbsPinViewModel> extends
|
||||
label = view.findViewById(R.id.edit_kbs_pin_input_label);
|
||||
keyboardToggle = view.findViewById(R.id.edit_kbs_pin_keyboard_toggle);
|
||||
confirm = view.findViewById(R.id.edit_kbs_pin_confirm);
|
||||
lottieProgress = view.findViewById(R.id.edit_kbs_pin_lottie_progress);
|
||||
lottieEnd = view.findViewById(R.id.edit_kbs_pin_lottie_end);
|
||||
|
||||
initializeViewStates();
|
||||
}
|
||||
|
||||
@@ -1,189 +0,0 @@
|
||||
package org.thoughtcrime.securesms.lock.v2;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.app.Activity;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RawRes;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.autofill.HintConstants;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import com.airbnb.lottie.LottieAnimationView;
|
||||
import com.airbnb.lottie.LottieDrawable;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.animation.AnimationCompleteListener;
|
||||
import org.thoughtcrime.securesms.animation.AnimationRepeatListener;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.megaphone.Megaphones;
|
||||
import org.thoughtcrime.securesms.registration.RegistrationUtil;
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
|
||||
import org.thoughtcrime.securesms.util.SpanUtil;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class ConfirmKbsPinFragment extends BaseKbsPinFragment<ConfirmKbsPinViewModel> {
|
||||
|
||||
private ConfirmKbsPinViewModel viewModel;
|
||||
|
||||
@Override
|
||||
protected void initializeViewStates() {
|
||||
ConfirmKbsPinFragmentArgs args = ConfirmKbsPinFragmentArgs.fromBundle(requireArguments());
|
||||
|
||||
if (args.getIsPinChange()) {
|
||||
initializeViewStatesForPinChange();
|
||||
} else {
|
||||
initializeViewStatesForPinCreate();
|
||||
}
|
||||
ViewCompat.setAutofillHints(getInput(), HintConstants.AUTOFILL_HINT_NEW_PASSWORD);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ConfirmKbsPinViewModel initializeViewModel() {
|
||||
ConfirmKbsPinFragmentArgs args = ConfirmKbsPinFragmentArgs.fromBundle(requireArguments());
|
||||
KbsPin userEntry = Objects.requireNonNull(args.getUserEntry());
|
||||
PinKeyboardType keyboard = args.getKeyboard();
|
||||
ConfirmKbsPinRepository repository = new ConfirmKbsPinRepository();
|
||||
ConfirmKbsPinViewModel.Factory factory = new ConfirmKbsPinViewModel.Factory(userEntry, keyboard, repository);
|
||||
|
||||
viewModel = new ViewModelProvider(this, factory).get(ConfirmKbsPinViewModel.class);
|
||||
|
||||
viewModel.getLabel().observe(getViewLifecycleOwner(), this::updateLabel);
|
||||
viewModel.getSaveAnimation().observe(getViewLifecycleOwner(), this::updateSaveAnimation);
|
||||
|
||||
return viewModel;
|
||||
}
|
||||
|
||||
private void initializeViewStatesForPinCreate() {
|
||||
getTitle().setText(R.string.CreateKbsPinFragment__create_your_pin);
|
||||
getDescription().setText(R.string.ConfirmKbsPinFragment__confirm_your_pin);
|
||||
getKeyboardToggle().setVisibility(View.INVISIBLE);
|
||||
getLabel().setText("");
|
||||
getDescription().setLearnMoreVisible(false);
|
||||
}
|
||||
|
||||
private void initializeViewStatesForPinChange() {
|
||||
getTitle().setText(R.string.CreateKbsPinFragment__create_a_new_pin);
|
||||
getDescription().setText(R.string.ConfirmKbsPinFragment__confirm_your_pin);
|
||||
getDescription().setLearnMoreVisible(false);
|
||||
getKeyboardToggle().setVisibility(View.INVISIBLE);
|
||||
getLabel().setText("");
|
||||
}
|
||||
|
||||
private void updateLabel(@NonNull ConfirmKbsPinViewModel.Label label) {
|
||||
switch (label) {
|
||||
case EMPTY:
|
||||
getLabel().setText("");
|
||||
break;
|
||||
case CREATING_PIN:
|
||||
getLabel().setText(R.string.ConfirmKbsPinFragment__creating_pin);
|
||||
getInput().setEnabled(false);
|
||||
break;
|
||||
case RE_ENTER_PIN:
|
||||
getLabel().setText(R.string.ConfirmKbsPinFragment__re_enter_your_pin);
|
||||
break;
|
||||
case PIN_DOES_NOT_MATCH:
|
||||
getLabel().setText(SpanUtil.color(ContextCompat.getColor(requireContext(), R.color.red_500),
|
||||
getString(R.string.ConfirmKbsPinFragment__pins_dont_match)));
|
||||
getInput().getText().clear();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateSaveAnimation(@NonNull ConfirmKbsPinViewModel.SaveAnimation animation) {
|
||||
updateAnimationAndInputVisibility(animation);
|
||||
LottieAnimationView lottieProgress = getLottieProgress();
|
||||
|
||||
switch (animation) {
|
||||
case NONE:
|
||||
lottieProgress.cancelAnimation();
|
||||
break;
|
||||
case LOADING:
|
||||
lottieProgress.setAnimation(R.raw.lottie_kbs_loading);
|
||||
lottieProgress.setRepeatMode(LottieDrawable.RESTART);
|
||||
lottieProgress.setRepeatCount(LottieDrawable.INFINITE);
|
||||
lottieProgress.playAnimation();
|
||||
break;
|
||||
case SUCCESS:
|
||||
startEndAnimationOnNextProgressRepetition(R.raw.lottie_kbs_success, new AnimationCompleteListener() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
requireActivity().setResult(Activity.RESULT_OK);
|
||||
closeNavGraphBranch();
|
||||
RegistrationUtil.maybeMarkRegistrationComplete(requireContext());
|
||||
StorageSyncHelper.scheduleSyncForDataChange();
|
||||
}
|
||||
});
|
||||
break;
|
||||
case FAILURE:
|
||||
startEndAnimationOnNextProgressRepetition(R.raw.lottie_kbs_fail, new AnimationCompleteListener() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
RegistrationUtil.maybeMarkRegistrationComplete(requireContext());
|
||||
displayFailedDialog();
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void startEndAnimationOnNextProgressRepetition(@RawRes int lottieAnimationId,
|
||||
@NonNull AnimationCompleteListener listener)
|
||||
{
|
||||
LottieAnimationView lottieProgress = getLottieProgress();
|
||||
LottieAnimationView lottieEnd = getLottieEnd();
|
||||
|
||||
lottieEnd.setAnimation(lottieAnimationId);
|
||||
lottieEnd.removeAllAnimatorListeners();
|
||||
lottieEnd.setRepeatCount(0);
|
||||
lottieEnd.addAnimatorListener(listener);
|
||||
|
||||
if (lottieProgress.isAnimating()) {
|
||||
lottieProgress.addAnimatorListener(new AnimationRepeatListener(animator ->
|
||||
hideProgressAndStartEndAnimation(lottieProgress, lottieEnd)
|
||||
));
|
||||
} else {
|
||||
hideProgressAndStartEndAnimation(lottieProgress, lottieEnd);
|
||||
}
|
||||
}
|
||||
|
||||
private void hideProgressAndStartEndAnimation(@NonNull LottieAnimationView lottieProgress,
|
||||
@NonNull LottieAnimationView lottieEnd)
|
||||
{
|
||||
viewModel.onLoadingAnimationComplete();
|
||||
lottieProgress.setVisibility(View.GONE);
|
||||
lottieEnd.setVisibility(View.VISIBLE);
|
||||
lottieEnd.playAnimation();
|
||||
}
|
||||
|
||||
private void updateAnimationAndInputVisibility(ConfirmKbsPinViewModel.SaveAnimation saveAnimation) {
|
||||
if (saveAnimation == ConfirmKbsPinViewModel.SaveAnimation.NONE) {
|
||||
getInput().setVisibility(View.VISIBLE);
|
||||
getLottieProgress().setVisibility(View.GONE);
|
||||
} else {
|
||||
getInput().setVisibility(View.GONE);
|
||||
getLottieProgress().setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
private void displayFailedDialog() {
|
||||
new AlertDialog.Builder(requireContext()).setTitle(R.string.ConfirmKbsPinFragment__pin_creation_failed)
|
||||
.setMessage(R.string.ConfirmKbsPinFragment__your_pin_was_not_saved)
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(R.string.ok, (d, w) -> {
|
||||
d.dismiss();
|
||||
markMegaphoneSeenIfNecessary();
|
||||
requireActivity().setResult(Activity.RESULT_CANCELED);
|
||||
closeNavGraphBranch();
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
private void markMegaphoneSeenIfNecessary() {
|
||||
ApplicationDependencies.getMegaphoneRepository().markSeen(Megaphones.Event.PINS_FOR_ALL);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package org.thoughtcrime.securesms.lock.v2
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.DialogInterface
|
||||
import android.view.View
|
||||
import androidx.autofill.HintConstants
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.lock.v2.ConfirmKbsPinViewModel.SaveAnimation
|
||||
import org.thoughtcrime.securesms.megaphone.Megaphones
|
||||
import org.thoughtcrime.securesms.registration.RegistrationUtil
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper
|
||||
import org.thoughtcrime.securesms.util.SpanUtil
|
||||
|
||||
internal class ConfirmKbsPinFragment : BaseKbsPinFragment<ConfirmKbsPinViewModel>() {
|
||||
|
||||
override fun initializeViewStates() {
|
||||
val args = ConfirmKbsPinFragmentArgs.fromBundle(requireArguments())
|
||||
if (args.isPinChange) {
|
||||
initializeViewStatesForPinChange()
|
||||
} else {
|
||||
initializeViewStatesForPinCreate()
|
||||
}
|
||||
ViewCompat.setAutofillHints(input, HintConstants.AUTOFILL_HINT_NEW_PASSWORD)
|
||||
}
|
||||
|
||||
override fun initializeViewModel(): ConfirmKbsPinViewModel {
|
||||
val args = ConfirmKbsPinFragmentArgs.fromBundle(requireArguments())
|
||||
val userEntry = args.userEntry!!
|
||||
val keyboard = args.keyboard
|
||||
val repository = ConfirmKbsPinRepository()
|
||||
val factory = ConfirmKbsPinViewModel.Factory(userEntry, keyboard, repository)
|
||||
val viewModel = ViewModelProvider(this, factory)[ConfirmKbsPinViewModel::class.java]
|
||||
viewModel.label.observe(viewLifecycleOwner) { label: ConfirmKbsPinViewModel.LabelState -> updateLabel(label) }
|
||||
viewModel.saveAnimation.observe(viewLifecycleOwner) { animation: SaveAnimation -> updateSaveAnimation(animation) }
|
||||
return viewModel
|
||||
}
|
||||
|
||||
private fun initializeViewStatesForPinCreate() {
|
||||
title.setText(R.string.ConfirmKbsPinFragment__confirm_your_pin)
|
||||
description.setText(R.string.ConfirmKbsPinFragment__re_enter_the_pin_you_just_created)
|
||||
keyboardToggle.visibility = View.INVISIBLE
|
||||
description.setLearnMoreVisible(false)
|
||||
label.text = ""
|
||||
confirm.isEnabled = true
|
||||
}
|
||||
|
||||
private fun initializeViewStatesForPinChange() {
|
||||
title.setText(R.string.ConfirmKbsPinFragment__confirm_your_pin)
|
||||
description.setText(R.string.ConfirmKbsPinFragment__re_enter_the_pin_you_just_created)
|
||||
description.setLearnMoreVisible(false)
|
||||
keyboardToggle.visibility = View.INVISIBLE
|
||||
label.text = ""
|
||||
confirm.isEnabled = true
|
||||
}
|
||||
|
||||
private fun updateLabel(labelState: ConfirmKbsPinViewModel.LabelState) {
|
||||
when (labelState) {
|
||||
ConfirmKbsPinViewModel.LabelState.EMPTY -> label.text = ""
|
||||
ConfirmKbsPinViewModel.LabelState.CREATING_PIN -> {
|
||||
label.setText(R.string.ConfirmKbsPinFragment__creating_pin)
|
||||
input.isEnabled = false
|
||||
}
|
||||
ConfirmKbsPinViewModel.LabelState.RE_ENTER_PIN -> label.setText(R.string.ConfirmKbsPinFragment__re_enter_your_pin)
|
||||
ConfirmKbsPinViewModel.LabelState.PIN_DOES_NOT_MATCH -> {
|
||||
label.text = SpanUtil.color(
|
||||
ContextCompat.getColor(requireContext(), R.color.red_500),
|
||||
getString(R.string.ConfirmKbsPinFragment__pins_dont_match)
|
||||
)
|
||||
input.text.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateSaveAnimation(animation: SaveAnimation) {
|
||||
updateInputVisibility(animation)
|
||||
when (animation) {
|
||||
SaveAnimation.NONE -> confirm.cancelSpinning()
|
||||
SaveAnimation.LOADING -> confirm.setSpinning()
|
||||
SaveAnimation.SUCCESS -> {
|
||||
confirm.cancelSpinning()
|
||||
requireActivity().setResult(Activity.RESULT_OK)
|
||||
closeNavGraphBranch()
|
||||
RegistrationUtil.maybeMarkRegistrationComplete(requireContext())
|
||||
StorageSyncHelper.scheduleSyncForDataChange()
|
||||
}
|
||||
SaveAnimation.FAILURE -> {
|
||||
confirm.cancelSpinning()
|
||||
RegistrationUtil.maybeMarkRegistrationComplete(requireContext())
|
||||
displayFailedDialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateInputVisibility(saveAnimation: SaveAnimation) {
|
||||
if (saveAnimation == SaveAnimation.NONE) {
|
||||
input.visibility = View.VISIBLE
|
||||
} else {
|
||||
input.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
private fun displayFailedDialog() {
|
||||
MaterialAlertDialogBuilder(requireContext()).setTitle(R.string.ConfirmKbsPinFragment__pin_creation_failed)
|
||||
.setMessage(R.string.ConfirmKbsPinFragment__your_pin_was_not_saved)
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(R.string.ok) { d: DialogInterface, w: Int ->
|
||||
d.dismiss()
|
||||
markMegaphoneSeenIfNecessary()
|
||||
requireActivity().setResult(Activity.RESULT_CANCELED)
|
||||
closeNavGraphBranch()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun markMegaphoneSeenIfNecessary() {
|
||||
ApplicationDependencies.getMegaphoneRepository().markSeen(Megaphones.Event.PINS_FOR_ALL)
|
||||
}
|
||||
}
|
||||
@@ -16,8 +16,8 @@ final class ConfirmKbsPinViewModel extends ViewModel implements BaseKbsPinViewMo
|
||||
|
||||
private final DefaultValueLiveData<KbsPin> userEntry = new DefaultValueLiveData<>(KbsPin.EMPTY);
|
||||
private final DefaultValueLiveData<PinKeyboardType> keyboard = new DefaultValueLiveData<>(PinKeyboardType.NUMERIC);
|
||||
private final DefaultValueLiveData<SaveAnimation> saveAnimation = new DefaultValueLiveData<>(SaveAnimation.NONE);
|
||||
private final DefaultValueLiveData<Label> label = new DefaultValueLiveData<>(Label.RE_ENTER_PIN);
|
||||
private final DefaultValueLiveData<SaveAnimation> saveAnimation = new DefaultValueLiveData<>(SaveAnimation.NONE);
|
||||
private final DefaultValueLiveData<LabelState> label = new DefaultValueLiveData<>(LabelState.EMPTY);
|
||||
|
||||
private final KbsPin pinToConfirm;
|
||||
|
||||
@@ -35,29 +35,25 @@ final class ConfirmKbsPinViewModel extends ViewModel implements BaseKbsPinViewMo
|
||||
return Transformations.distinctUntilChanged(saveAnimation);
|
||||
}
|
||||
|
||||
LiveData<Label> getLabel() {
|
||||
LiveData<LabelState> getLabel() {
|
||||
return Transformations.distinctUntilChanged(label);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void confirm() {
|
||||
KbsPin userEntry = this.userEntry.getValue();
|
||||
this.userEntry.setValue(KbsPin.EMPTY);
|
||||
|
||||
if (pinToConfirm.toString().equals(userEntry.toString())) {
|
||||
this.label.setValue(Label.CREATING_PIN);
|
||||
this.label.setValue(LabelState.CREATING_PIN);
|
||||
this.saveAnimation.setValue(SaveAnimation.LOADING);
|
||||
|
||||
repository.setPin(pinToConfirm, this.keyboard.getValue(), this::handleResult);
|
||||
} else {
|
||||
this.label.setValue(Label.PIN_DOES_NOT_MATCH);
|
||||
this.userEntry.setValue(KbsPin.EMPTY);
|
||||
this.label.setValue(LabelState.PIN_DOES_NOT_MATCH);
|
||||
}
|
||||
}
|
||||
|
||||
void onLoadingAnimationComplete() {
|
||||
this.label.setValue(Label.EMPTY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LiveData<KbsPin> getUserEntry() {
|
||||
return userEntry;
|
||||
@@ -91,7 +87,7 @@ final class ConfirmKbsPinViewModel extends ViewModel implements BaseKbsPinViewMo
|
||||
}
|
||||
}
|
||||
|
||||
enum Label {
|
||||
enum LabelState {
|
||||
RE_ENTER_PIN,
|
||||
PIN_DOES_NOT_MATCH,
|
||||
CREATING_PIN,
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
package org.thoughtcrime.securesms.lock.v2;
|
||||
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.TranslateAnimation;
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.PluralsRes;
|
||||
import androidx.autofill.HintConstants;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.util.SpanUtil;
|
||||
import org.thoughtcrime.securesms.util.navigation.SafeNavigation;
|
||||
|
||||
public class CreateKbsPinFragment extends BaseKbsPinFragment<CreateKbsPinViewModel> {
|
||||
|
||||
@Override
|
||||
protected void initializeViewStates() {
|
||||
CreateKbsPinFragmentArgs args = CreateKbsPinFragmentArgs.fromBundle(requireArguments());
|
||||
|
||||
if (args.getIsPinChange()) {
|
||||
initializeViewStatesForPinChange(args.getIsForgotPin());
|
||||
} else {
|
||||
initializeViewStatesForPinCreate();
|
||||
}
|
||||
|
||||
getLabel().setText(getPinLengthRestrictionText(R.plurals.CreateKbsPinFragment__pin_must_be_at_least_digits));
|
||||
getConfirm().setEnabled(false);
|
||||
ViewCompat.setAutofillHints(getInput(), HintConstants.AUTOFILL_HINT_NEW_PASSWORD);
|
||||
}
|
||||
|
||||
private void initializeViewStatesForPinChange(boolean isForgotPin) {
|
||||
getTitle().setText(R.string.CreateKbsPinFragment__create_a_new_pin);
|
||||
|
||||
getDescription().setText(R.string.CreateKbsPinFragment__you_can_choose_a_new_pin_as_long_as_this_device_is_registered);
|
||||
getDescription().setLearnMoreVisible(true);
|
||||
}
|
||||
|
||||
private void initializeViewStatesForPinCreate() {
|
||||
getTitle().setText(R.string.CreateKbsPinFragment__create_your_pin);
|
||||
getDescription().setText(R.string.CreateKbsPinFragment__pins_keep_information_stored_with_signal_encrypted);
|
||||
getDescription().setLearnMoreVisible(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CreateKbsPinViewModel initializeViewModel() {
|
||||
CreateKbsPinViewModel viewModel = new ViewModelProvider(this).get(CreateKbsPinViewModel.class);
|
||||
CreateKbsPinFragmentArgs args = CreateKbsPinFragmentArgs.fromBundle(requireArguments());
|
||||
|
||||
viewModel.getNavigationEvents().observe(getViewLifecycleOwner(), e -> onConfirmPin(e.getUserEntry(), e.getKeyboard(), args.getIsPinChange()));
|
||||
viewModel.getErrorEvents().observe(getViewLifecycleOwner(), e -> {
|
||||
if (e == CreateKbsPinViewModel.PinErrorEvent.WEAK_PIN) {
|
||||
getLabel().setText(SpanUtil.color(ContextCompat.getColor(requireContext(), R.color.red_500),
|
||||
getString(R.string.CreateKbsPinFragment__choose_a_stronger_pin)));
|
||||
shake(getInput(), () -> getInput().getText().clear());
|
||||
} else {
|
||||
throw new AssertionError("Unexpected PIN error!");
|
||||
}
|
||||
});
|
||||
viewModel.getKeyboard().observe(getViewLifecycleOwner(), k -> {
|
||||
getLabel().setText(getLabelText(k));
|
||||
getInput().getText().clear();
|
||||
});
|
||||
|
||||
return viewModel;
|
||||
}
|
||||
|
||||
private void onConfirmPin(@NonNull KbsPin userEntry, @NonNull PinKeyboardType keyboard, boolean isPinChange) {
|
||||
CreateKbsPinFragmentDirections.ActionConfirmPin action = CreateKbsPinFragmentDirections.actionConfirmPin();
|
||||
|
||||
action.setUserEntry(userEntry);
|
||||
action.setKeyboard(keyboard);
|
||||
action.setIsPinChange(isPinChange);
|
||||
|
||||
SafeNavigation.safeNavigate(Navigation.findNavController(requireView()), action);
|
||||
}
|
||||
|
||||
private String getLabelText(@NonNull PinKeyboardType keyboard) {
|
||||
if (keyboard == PinKeyboardType.ALPHA_NUMERIC) {
|
||||
return getPinLengthRestrictionText(R.plurals.CreateKbsPinFragment__pin_must_be_at_least_characters);
|
||||
} else {
|
||||
return getPinLengthRestrictionText(R.plurals.CreateKbsPinFragment__pin_must_be_at_least_digits);
|
||||
}
|
||||
}
|
||||
|
||||
private String getPinLengthRestrictionText(@PluralsRes int plurals) {
|
||||
return requireContext().getResources().getQuantityString(plurals, KbsConstants.MINIMUM_PIN_LENGTH, KbsConstants.MINIMUM_PIN_LENGTH);
|
||||
}
|
||||
|
||||
private static void shake(@NonNull EditText view, @NonNull Runnable afterwards) {
|
||||
TranslateAnimation shake = new TranslateAnimation(0, 30, 0, 0);
|
||||
shake.setDuration(50);
|
||||
shake.setRepeatCount(7);
|
||||
shake.setAnimationListener(new Animation.AnimationListener() {
|
||||
@Override
|
||||
public void onAnimationStart(Animation animation) {}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animation animation) {
|
||||
afterwards.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationRepeat(Animation animation) {}
|
||||
});
|
||||
view.startAnimation(shake);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package org.thoughtcrime.securesms.lock.v2
|
||||
|
||||
import android.view.animation.Animation
|
||||
import android.view.animation.TranslateAnimation
|
||||
import android.widget.EditText
|
||||
import androidx.annotation.PluralsRes
|
||||
import androidx.autofill.HintConstants
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.navigation.Navigation.findNavController
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.lock.v2.CreateKbsPinViewModel.NavigationEvent
|
||||
import org.thoughtcrime.securesms.lock.v2.CreateKbsPinViewModel.PinErrorEvent
|
||||
import org.thoughtcrime.securesms.util.SpanUtil
|
||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||
|
||||
class CreateKbsPinFragment : BaseKbsPinFragment<CreateKbsPinViewModel?>() {
|
||||
override fun initializeViewStates() {
|
||||
val args = CreateKbsPinFragmentArgs.fromBundle(requireArguments())
|
||||
if (args.isPinChange) {
|
||||
initializeViewStatesForPinChange(args.isForgotPin)
|
||||
} else {
|
||||
initializeViewStatesForPinCreate()
|
||||
}
|
||||
label.text = getPinLengthRestrictionText(R.plurals.CreateKbsPinFragment__pin_must_be_at_least_digits)
|
||||
confirm.isEnabled = false
|
||||
ViewCompat.setAutofillHints(input, HintConstants.AUTOFILL_HINT_NEW_PASSWORD)
|
||||
}
|
||||
|
||||
private fun initializeViewStatesForPinChange(isForgotPin: Boolean) {
|
||||
title.setText(R.string.CreateKbsPinFragment__create_a_new_pin)
|
||||
description.setText(R.string.CreateKbsPinFragment__you_can_choose_a_new_pin_as_long_as_this_device_is_registered)
|
||||
description.setLearnMoreVisible(true)
|
||||
}
|
||||
|
||||
private fun initializeViewStatesForPinCreate() {
|
||||
title.setText(R.string.CreateKbsPinFragment__create_your_pin)
|
||||
description.setText(R.string.CreateKbsPinFragment__pins_can_help_you_restore_your_account)
|
||||
description.setLearnMoreVisible(true)
|
||||
}
|
||||
|
||||
override fun initializeViewModel(): CreateKbsPinViewModel {
|
||||
val viewModel = ViewModelProvider(this)[CreateKbsPinViewModel::class.java]
|
||||
val args = CreateKbsPinFragmentArgs.fromBundle(requireArguments())
|
||||
viewModel.navigationEvents.observe(viewLifecycleOwner) { e: NavigationEvent -> onConfirmPin(e.userEntry, e.keyboard, args.isPinChange) }
|
||||
viewModel.errorEvents.observe(viewLifecycleOwner) { e: PinErrorEvent ->
|
||||
if (e == PinErrorEvent.WEAK_PIN) {
|
||||
label.text = SpanUtil.color(
|
||||
ContextCompat.getColor(requireContext(), R.color.red_500),
|
||||
getString(R.string.CreateKbsPinFragment__choose_a_stronger_pin)
|
||||
)
|
||||
shake(input) { input.text.clear() }
|
||||
} else {
|
||||
throw AssertionError("Unexpected PIN error!")
|
||||
}
|
||||
}
|
||||
viewModel.keyboard.observe(viewLifecycleOwner) { k: PinKeyboardType ->
|
||||
label.text = getLabelText(k)
|
||||
input.text.clear()
|
||||
}
|
||||
return viewModel
|
||||
}
|
||||
|
||||
private fun onConfirmPin(userEntry: KbsPin, keyboard: PinKeyboardType, isPinChange: Boolean) {
|
||||
val action = CreateKbsPinFragmentDirections.actionConfirmPin()
|
||||
action.userEntry = userEntry
|
||||
action.keyboard = keyboard
|
||||
action.isPinChange = isPinChange
|
||||
findNavController(requireView()).safeNavigate(action)
|
||||
}
|
||||
|
||||
private fun getLabelText(keyboard: PinKeyboardType): String {
|
||||
return if (keyboard == PinKeyboardType.ALPHA_NUMERIC) {
|
||||
getPinLengthRestrictionText(R.plurals.CreateKbsPinFragment__pin_must_be_at_least_characters)
|
||||
} else {
|
||||
getPinLengthRestrictionText(R.plurals.CreateKbsPinFragment__pin_must_be_at_least_digits)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getPinLengthRestrictionText(@PluralsRes plurals: Int): String {
|
||||
return resources.getQuantityString(plurals, KbsConstants.MINIMUM_PIN_LENGTH, KbsConstants.MINIMUM_PIN_LENGTH)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private fun shake(view: EditText, afterwards: Runnable) {
|
||||
val shake = TranslateAnimation(0F, 30F, 0F, 0F)
|
||||
shake.duration = 50
|
||||
shake.repeatCount = 7
|
||||
shake.setAnimationListener(object : Animation.AnimationListener {
|
||||
override fun onAnimationStart(animation: Animation) {}
|
||||
override fun onAnimationEnd(animation: Animation) {
|
||||
afterwards.run()
|
||||
}
|
||||
|
||||
override fun onAnimationRepeat(animation: Animation) {}
|
||||
})
|
||||
view.startAnimation(shake)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user