Redesign FTUX to use Material Design 3.

This commit is contained in:
Nicholas
2023-01-23 16:30:57 -05:00
committed by Greyson Parrelli
parent 0303467c91
commit 150bbf181d
25 changed files with 842 additions and 918 deletions

View File

@@ -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();
}

View File

@@ -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);
}
}

View File

@@ -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)
}
}

View File

@@ -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,

View File

@@ -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);
}
}

View File

@@ -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)
}
}
}