diff --git a/app/src/main/java/com/m2049r/xmrwallet/GenerateFragment.java b/app/src/main/java/com/m2049r/xmrwallet/GenerateFragment.java index b35edac..fd6da0b 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/GenerateFragment.java +++ b/app/src/main/java/com/m2049r/xmrwallet/GenerateFragment.java @@ -50,9 +50,8 @@ import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.KeyStoreHelper; import com.m2049r.xmrwallet.util.RestoreHeight; import com.m2049r.xmrwallet.util.ledger.Monero; +import com.m2049r.xmrwallet.widget.PasswordEntryView; import com.m2049r.xmrwallet.widget.Toolbar; -import com.nulabinc.zxcvbn.Strength; -import com.nulabinc.zxcvbn.Zxcvbn; import java.io.File; import java.text.ParseException; @@ -70,7 +69,7 @@ public class GenerateFragment extends Fragment { static final String TYPE_VIEWONLY = "view"; private TextInputLayout etWalletName; - private TextInputLayout etWalletPassword; + private PasswordEntryView etWalletPassword; private LinearLayout llFingerprintAuth; private TextInputLayout etWalletAddress; private TextInputLayout etWalletMnemonic; @@ -131,21 +130,6 @@ public class GenerateFragment extends Fragment { }); clearErrorOnTextEntry(etWalletName); - etWalletPassword.getEditText().addTextChangedListener(new TextWatcher() { - @Override - public void afterTextChanged(Editable editable) { - checkPassword(); - } - - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - } - }); - etWalletMnemonic.getEditText().setOnFocusChangeListener(new View.OnFocusChangeListener() { @Override public void onFocusChange(View v, boolean hasFocus) { @@ -354,45 +338,10 @@ public class GenerateFragment extends Fragment { }); etWalletName.requestFocus(); - initZxcvbn(); return view; } - Zxcvbn zxcvbn = new Zxcvbn(); - - // initialize zxcvbn engine in background thread - private void initZxcvbn() { - new Thread(new Runnable() { - @Override - public void run() { - zxcvbn.measure(""); - } - }).start(); - } - - private void checkPassword() { - String password = etWalletPassword.getEditText().getText().toString(); - if (!password.isEmpty()) { - Strength strength = zxcvbn.measure(password); - int msg; - double guessesLog10 = strength.getGuessesLog10(); - if (guessesLog10 < 10) - msg = R.string.password_weak; - else if (guessesLog10 < 11) - msg = R.string.password_fair; - else if (guessesLog10 < 12) - msg = R.string.password_good; - else if (guessesLog10 < 13) - msg = R.string.password_strong; - else - msg = R.string.password_very_strong; - etWalletPassword.setError(getResources().getString(msg)); - } else { - etWalletPassword.setError(null); - } - } - private boolean checkName() { String name = etWalletName.getEditText().getText().toString(); boolean ok = true; @@ -654,24 +603,7 @@ public class GenerateFragment extends Fragment { final TextInputLayout etSeed = promptsView.findViewById(R.id.etSeed); final TextInputLayout etPassphrase = promptsView.findViewById(R.id.etPassphrase); - etSeed.getEditText().addTextChangedListener(new TextWatcher() { - @Override - public void afterTextChanged(Editable s) { - if (etSeed.getError() != null) { - etSeed.setError(null); - } - } - - @Override - public void beforeTextChanged(CharSequence s, int start, - int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, - int before, int count) { - } - }); + clearErrorOnTextEntry(etSeed); alertDialogBuilder .setCancelable(false) diff --git a/app/src/main/java/com/m2049r/xmrwallet/GenerateReviewFragment.java b/app/src/main/java/com/m2049r/xmrwallet/GenerateReviewFragment.java index d65d8c1..310e318 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/GenerateReviewFragment.java +++ b/app/src/main/java/com/m2049r/xmrwallet/GenerateReviewFragment.java @@ -56,6 +56,7 @@ import com.m2049r.xmrwallet.util.FingerprintHelper; import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.KeyStoreHelper; import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor; +import com.m2049r.xmrwallet.widget.PasswordEntryView; import com.m2049r.xmrwallet.widget.Toolbar; import java.text.NumberFormat; @@ -473,7 +474,7 @@ public class GenerateReviewFragment extends Fragment { AlertDialog.Builder alertDialogBuilder = new MaterialAlertDialogBuilder(getActivity()); alertDialogBuilder.setView(promptsView); - final TextInputLayout etPasswordA = promptsView.findViewById(R.id.etWalletPasswordA); + final PasswordEntryView etPasswordA = promptsView.findViewById(R.id.etWalletPasswordA); etPasswordA.setHint(getString(R.string.prompt_changepw, walletName)); final TextInputLayout etPasswordB = promptsView.findViewById(R.id.etWalletPasswordB); @@ -509,9 +510,6 @@ public class GenerateReviewFragment extends Fragment { etPasswordA.getEditText().addTextChangedListener(new TextWatcher() { @Override public void afterTextChanged(Editable s) { - if (etPasswordA.getError() != null) { - etPasswordA.setError(null); - } if (etPasswordB.getError() != null) { etPasswordB.setError(null); } @@ -531,9 +529,6 @@ public class GenerateReviewFragment extends Fragment { etPasswordB.getEditText().addTextChangedListener(new TextWatcher() { @Override public void afterTextChanged(Editable s) { - if (etPasswordA.getError() != null) { - etPasswordA.setError(null); - } if (etPasswordB.getError() != null) { etPasswordB.setError(null); } @@ -564,29 +559,20 @@ public class GenerateReviewFragment extends Fragment { }); openDialog = alertDialogBuilder.create(); - openDialog.setOnShowListener(new DialogInterface.OnShowListener() { - @Override - public void onShow(final DialogInterface dialog) { - Button button = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE); - button.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - String newPasswordA = etPasswordA.getEditText().getText().toString(); - String newPasswordB = etPasswordB.getEditText().getText().toString(); - // disallow empty passwords - if (newPasswordA.isEmpty()) { - etPasswordA.setError(getString(R.string.generate_empty_passwordB)); - } else if (!newPasswordA.equals(newPasswordB)) { - etPasswordB.setError(getString(R.string.generate_bad_passwordB)); - } else { - new AsyncChangePassword().execute(newPasswordA, Boolean.toString(swFingerprintAllowed.isChecked())); - Helper.hideKeyboardAlways(getActivity()); - openDialog.dismiss(); - openDialog = null; - } - } - }); - } + openDialog.setOnShowListener(dialog -> { + Button button = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE); + button.setOnClickListener(view -> { + String newPasswordA = etPasswordA.getEditText().getText().toString(); + String newPasswordB = etPasswordB.getEditText().getText().toString(); + if (!newPasswordA.equals(newPasswordB)) { + etPasswordB.setError(getString(R.string.generate_bad_passwordB)); + } else { + new AsyncChangePassword().execute(newPasswordA, Boolean.toString(swFingerprintAllowed.isChecked())); + Helper.hideKeyboardAlways(getActivity()); + openDialog.dismiss(); + openDialog = null; + } + }); }); // accept keyboard "ok" @@ -597,9 +583,7 @@ public class GenerateReviewFragment extends Fragment { String newPasswordA = etPasswordA.getEditText().getText().toString(); String newPasswordB = etPasswordB.getEditText().getText().toString(); // disallow empty passwords - if (newPasswordA.isEmpty()) { - etPasswordA.setError(getString(R.string.generate_empty_passwordB)); - } else if (!newPasswordA.equals(newPasswordB)) { + if (!newPasswordA.equals(newPasswordB)) { etPasswordB.setError(getString(R.string.generate_bad_passwordB)); } else { new AsyncChangePassword().execute(newPasswordA, Boolean.toString(swFingerprintAllowed.isChecked())); diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/Helper.java b/app/src/main/java/com/m2049r/xmrwallet/util/Helper.java index 66a3704..b5070dc 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/util/Helper.java +++ b/app/src/main/java/com/m2049r/xmrwallet/util/Helper.java @@ -59,9 +59,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.textfield.TextInputLayout; import com.m2049r.xmrwallet.BuildConfig; import com.m2049r.xmrwallet.R; -import com.m2049r.xmrwallet.model.NetworkType; import com.m2049r.xmrwallet.model.WalletManager; -import com.m2049r.xmrwallet.service.exchange.api.ExchangeApi; import java.io.File; import java.io.IOException; @@ -77,7 +75,6 @@ import java.util.concurrent.atomic.AtomicBoolean; import javax.net.ssl.HttpsURLConnection; -import okhttp3.HttpUrl; import timber.log.Timber; public class Helper { diff --git a/app/src/main/java/com/m2049r/xmrwallet/widget/PasswordEntryView.java b/app/src/main/java/com/m2049r/xmrwallet/widget/PasswordEntryView.java new file mode 100644 index 0000000..6d5fee1 --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/widget/PasswordEntryView.java @@ -0,0 +1,73 @@ +package com.m2049r.xmrwallet.widget; + +import android.content.Context; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.google.android.material.textfield.TextInputLayout; +import com.m2049r.xmrwallet.R; +import com.nulabinc.zxcvbn.Zxcvbn; + +public class PasswordEntryView extends TextInputLayout implements TextWatcher { + final private Zxcvbn zxcvbn = new Zxcvbn(); + + public PasswordEntryView(@NonNull Context context) { + super(context, null); + } + + public PasswordEntryView(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs, R.attr.textInputStyle); + } + + public PasswordEntryView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + public void addView(@NonNull View child, int index, @NonNull final ViewGroup.LayoutParams params) { + super.addView(child, index, params); + final EditText et = getEditText(); + if (et != null) + et.addTextChangedListener(this); + } + + @Override + public void afterTextChanged(Editable s) { + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + String password = s.toString(); + int icon = 0; + if (!password.isEmpty()) { + final double strength = Math.min(zxcvbn.measure(password).getGuessesLog10(), 15) / 3 * 20; // 0-100% + if (strength < 21) + icon = R.drawable.ic_smiley_sad_filled; + else if (strength < 40) + icon = R.drawable.ic_smiley_meh_filled; + else if (strength < 60) + icon = R.drawable.ic_smiley_neutral_filled; + else if (strength < 80) + icon = R.drawable.ic_smiley_happy_filled; + else if (strength < 99) + icon = R.drawable.ic_smiley_ecstatic_filled; + else + icon = R.drawable.ic_smiley_gunther_filled; + } + setErrorIconDrawable(icon); + if (icon != 0) + setError(" "); + else setError(null); + } +} diff --git a/app/src/main/res/drawable/ic_smiley_ecstatic_filled.xml b/app/src/main/res/drawable/ic_smiley_ecstatic_filled.xml new file mode 100644 index 0000000..5599f40 --- /dev/null +++ b/app/src/main/res/drawable/ic_smiley_ecstatic_filled.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_smiley_gunther_filled.xml b/app/src/main/res/drawable/ic_smiley_gunther_filled.xml new file mode 100644 index 0000000..b984c88 --- /dev/null +++ b/app/src/main/res/drawable/ic_smiley_gunther_filled.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_smiley_happy_filled.xml b/app/src/main/res/drawable/ic_smiley_happy_filled.xml new file mode 100644 index 0000000..2a90da4 --- /dev/null +++ b/app/src/main/res/drawable/ic_smiley_happy_filled.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_smiley_meh_filled.xml b/app/src/main/res/drawable/ic_smiley_meh_filled.xml new file mode 100644 index 0000000..057e41e --- /dev/null +++ b/app/src/main/res/drawable/ic_smiley_meh_filled.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_smiley_neutral_filled.xml b/app/src/main/res/drawable/ic_smiley_neutral_filled.xml new file mode 100644 index 0000000..13cff5b --- /dev/null +++ b/app/src/main/res/drawable/ic_smiley_neutral_filled.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_smiley_sad_filled.xml b/app/src/main/res/drawable/ic_smiley_sad_filled.xml new file mode 100644 index 0000000..db30f3e --- /dev/null +++ b/app/src/main/res/drawable/ic_smiley_sad_filled.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/fragment_generate.xml b/app/src/main/res/layout/fragment_generate.xml index a3fa20b..20dbad7 100644 --- a/app/src/main/res/layout/fragment_generate.xml +++ b/app/src/main/res/layout/fragment_generate.xml @@ -29,13 +29,17 @@ android:textAlignment="textStart" /> - + app:boxStrokeErrorColor="@color/monerujoPrimary" + app:errorEnabled="true" + app:errorIconDrawable="@drawable/ic_smiley_gunther_filled" + app:errorIconTint="@color/monerujoPrimary" + app:errorTextColor="@color/monerujoPrimary"> - + - + app:boxStrokeErrorColor="@color/monerujoPrimary" + app:errorEnabled="true" + app:errorIconDrawable="@drawable/ic_smiley_gunther_filled" + app:errorIconTint="@color/monerujoPrimary" + app:errorTextColor="@color/monerujoPrimary"> - +