From 54c35e346e81212ccbb781a409b6caba33587142 Mon Sep 17 00:00:00 2001 From: Peter Hinson Date: Tue, 3 Mar 2015 00:56:49 -0800 Subject: [PATCH 1/4] Droneshare login page UX tweaks. Soft input is now hidden on login/account creation - it'd occasionally persist after login screen was dismissed. --- .../account/DroneshareLoginFragment.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Android/src/org/droidplanner/android/fragments/account/DroneshareLoginFragment.java b/Android/src/org/droidplanner/android/fragments/account/DroneshareLoginFragment.java index 641c7b5776..d4c71d52ae 100644 --- a/Android/src/org/droidplanner/android/fragments/account/DroneshareLoginFragment.java +++ b/Android/src/org/droidplanner/android/fragments/account/DroneshareLoginFragment.java @@ -2,6 +2,7 @@ import android.app.Activity; import android.app.ProgressDialog; +import android.content.Context; import android.os.AsyncTask; import android.os.Bundle; import android.support.v4.app.Fragment; @@ -9,6 +10,7 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; @@ -77,6 +79,8 @@ public void onViewCreated(View root, Bundle savedInstanceState) { public void onClick(View v) { final String usernameText = username.getText().toString(); final String passwordText = password.getText().toString(); + // Hide the soft keyboard, otherwise can remain after logging in. + hideSoftInput(); new AsyncConnect(false).execute(usernameText, passwordText); } }); @@ -99,6 +103,10 @@ public void onClick(View v) { final String usernameText = username.getText().toString(); final String passwordText = password.getText().toString(); final String emailText = email.getText().toString(); + + // Hide the soft keyboard, otherwise can remain after logging in. + hideSoftInput(); + new AsyncConnect(true).execute(usernameText, passwordText, emailText); } }); @@ -159,6 +167,17 @@ public void onClick(View v) { // GAUtils.sendEvent(eventBuilder); // } + private void hideSoftInput() { + // Get currently focused view and hide soft input. + final Activity activity = getActivity(); + View view = activity.getCurrentFocus(); + if (view != null) { + final InputMethodManager inputManager = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE); + if (inputManager != null) + inputManager.hideSoftInputFromWindow(view.getWindowToken(), 0); + } + } + private class AsyncConnect extends AsyncTask> { private final boolean isSignup; From 2fdd4439b4c21f4b37cc7eb39829e5e22beaa609 Mon Sep 17 00:00:00 2001 From: Peter Hinson Date: Tue, 3 Mar 2015 18:59:56 -0800 Subject: [PATCH 2/4] Added basic field validation to Droneshare login page. Error icon and message are added to fields missing values, login can only proceed once all fields have been entered. --- .../account/DroneshareLoginFragment.java | 89 ++++++++++++++++--- 1 file changed, 77 insertions(+), 12 deletions(-) diff --git a/Android/src/org/droidplanner/android/fragments/account/DroneshareLoginFragment.java b/Android/src/org/droidplanner/android/fragments/account/DroneshareLoginFragment.java index d4c71d52ae..e1edf1de46 100644 --- a/Android/src/org/droidplanner/android/fragments/account/DroneshareLoginFragment.java +++ b/Android/src/org/droidplanner/android/fragments/account/DroneshareLoginFragment.java @@ -7,6 +7,8 @@ import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.util.Pair; +import android.text.Editable; +import android.text.TextWatcher; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -77,11 +79,15 @@ public void onViewCreated(View root, Bundle savedInstanceState) { loginButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - final String usernameText = username.getText().toString(); - final String passwordText = password.getText().toString(); - // Hide the soft keyboard, otherwise can remain after logging in. - hideSoftInput(); - new AsyncConnect(false).execute(usernameText, passwordText); + // Validate required fields + if (validateField(username) && validateField(password)) { + final String usernameText = username.getText().toString(); + final String passwordText = password.getText().toString(); + + // Hide the soft keyboard, otherwise can remain after logging in. + hideSoftInput(); + new AsyncConnect(false).execute(usernameText, passwordText); + } } }); @@ -100,14 +106,17 @@ public void onClick(View v) { signupButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - final String usernameText = username.getText().toString(); - final String passwordText = password.getText().toString(); - final String emailText = email.getText().toString(); + // If we have all required fields... + if (validateField(username) && validateField(password) && validateField(email)) { + final String usernameText = username.getText().toString(); + final String passwordText = password.getText().toString(); + final String emailText = email.getText().toString(); - // Hide the soft keyboard, otherwise can remain after logging in. - hideSoftInput(); + // Hide the soft keyboard, otherwise can remain after logging in. + hideSoftInput(); - new AsyncConnect(true).execute(usernameText, passwordText, emailText); + new AsyncConnect(true).execute(usernameText, passwordText, emailText); + } } }); @@ -124,6 +133,29 @@ public void onClick(View v) { password.setText(prefs.getDronesharePassword()); email.setText(prefs.getDroneshareEmail()); + // Field validation - we need to add these after the fields have been set + // otherwise validation will fail on empty fields (ugly). + username.addTextChangedListener(new TextValidator(username) { + @Override public void validate(TextView textView, String text) { + // TODO: validate on acceptable characters, etc + if(text.length() == 0) textView.setError("Please enter a username"); + } + }); + + password.addTextChangedListener(new TextValidator(password) { + @Override public void validate(TextView textView, String text) { + // TODO: check for valid length, characters + if(text.length() == 0) textView.setError("Please enter a password"); + } + }); + + email.addTextChangedListener(new TextValidator(email) { + @Override public void validate(TextView textView, String text) { + // TODO: validate on acceptable characters, etc + if(text.length() == 0) textView.setError("Please enter an email"); + } + }); + // Save to prefs on save // builder.setView(root) // // Add action buttons @@ -168,7 +200,7 @@ public void onClick(View v) { // } private void hideSoftInput() { - // Get currently focused view and hide soft input. + // Hide the soft keyboard and unfocus any active inputs. final Activity activity = getActivity(); View view = activity.getCurrentFocus(); if (view != null) { @@ -178,6 +210,39 @@ private void hideSoftInput() { } } + private Boolean validateField(EditText field) { + // Trigger text changed to see if we have any errors: + field.setText(field.getText()); + if (field.getError() != null) { + return false; // Invalid, the text field will display an error + } else { + return true; // Valid + } + } + + public abstract class TextValidator implements TextWatcher { + // Wrapper for TextWatcher, providing a shorthand method of field specific validations + private final TextView textView; + + public TextValidator(TextView textView) { + this.textView = textView; + } + + public abstract void validate(TextView textView, String text); + + @Override + final public void afterTextChanged(Editable s) { + String text = textView.getText().toString(); + validate(textView, text); + } + + @Override + final public void beforeTextChanged(CharSequence s, int start, int count, int after) { /* Don't care */ } + + @Override + final public void onTextChanged(CharSequence s, int start, int before, int count) { /* Don't care */ } + } + private class AsyncConnect extends AsyncTask> { private final boolean isSignup; From fb06697e18bc8adc16ec62683aa16d60f1b11300 Mon Sep 17 00:00:00 2001 From: Peter Hinson Date: Tue, 3 Mar 2015 20:12:31 -0800 Subject: [PATCH 3/4] Email and password validation. New accounts must have passwords >= 7 characters, with 1 digit. Login allows abritary passwords, since existing accounts might not meet this requirement. --- .../account/DroneshareLoginFragment.java | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/Android/src/org/droidplanner/android/fragments/account/DroneshareLoginFragment.java b/Android/src/org/droidplanner/android/fragments/account/DroneshareLoginFragment.java index e1edf1de46..2974b3e721 100644 --- a/Android/src/org/droidplanner/android/fragments/account/DroneshareLoginFragment.java +++ b/Android/src/org/droidplanner/android/fragments/account/DroneshareLoginFragment.java @@ -28,7 +28,7 @@ public class DroneshareLoginFragment extends Fragment { private static final String DRONESHARE_PROMPT_ACTION = "droneshare_prompt"; - + private static final short DRONESHARE_MIN_PASSWORD = 7; private final DroneshareClient dshareClient = new DroneshareClient(); private DroidPlannerPrefs prefs; @@ -138,21 +138,34 @@ public void onClick(View v) { username.addTextChangedListener(new TextValidator(username) { @Override public void validate(TextView textView, String text) { // TODO: validate on acceptable characters, etc - if(text.length() == 0) textView.setError("Please enter a username"); + if(text.length() == 0) + textView.setError("Please enter a username"); + else + textView.setError(null); } }); password.addTextChangedListener(new TextValidator(password) { @Override public void validate(TextView textView, String text) { - // TODO: check for valid length, characters - if(text.length() == 0) textView.setError("Please enter a password"); + if(loginSection.getVisibility() == View.VISIBLE && ( + text.length() < 1)) + // Since some accounts have been created with < 7 and no digits, allow login + textView.setError("Please enter a password"); + else if(loginSection.getVisibility() == View.GONE && ( + text.length() < DRONESHARE_MIN_PASSWORD || !text.matches(".*\\d+.*"))) + // New accounts require at least 7 characters and digit + textView.setError("Use at least 7 characters and one digit"); + else + textView.setError(null); } }); email.addTextChangedListener(new TextValidator(email) { @Override public void validate(TextView textView, String text) { - // TODO: validate on acceptable characters, etc - if(text.length() == 0) textView.setError("Please enter an email"); + if(text.isEmpty() || !android.util.Patterns.EMAIL_ADDRESS.matcher(text).matches()) + textView.setError("Please enter a valid email"); + else + textView.setError(null); } }); From 95d2594a67b07c1c91b21663215961602d60983d Mon Sep 17 00:00:00 2001 From: Peter Hinson Date: Wed, 4 Mar 2015 12:49:06 -0800 Subject: [PATCH 4/4] TextValidator class set as private static. --- .../android/fragments/account/DroneshareLoginFragment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Android/src/org/droidplanner/android/fragments/account/DroneshareLoginFragment.java b/Android/src/org/droidplanner/android/fragments/account/DroneshareLoginFragment.java index 2974b3e721..c01da71982 100644 --- a/Android/src/org/droidplanner/android/fragments/account/DroneshareLoginFragment.java +++ b/Android/src/org/droidplanner/android/fragments/account/DroneshareLoginFragment.java @@ -233,7 +233,7 @@ private Boolean validateField(EditText field) { } } - public abstract class TextValidator implements TextWatcher { + private static abstract class TextValidator implements TextWatcher { // Wrapper for TextWatcher, providing a shorthand method of field specific validations private final TextView textView;