| @@ -0,0 +1,58 @@ | ||
| package soilsmart.soilsmartapp; | ||
|
|
||
| import android.content.Context; | ||
| import android.content.SharedPreferences; | ||
|
|
||
| import com.google.gson.Gson; | ||
| import com.google.gson.reflect.TypeToken; | ||
|
|
||
| import java.lang.reflect.Type; | ||
| import java.util.ArrayList; | ||
|
|
||
| /** | ||
| * SoilSmartApp | ||
| * Created by Ricardo Morones on 2/27/16. | ||
| */ | ||
| public class UserLocalStore { | ||
| final static public String SP_NAME = "userDetails"; | ||
| final SharedPreferences userLocalDatabase; | ||
| final Gson gson; | ||
|
|
||
| public UserLocalStore(Context context) { | ||
| userLocalDatabase = context.getSharedPreferences(SP_NAME, 0); | ||
| gson = new Gson(); | ||
| } | ||
|
|
||
| public void storeUserData(User user) { | ||
| SharedPreferences.Editor spEditor = userLocalDatabase.edit(); | ||
| spEditor.putString("email", user.getEmail()); | ||
| spEditor.putString("password", user.getPasswordHash()); | ||
| spEditor.putString("nodes", gson.toJson(user.getNodes())); | ||
| spEditor.apply(); | ||
| } | ||
|
|
||
| public User getLoggedInUser() { | ||
| final String email = userLocalDatabase.getString("email", ""); | ||
| final String password = userLocalDatabase.getString("password", ""); | ||
| final String nodesJson = userLocalDatabase.getString("nodes", ""); | ||
| final Type type = new TypeToken<ArrayList<SoilSmartNode>>() {}.getType(); | ||
| ArrayList<SoilSmartNode> nodes = gson.fromJson(nodesJson, type); | ||
| return new User(email, password, nodes); | ||
| } | ||
|
|
||
| public void setUserLoggedIn(boolean loggedIn) { | ||
| SharedPreferences.Editor spEditor = userLocalDatabase.edit(); | ||
| spEditor.putBoolean("loggedIn", loggedIn); | ||
| spEditor.apply(); | ||
| } | ||
|
|
||
| public boolean getUserLoggedIn() { | ||
| return userLocalDatabase.getBoolean("loggedIn", false); | ||
| } | ||
|
|
||
| public void clearUserData() { | ||
| SharedPreferences.Editor spEditor = userLocalDatabase.edit(); | ||
| spEditor.clear(); | ||
| spEditor.apply(); | ||
| } | ||
| } |
| @@ -0,0 +1,390 @@ | ||
| package soilsmart.soilsmartapp.views; | ||
|
|
||
| import android.animation.Animator; | ||
| import android.animation.AnimatorListenerAdapter; | ||
| import android.annotation.TargetApi; | ||
| import android.content.Intent; | ||
| import android.content.pm.PackageManager; | ||
| import android.support.annotation.NonNull; | ||
| import android.support.design.widget.Snackbar; | ||
| import android.support.v7.app.AppCompatActivity; | ||
| import android.app.LoaderManager.LoaderCallbacks; | ||
|
|
||
| import android.content.CursorLoader; | ||
| import android.content.Loader; | ||
| import android.database.Cursor; | ||
| import android.net.Uri; | ||
| import android.os.AsyncTask; | ||
|
|
||
| import android.os.Build; | ||
| import android.os.Bundle; | ||
| import android.provider.ContactsContract; | ||
| import android.text.TextUtils; | ||
| import android.view.KeyEvent; | ||
| import android.view.View; | ||
| import android.view.View.OnClickListener; | ||
| import android.view.inputmethod.EditorInfo; | ||
| import android.widget.ArrayAdapter; | ||
| import android.widget.AutoCompleteTextView; | ||
| import android.widget.Button; | ||
| import android.widget.EditText; | ||
| import android.widget.TextView; | ||
| import android.widget.Toast; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
|
|
||
| import soilsmart.soilsmartapp.R; | ||
| import soilsmart.soilsmartapp.SoilSmartService; | ||
| import soilsmart.soilsmartapp.User; | ||
| import soilsmart.soilsmartapp.UserLocalStore; | ||
|
|
||
| import static android.Manifest.permission.READ_CONTACTS; | ||
|
|
||
| /** | ||
| * A login screen that offers login via email/password. | ||
| */ | ||
| public class LoginActivity extends AppCompatActivity implements LoaderCallbacks<Cursor> { | ||
|
|
||
| /** | ||
| * Id to identity READ_CONTACTS permission request. | ||
| */ | ||
| private static final int REQUEST_READ_CONTACTS = 0; | ||
|
|
||
| /** | ||
| * Keep track of the login task to ensure we can cancel it if requested. | ||
| */ | ||
| private UserLoginTask mAuthTask = null; | ||
| private UserLocalStore userLocalStore; | ||
|
|
||
| // UI references. | ||
| private AutoCompleteTextView mEmailView; | ||
| private EditText mPasswordView; | ||
| private View mProgressView; | ||
| private View mLoginFormView; | ||
|
|
||
| @Override | ||
| protected void onCreate(Bundle savedInstanceState) { | ||
| super.onCreate(savedInstanceState); | ||
| setContentView(R.layout.activity_login); | ||
| // Set up the login form. | ||
| mEmailView = (AutoCompleteTextView) findViewById(R.id.email); | ||
| mEmailView.setOnEditorActionListener(new TextView.OnEditorActionListener() { | ||
| @Override | ||
| public boolean onEditorAction(final TextView v, final int actionId, final KeyEvent event) { | ||
| if (isEmailValid(v.getText().toString())) { | ||
| mPasswordView.requestFocus(); | ||
| return true; | ||
| } else { | ||
| mEmailView.setError(getString(R.string.error_invalid_email)); | ||
| } | ||
| return false; | ||
| } | ||
| }); | ||
| populateAutoComplete(); | ||
|
|
||
| mPasswordView = (EditText) findViewById(R.id.password); | ||
| mPasswordView.setOnEditorActionListener(new TextView.OnEditorActionListener() { | ||
| @Override | ||
| public boolean onEditorAction(final TextView textView, final int id, final KeyEvent keyEvent) { | ||
| if (id == R.id.login || id == EditorInfo.IME_NULL) { | ||
| attemptLogin(); | ||
| return true; | ||
| } | ||
| return false; | ||
| } | ||
| }); | ||
|
|
||
| final Button mEmailSignInButton = (Button) findViewById(R.id.login_button); | ||
| mEmailSignInButton.setOnClickListener(new OnClickListener() { | ||
| @Override | ||
| public void onClick(View view) { | ||
| attemptLogin(); | ||
| } | ||
| }); | ||
|
|
||
| final TextView register = (TextView) findViewById(R.id.create_account_msg); | ||
| register.setOnClickListener(new View.OnClickListener() { | ||
| @Override | ||
| public void onClick(final View v) { | ||
| launchActivity(RegisterActivity.class); | ||
| finish(); | ||
| } | ||
| }); | ||
|
|
||
| mLoginFormView = findViewById(R.id.login_form); | ||
| mProgressView = findViewById(R.id.login_progress); | ||
| userLocalStore = new UserLocalStore(this); | ||
| } | ||
|
|
||
| @Override | ||
| protected void onStart() { | ||
| super.onStart(); | ||
| if (userLocalStore.getUserLoggedIn()) { | ||
| final User user = userLocalStore.getLoggedInUser(); | ||
| Toast.makeText(this, | ||
| "Logged in as: " + user.getEmail(), | ||
| Toast.LENGTH_LONG).show(); | ||
| launchActivity(NodeLocationsActivity.class); | ||
| finish(); | ||
| } | ||
| } | ||
|
|
||
| private void populateAutoComplete() { | ||
| if (!mayRequestContacts()) { | ||
| return; | ||
| } | ||
|
|
||
| getLoaderManager().initLoader(0, null, this); | ||
| } | ||
|
|
||
| private boolean mayRequestContacts() { | ||
| if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { | ||
| return true; | ||
| } | ||
| if (checkSelfPermission(READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) { | ||
| return true; | ||
| } | ||
| if (shouldShowRequestPermissionRationale(READ_CONTACTS)) { | ||
| Snackbar.make(mEmailView, R.string.permission_rationale, Snackbar.LENGTH_INDEFINITE) | ||
| .setAction(android.R.string.ok, new View.OnClickListener() { | ||
| @Override | ||
| @TargetApi(Build.VERSION_CODES.M) | ||
| public void onClick(View v) { | ||
| requestPermissions(new String[] { READ_CONTACTS }, REQUEST_READ_CONTACTS); | ||
| } | ||
| }); | ||
| } else { | ||
| requestPermissions(new String[] { READ_CONTACTS }, REQUEST_READ_CONTACTS); | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| /** | ||
| * Callback received when a permissions request has been completed. | ||
| */ | ||
| @Override | ||
| public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, | ||
| @NonNull int[] grantResults) { | ||
| if (requestCode == REQUEST_READ_CONTACTS) { | ||
| if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { | ||
| populateAutoComplete(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Attempts to sign in or register the account specified by the login form. | ||
| * If there are form errors (invalid email, missing fields, etc.), the | ||
| * errors are presented and no actual login attempt is made. | ||
| */ | ||
| private void attemptLogin() { | ||
| if (mAuthTask != null) { | ||
| return; | ||
| } | ||
|
|
||
| // Reset errors. | ||
| mEmailView.setError(null); | ||
| mPasswordView.setError(null); | ||
|
|
||
| // Store values at the time of the login attempt. | ||
| final String email = mEmailView.getText().toString(); | ||
| final String password = mPasswordView.getText().toString(); | ||
|
|
||
| boolean cancel = false; | ||
| View focusView = null; | ||
|
|
||
| // Check for a valid email address. | ||
| if (TextUtils.isEmpty(email)) { | ||
| mEmailView.setError(getString(R.string.error_field_required)); | ||
| focusView = mEmailView; | ||
| cancel = true; | ||
| } else if (!isEmailValid(email)) { | ||
| mEmailView.setError(getString(R.string.error_invalid_email)); | ||
| focusView = mEmailView; | ||
| cancel = true; | ||
| } | ||
|
|
||
| // Check for a valid password, if the user entered one. | ||
| if (TextUtils.isEmpty(password)) { | ||
| mPasswordView.setError(getString(R.string.error_field_required)); | ||
| focusView = mPasswordView; | ||
| cancel = true; | ||
| } | ||
|
|
||
| if (!isPasswordValid(password) && !cancel) { | ||
| showProgress(true); | ||
| mPasswordView.setError(getString(R.string.error_incorrect_password)); | ||
| mEmailView.setError(getString(R.string.error_incorrect_password)); | ||
| mEmailView.requestFocus(); | ||
| showProgress(false); | ||
| return; | ||
| } | ||
|
|
||
| if (cancel) { | ||
| // There was an error; don't attempt login and focus the first | ||
| // form field with an error. | ||
| focusView.requestFocus(); | ||
| } else { | ||
| // Show a progress spinner, and kick off a background task to | ||
| // perform the user login attempt. | ||
| showProgress(true); | ||
|
|
||
| mAuthTask = new UserLoginTask(new User(email, password)); | ||
| mAuthTask.execute((Void) null); | ||
| } | ||
| } | ||
|
|
||
| private boolean isEmailValid(String email) { | ||
| return email.matches(getString(R.string.email_regex)); | ||
| } | ||
|
|
||
| private boolean isPasswordValid(String password) { | ||
| return password.matches(getString(R.string.password_regex)); | ||
| } | ||
|
|
||
| /** | ||
| * Shows the progress UI and hides the login form. | ||
| */ | ||
| @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2) | ||
| private void showProgress(final boolean show) { | ||
| // On Honeycomb MR2 we have the ViewPropertyAnimator APIs, which allow | ||
| // for very easy animations. If available, use these APIs to fade-in | ||
| // the progress spinner. | ||
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) { | ||
| int shortAnimTime = getResources().getInteger(android.R.integer.config_shortAnimTime); | ||
|
|
||
| mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE); | ||
| mLoginFormView.animate().setDuration(shortAnimTime).alpha( | ||
| show ? 0 : 1).setListener(new AnimatorListenerAdapter() { | ||
| @Override | ||
| public void onAnimationEnd(Animator animation) { | ||
| mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE); | ||
| } | ||
| }); | ||
|
|
||
| mProgressView.setVisibility(show ? View.VISIBLE : View.GONE); | ||
| mProgressView.animate().setDuration(shortAnimTime).alpha( | ||
| show ? 1 : 0).setListener(new AnimatorListenerAdapter() { | ||
| @Override | ||
| public void onAnimationEnd(Animator animation) { | ||
| mProgressView.setVisibility(show ? View.VISIBLE : View.GONE); | ||
| } | ||
| }); | ||
| } else { | ||
| // The ViewPropertyAnimator APIs are not available, so simply show | ||
| // and hide the relevant UI components. | ||
| mProgressView.setVisibility(show ? View.VISIBLE : View.GONE); | ||
| mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public Loader<Cursor> onCreateLoader(int i, Bundle bundle) { | ||
| return new CursorLoader(this, | ||
| // Retrieve data rows for the device user's 'profile' contact. | ||
| Uri.withAppendedPath(ContactsContract.Profile.CONTENT_URI, | ||
| ContactsContract.Contacts.Data.CONTENT_DIRECTORY), ProfileQuery.PROJECTION, | ||
|
|
||
| // Select only email addresses. | ||
| ContactsContract.Contacts.Data.MIMETYPE + | ||
| " = ?", new String[]{ContactsContract.CommonDataKinds.Email | ||
| .CONTENT_ITEM_TYPE}, | ||
|
|
||
| // Show primary email addresses first. Note that there won't be | ||
| // a primary email address if the user hasn't specified one. | ||
| ContactsContract.Contacts.Data.IS_PRIMARY + " DESC"); | ||
| } | ||
|
|
||
| @Override | ||
| public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) { | ||
| List<String> emails = new ArrayList<>(); | ||
| cursor.moveToFirst(); | ||
| while (!cursor.isAfterLast()) { | ||
| emails.add(cursor.getString(ProfileQuery.ADDRESS)); | ||
| cursor.moveToNext(); | ||
| } | ||
|
|
||
| addEmailsToAutoComplete(emails); | ||
| } | ||
|
|
||
| @Override | ||
| public void onLoaderReset(Loader<Cursor> cursorLoader) { | ||
|
|
||
| } | ||
|
|
||
| private void addEmailsToAutoComplete(List<String> emailAddressCollection) { | ||
| //Create adapter to tell the AutoCompleteTextView what to show in its dropdown list. | ||
| ArrayAdapter<String> adapter = | ||
| new ArrayAdapter<>(LoginActivity.this, | ||
| android.R.layout.simple_dropdown_item_1line, emailAddressCollection); | ||
|
|
||
| mEmailView.setAdapter(adapter); | ||
| } | ||
|
|
||
|
|
||
| private interface ProfileQuery { | ||
| String[] PROJECTION = { | ||
| ContactsContract.CommonDataKinds.Email.ADDRESS, | ||
| ContactsContract.CommonDataKinds.Email.IS_PRIMARY, | ||
| }; | ||
|
|
||
| int ADDRESS = 0; | ||
| int IS_PRIMARY = 1; | ||
| } | ||
|
|
||
| private void launchActivity(Class clazz) { | ||
| startActivity(new Intent(this, clazz)); | ||
| } | ||
|
|
||
| /** | ||
| * Represents an asynchronous login/registration task used to authenticate | ||
| * the user. | ||
| */ | ||
| public class UserLoginTask extends AsyncTask<Void, Void, User> { | ||
|
|
||
| private final User user; | ||
|
|
||
| UserLoginTask(User user) { | ||
| this.user = user; | ||
| } | ||
|
|
||
| @Override | ||
| protected User doInBackground(Void... params) { | ||
| // TODO: attempt authentication against a network service. | ||
| final SoilSmartService soilSmartService = SoilSmartService.getInstance(); | ||
| try { | ||
| if (soilSmartService.authenticate(user)) { | ||
| return user; | ||
| } | ||
| } catch (Exception e) { | ||
| return null; | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| @Override | ||
| protected void onPostExecute(final User user) { | ||
| mAuthTask = null; | ||
| showProgress(false); | ||
|
|
||
| if (user != null) { | ||
| userLocalStore.storeUserData(user); | ||
| userLocalStore.setUserLoggedIn(true); | ||
| launchActivity(NodeLocationsActivity.class); | ||
| finish(); | ||
| } else { | ||
| mPasswordView.setError(getString(R.string.error_incorrect_password)); | ||
| mEmailView.setError(getString(R.string.error_incorrect_password)); | ||
| mEmailView.requestFocus(); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| protected void onCancelled() { | ||
| mAuthTask = null; | ||
| showProgress(false); | ||
| } | ||
| } | ||
| } | ||
|
|
| @@ -0,0 +1,126 @@ | ||
| package soilsmart.soilsmartapp.views; | ||
|
|
||
| <<<<<<< HEAD:SoilSmartApp/app/src/main/java/soilsmart/soilsmartapp/views/NodeLocationsActivity.java | ||
| import android.content.Intent; | ||
| import android.os.Bundle; | ||
| import android.support.v7.app.AppCompatActivity; | ||
| ======= | ||
| import android.content.ComponentName; | ||
| import android.content.Intent; | ||
| import android.support.v4.app.FragmentActivity; | ||
| import android.os.Bundle; | ||
| import android.view.View; | ||
| import android.widget.LinearLayout; | ||
| import android.widget.PopupWindow; | ||
| import android.widget.TextView; | ||
| >>>>>>> Commit/push before merge from master:SoilSmartApp/app/src/main/java/soilsmart/soilsmartapp/NodeLocationsActivity.java | ||
|
|
||
| import com.google.android.gms.maps.CameraUpdateFactory; | ||
| import com.google.android.gms.maps.GoogleMap; | ||
| import com.google.android.gms.maps.OnMapReadyCallback; | ||
| import com.google.android.gms.maps.SupportMapFragment; | ||
| import com.google.android.gms.maps.model.BitmapDescriptorFactory; | ||
| import com.google.android.gms.maps.model.LatLng; | ||
| import com.google.android.gms.maps.model.Marker; | ||
| import com.google.android.gms.maps.model.MarkerOptions; | ||
|
|
||
| <<<<<<< HEAD:SoilSmartApp/app/src/main/java/soilsmart/soilsmartapp/views/NodeLocationsActivity.java | ||
| import soilsmart.soilsmartapp.R; | ||
| import soilsmart.soilsmartapp.UserLocalStore; | ||
|
|
||
| /** | ||
| * | ||
| */ | ||
| public class NodeLocationsActivity extends AppCompatActivity implements OnMapReadyCallback { | ||
| ======= | ||
| import java.sql.Struct; | ||
| import java.util.ArrayList; | ||
| import java.util.HashMap; | ||
| import java.util.Map; | ||
|
|
||
| public class NodeLocationsActivity extends FragmentActivity implements OnMapReadyCallback { | ||
| >>>>>>> Commit/push before merge from master:SoilSmartApp/app/src/main/java/soilsmart/soilsmartapp/NodeLocationsActivity.java | ||
|
|
||
| private GoogleMap mMap; | ||
| private UserLocalStore userLocalStore; | ||
|
|
||
| @Override | ||
| protected void onCreate(Bundle savedInstanceState) { | ||
| super.onCreate(savedInstanceState); | ||
| setContentView(R.layout.activity_node_locations); | ||
| // Obtain the SupportMapFragment and get notified when the map is ready to be used. | ||
| SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager() | ||
| .findFragmentById(R.id.map); | ||
| mapFragment.getMapAsync(this); | ||
| userLocalStore = new UserLocalStore(this); | ||
| } | ||
|
|
||
|
|
||
| /** | ||
| * Manipulates the map once available. | ||
| * This callback is triggered when the map is ready to be used. | ||
| * This is where we can add markers or lines, add listeners or move the camera. In this case, | ||
| * we just add a marker near Sydney, Australia. | ||
| * If Google Play services is not installed on the device, the user will be prompted to install | ||
| * it inside the SupportMapFragment. This method will only be triggered once the user has | ||
| * installed Google Play services and returned to the app. | ||
| */ | ||
| @Override | ||
| public void onMapReady(GoogleMap googleMap) { | ||
| mMap = googleMap; | ||
|
|
||
| // Add a marker in Sydney and move the camera | ||
| <<<<<<< HEAD:SoilSmartApp/app/src/main/java/soilsmart/soilsmartapp/views/NodeLocationsActivity.java | ||
| final LatLng sydney = new LatLng(-34, 151); | ||
| mMap.addMarker(new MarkerOptions().position(sydney).title("Marker in Sydney")); | ||
| ======= | ||
| LatLng sydney = new LatLng(-34, 151); | ||
| mMap.setMapType(GoogleMap.MAP_TYPE_SATELLITE); | ||
| mMap.addMarker(new MarkerOptions().position(sydney).icon(BitmapDescriptorFactory.fromResource(R.drawable.tree_drop_pin))); | ||
| >>>>>>> Commit/push before merge from master:SoilSmartApp/app/src/main/java/soilsmart/soilsmartapp/NodeLocationsActivity.java | ||
| mMap.moveCamera(CameraUpdateFactory.newLatLng(sydney)); | ||
|
|
||
| // Setup ClickListener to start new activity for individual sensor module data page | ||
| mMap.setOnInfoWindowClickListener(new GoogleMap.OnInfoWindowClickListener() { | ||
| @Override | ||
| public void onInfoWindowClick(Marker marker) { | ||
| Intent newIntent = new Intent(getApplicationContext(), test.class); | ||
| startActivity(newIntent); | ||
|
|
||
| } | ||
| }); | ||
|
|
||
| // Implement custom InfoWindowAdapter for popup after marker click | ||
| mMap.setInfoWindowAdapter(new GoogleMap.InfoWindowAdapter() { | ||
| @Override | ||
| public View getInfoWindow(Marker marker) { | ||
| return null; | ||
| } | ||
|
|
||
| @Override | ||
| public View getInfoContents(Marker marker) { | ||
| View v = getLayoutInflater().inflate(R.layout.custom_info_window,null); | ||
| LatLng latLng = marker.getPosition(); | ||
|
|
||
| TextView tvLatLng = (TextView) v.findViewById(R.id.tv_lat_lng); | ||
| TextView tvID = (TextView) v.findViewById(R.id.tv_ID); | ||
| tvLatLng.setText("(Lat,Long): ("+latLng.latitude+","+latLng.longitude+")"); | ||
|
|
||
| return v; | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| <<<<<<< HEAD:SoilSmartApp/app/src/main/java/soilsmart/soilsmartapp/views/NodeLocationsActivity.java | ||
| @Override | ||
| protected void onStart() { | ||
| super.onStart(); | ||
| if (!userLocalStore.getUserLoggedIn()) { | ||
| startActivity(new Intent(this, LoginActivity.class)); | ||
| finish(); | ||
| } | ||
| } | ||
| ======= | ||
|
|
||
| >>>>>>> Commit/push before merge from master:SoilSmartApp/app/src/main/java/soilsmart/soilsmartapp/NodeLocationsActivity.java | ||
| } |
| @@ -0,0 +1,282 @@ | ||
| package soilsmart.soilsmartapp.views; | ||
|
|
||
| import android.animation.Animator; | ||
| import android.animation.AnimatorListenerAdapter; | ||
| import android.annotation.TargetApi; | ||
| import android.content.Intent; | ||
| import android.os.AsyncTask; | ||
| import android.os.Build; | ||
| import android.os.Bundle; | ||
| import android.support.design.widget.Snackbar; | ||
| import android.support.v7.app.AppCompatActivity; | ||
| import android.text.TextUtils; | ||
| import android.view.KeyEvent; | ||
| import android.view.View; | ||
| import android.widget.AutoCompleteTextView; | ||
| import android.widget.Button; | ||
| import android.widget.EditText; | ||
| import android.widget.TextView; | ||
| import android.widget.Toast; | ||
|
|
||
| import soilsmart.soilsmartapp.R; | ||
| import soilsmart.soilsmartapp.SoilSmartService; | ||
| import soilsmart.soilsmartapp.User; | ||
| import soilsmart.soilsmartapp.views.LoginActivity; | ||
|
|
||
| public class RegisterActivity extends AppCompatActivity { | ||
|
|
||
| UserRegisterTask mAuthTask = null; | ||
|
|
||
| AutoCompleteTextView mEmailView; | ||
| EditText mPasswordView; | ||
| EditText mProductkeyView; | ||
| private View mProgressView; | ||
| private View mRegisterFormView; | ||
|
|
||
| @Override | ||
| protected void onCreate(Bundle savedInstanceState) { | ||
| super.onCreate(savedInstanceState); | ||
| setContentView(R.layout.activity_register); | ||
|
|
||
| final Button blogin = (Button) findViewById(R.id.register_button); | ||
| final Button breturn = (Button) findViewById(R.id.back_to_login_button); | ||
| mEmailView = (AutoCompleteTextView) findViewById(R.id.email); | ||
| mPasswordView = (EditText) findViewById(R.id.password); | ||
| mProductkeyView = (EditText) findViewById(R.id.product_key); | ||
|
|
||
| blogin.setOnClickListener(new View.OnClickListener() { | ||
| @Override | ||
| public void onClick(final View v) { | ||
| attemptRegister(); | ||
| } | ||
| }); | ||
|
|
||
| breturn.setOnClickListener(new View.OnClickListener() { | ||
| @Override | ||
| public void onClick(final View v) { | ||
| launchActivity(LoginActivity.class); | ||
| finish(); | ||
| } | ||
| }); | ||
|
|
||
| mProgressView = findViewById(R.id.login_progress); | ||
| mRegisterFormView = findViewById(R.id.registration_form); | ||
|
|
||
| mPasswordView.setOnEditorActionListener(new TextView.OnEditorActionListener() { | ||
| @Override | ||
| public boolean onEditorAction(final TextView v, final int id, final KeyEvent keyEvent) { | ||
| if (isPasswordValid(v.getText().toString())) { | ||
| mProductkeyView.requestFocus(); | ||
| return true; | ||
| } else { | ||
| mPasswordView.setError(getString(R.string.error_invalid_password)); | ||
| } | ||
| return false; | ||
| } | ||
| }); | ||
|
|
||
| mEmailView.setOnEditorActionListener(new TextView.OnEditorActionListener() { | ||
| @Override | ||
| public boolean onEditorAction(final TextView v, final int actionId, final KeyEvent event) { | ||
| if (isEmailValid(v.getText().toString())) { | ||
| mPasswordView.requestFocus(); | ||
| return true; | ||
| } else { | ||
| mEmailView.setError(getString(R.string.error_invalid_email)); | ||
| } | ||
| return false; | ||
| } | ||
| }); | ||
|
|
||
| mProductkeyView.setOnEditorActionListener(new TextView.OnEditorActionListener() { | ||
| @Override | ||
| public boolean onEditorAction(final TextView v, final int id, final KeyEvent keyEvent) { | ||
| if (isValidKey(v.getText().toString())) { | ||
| blogin.requestFocus(); | ||
| return true; | ||
| } | ||
| return false; | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| private boolean isEmailValid(String email) { | ||
| return email.matches(getString(R.string.email_regex)); | ||
| } | ||
|
|
||
| private boolean isPasswordValid(String password) { | ||
| return password.matches(getString(R.string.password_regex)); | ||
| } | ||
|
|
||
| private boolean isValidKey(String key) { | ||
| return key.length() > 10; | ||
| } | ||
|
|
||
| private void attemptRegister() { | ||
| if (mAuthTask != null) | ||
| return; | ||
|
|
||
| // Reset errors. | ||
| mEmailView.setError(null); | ||
| mPasswordView.setError(null); | ||
|
|
||
| // Store values at the time of the registration attempt. | ||
| final String email = mEmailView.getText().toString(); | ||
| final String password = mPasswordView.getText().toString(); | ||
| final String productKey = mProductkeyView.getText().toString(); | ||
|
|
||
| boolean cancel = false; | ||
| View focusView = null; | ||
|
|
||
| // Check for a valid email address. | ||
| if (TextUtils.isEmpty(email)) { | ||
| mEmailView.setError(getString(R.string.error_field_required)); | ||
| focusView = mEmailView; | ||
| cancel = true; | ||
| } else if (!isEmailValid(email)) { | ||
| mEmailView.setError(getString(R.string.error_invalid_email)); | ||
| focusView = mEmailView; | ||
| cancel = true; | ||
| } | ||
|
|
||
| // Check for a valid password, if the user entered one. | ||
| if (!isPasswordValid(password)) { | ||
| mPasswordView.setError(getString(R.string.error_invalid_password)); | ||
| focusView = mPasswordView; | ||
| cancel = true; | ||
| } | ||
|
|
||
| if (TextUtils.isEmpty(password)) { | ||
| mPasswordView.setError(getString(R.string.error_field_required)); | ||
| focusView = mPasswordView; | ||
| cancel = true; | ||
| } | ||
|
|
||
| // Check for a valid product key, if the user entered one. | ||
| if (TextUtils.isEmpty(productKey)) { | ||
| mProductkeyView.setError(getString(R.string.error_field_required)); | ||
| focusView = mProductkeyView; | ||
| cancel = true; | ||
| } | ||
|
|
||
| if (!isValidKey(productKey)) { | ||
| showMessage(null); | ||
| cancel = true; | ||
| } | ||
|
|
||
| if (cancel) { | ||
| // There was an error; don't attempt login and focus the first | ||
| // form field with an error. | ||
| if (focusView != null) | ||
| focusView.requestFocus(); | ||
| } else { | ||
| // Show a progress spinner, and kick off a background task to | ||
| // perform the user login attempt. | ||
| showProgress(true); | ||
|
|
||
| mAuthTask = new UserRegisterTask(new User(email, password), productKey); | ||
| mAuthTask.execute((Void) null); | ||
| } | ||
| } | ||
|
|
||
| private void launchActivity(Class clazz) { | ||
| startActivity(new Intent(this, clazz)); | ||
| } | ||
|
|
||
| /** | ||
| * Shows the progress UI and hides the login form. | ||
| */ | ||
| @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2) | ||
| private void showProgress(final boolean show) { | ||
| // On Honeycomb MR2 we have the ViewPropertyAnimator APIs, which allow | ||
| // for very easy animations. If available, use these APIs to fade-in | ||
| // the progress spinner. | ||
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) { | ||
| int shortAnimTime = getResources().getInteger(android.R.integer.config_shortAnimTime); | ||
|
|
||
| mRegisterFormView.setVisibility(show ? View.GONE : View.VISIBLE); | ||
| mRegisterFormView.animate().setDuration(shortAnimTime).alpha( | ||
| show ? 0 : 1).setListener(new AnimatorListenerAdapter() { | ||
| @Override | ||
| public void onAnimationEnd(Animator animation) { | ||
| mRegisterFormView.setVisibility(show ? View.GONE : View.VISIBLE); | ||
| } | ||
| }); | ||
|
|
||
| mProgressView.setVisibility(show ? View.VISIBLE : View.GONE); | ||
| mProgressView.animate().setDuration(shortAnimTime).alpha( | ||
| show ? 1 : 0).setListener(new AnimatorListenerAdapter() { | ||
| @Override | ||
| public void onAnimationEnd(Animator animation) { | ||
| mProgressView.setVisibility(show ? View.VISIBLE : View.GONE); | ||
| } | ||
| }); | ||
| } else { | ||
| // The ViewPropertyAnimator APIs are not available, so simply show | ||
| // and hide the relevant UI components. | ||
| mProgressView.setVisibility(show ? View.VISIBLE : View.GONE); | ||
| mRegisterFormView.setVisibility(show ? View.GONE : View.VISIBLE); | ||
| } | ||
| } | ||
|
|
||
| void showMessage(Object obj) { | ||
| if (obj != null) | ||
| Toast.makeText(this, | ||
| R.string.register_success, | ||
| Toast.LENGTH_LONG).show(); | ||
| else | ||
| Snackbar.make(mRegisterFormView, | ||
| getString(R.string.registration_failed), | ||
| Snackbar.LENGTH_LONG).show(); | ||
| } | ||
|
|
||
| /** | ||
| * Represents an asynchronous registration task used to authenticate | ||
| * the user. | ||
| */ | ||
| public class UserRegisterTask extends AsyncTask<Void, Void, User> { | ||
|
|
||
| private final User user; | ||
| private final String key; | ||
|
|
||
| UserRegisterTask(User user, String key) { | ||
| this.user = user; | ||
| this.key = key; | ||
| } | ||
|
|
||
| @Override | ||
| protected User doInBackground(Void... params) { | ||
| // TODO: attempt authentication against a network service. | ||
| final SoilSmartService soilSmartService = SoilSmartService.getInstance(); | ||
| try { | ||
| if (soilSmartService.registerUser(user, key)) { | ||
| return user; | ||
| } | ||
| } catch (Exception e) { | ||
| return null; | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| @Override | ||
| protected void onPostExecute(final User user) { | ||
| mAuthTask = null; | ||
| showProgress(false); | ||
|
|
||
| if (user != null) { | ||
| showMessage(user); | ||
| launchActivity(LoginActivity.class); | ||
| finish(); | ||
| } else { | ||
| showMessage(null); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| protected void onCancelled() { | ||
| mAuthTask = null; | ||
| showProgress(false); | ||
| } | ||
| } | ||
|
|
||
| } |
| @@ -0,0 +1,109 @@ | ||
| <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||
| xmlns:tools="http://schemas.android.com/tools" | ||
| android:layout_width="match_parent" | ||
| android:layout_height="match_parent" | ||
| android:gravity="center_horizontal" | ||
| android:orientation="vertical" | ||
| android:paddingBottom="@dimen/activity_vertical_margin" | ||
| android:paddingLeft="@dimen/activity_horizontal_margin" | ||
| android:paddingRight="@dimen/activity_horizontal_margin" | ||
| android:paddingTop="@dimen/activity_vertical_margin" | ||
| android:fitsSystemWindows="true" | ||
| android:background="#cdc5bf" | ||
| tools:context=".views.LoginActivity"> | ||
|
|
||
| <!-- Login progress --> | ||
| <ProgressBar | ||
| android:id="@+id/login_progress" | ||
| style="?android:attr/progressBarStyleLarge" | ||
| android:layout_width="wrap_content" | ||
| android:layout_height="wrap_content" | ||
| android:layout_marginBottom="8dp" | ||
| android:visibility="gone" /> | ||
|
|
||
| <RelativeLayout | ||
| android:id="@+id/soilsmart_logo_view" | ||
| android:layout_width="match_parent" | ||
| android:layout_height="0dp" | ||
| android:layout_weight="0.4"> | ||
|
|
||
| <ImageView | ||
| android:layout_width="wrap_content" | ||
| android:layout_height="wrap_content" | ||
| android:contentDescription="@string/logo_description" | ||
| android:id="@+id/soilsmart_logo" | ||
| android:src="@drawable/soilsmart_logo" | ||
| android:layout_centerHorizontal="true" /> | ||
|
|
||
| </RelativeLayout> | ||
|
|
||
| <RelativeLayout | ||
| android:id="@+id/login_form" | ||
| android:layout_width="match_parent" | ||
| android:layout_height="0dp" | ||
| android:layout_weight="0.6"> | ||
|
|
||
| <android.support.design.widget.TextInputLayout | ||
| android:id="@+id/email_container" | ||
| android:layout_width="match_parent" | ||
| android:layout_height="wrap_content"> | ||
|
|
||
| <AutoCompleteTextView | ||
| android:id="@+id/email" | ||
| android:layout_width="match_parent" | ||
| android:layout_height="wrap_content" | ||
| android:hint="@string/prompt_email" | ||
| android:inputType="textEmailAddress" | ||
| android:maxLines="1" | ||
| android:singleLine="true" | ||
| android:textColorHint="#00c71b" | ||
| android:textColorHighlight="#9aeffd" /> | ||
|
|
||
| </android.support.design.widget.TextInputLayout> | ||
|
|
||
| <android.support.design.widget.TextInputLayout | ||
| android:id="@+id/password_container" | ||
| android:layout_width="match_parent" | ||
| android:layout_height="wrap_content" | ||
| android:layout_below="@+id/email_container"> | ||
|
|
||
| <EditText | ||
| android:id="@+id/password" | ||
| android:layout_width="match_parent" | ||
| android:layout_height="wrap_content" | ||
| android:hint="@string/prompt_password" | ||
| android:imeActionId="@+id/login" | ||
| android:imeActionLabel="@string/action_sign_in_short" | ||
| android:imeOptions="actionUnspecified" | ||
| android:inputType="textPassword" | ||
| android:maxLines="1" | ||
| android:singleLine="true" | ||
| android:textColorHighlight="#9aeffd" | ||
| android:textColorHint="#00c71b" /> | ||
|
|
||
| </android.support.design.widget.TextInputLayout> | ||
|
|
||
| <Button | ||
| android:layout_width="match_parent" | ||
| android:layout_height="wrap_content" | ||
| android:text="@string/login" | ||
| android:id="@+id/login_button" | ||
| android:layout_below="@+id/password_container" /> | ||
|
|
||
| <TextView | ||
| android:layout_width="wrap_content" | ||
| android:layout_height="wrap_content" | ||
| android:clickable="true" | ||
| android:textSize="12sp" | ||
| android:text="@string/create_account_msg" | ||
| android:contextClickable="false" | ||
| android:textColor="#4577e4" | ||
| android:textStyle="italic" | ||
| android:id="@+id/create_account_msg" | ||
| android:layout_below="@+id/login_button" | ||
| android:layout_centerHorizontal="true" | ||
| android:textAlignment="center"/> | ||
|
|
||
| </RelativeLayout> | ||
|
|
||
| </LinearLayout> |
| @@ -1,8 +1,9 @@ | ||
| <fragment | ||
| android:id="@+id/map" | ||
| android:name="com.google.android.gms.maps.SupportMapFragment" | ||
| xmlns:android="http://schemas.android.com/apk/res/android" | ||
| xmlns:map="http://schemas.android.com/apk/res-auto" | ||
| xmlns:tools="http://schemas.android.com/tools" | ||
| android:layout_width="match_parent" | ||
| android:layout_height="match_parent" | ||
| tools:context=".views.NodeLocationsActivity" /> |
| @@ -0,0 +1,122 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||
| xmlns:tools="http://schemas.android.com/tools" | ||
| android:layout_width="match_parent" | ||
| android:layout_height="match_parent" | ||
| android:orientation="vertical" | ||
| android:gravity="center_horizontal" | ||
| android:paddingBottom="@dimen/activity_vertical_margin" | ||
| android:paddingLeft="@dimen/activity_horizontal_margin" | ||
| android:paddingRight="@dimen/activity_horizontal_margin" | ||
| android:paddingTop="@dimen/activity_vertical_margin" | ||
| android:fitsSystemWindows="true" | ||
| android:background="#cdc5bf" | ||
| tools:context=".views.RegisterActivity"> | ||
|
|
||
| <!-- Login progress --> | ||
| <ProgressBar | ||
| android:id="@+id/login_progress" | ||
| style="?android:attr/progressBarStyleLarge" | ||
| android:layout_width="wrap_content" | ||
| android:layout_height="wrap_content" | ||
| android:layout_marginBottom="8dp" | ||
| android:visibility="gone" /> | ||
|
|
||
| <RelativeLayout | ||
| android:id="@+id/soilsmart_logo_view" | ||
| android:layout_width="match_parent" | ||
| android:layout_height="0dp" | ||
| android:layout_weight="0.2"> | ||
|
|
||
| <ImageView | ||
| android:layout_width="wrap_content" | ||
| android:layout_height="wrap_content" | ||
| android:contentDescription="@string/logo_description" | ||
| android:id="@+id/soilsmart_logo" | ||
| android:src="@drawable/soilsmart_logo" | ||
| android:layout_centerHorizontal="true" /> | ||
|
|
||
| </RelativeLayout> | ||
|
|
||
| <RelativeLayout | ||
| android:id="@+id/registration_form" | ||
| android:layout_width="match_parent" | ||
| android:layout_height="0dp" | ||
| android:layout_weight="0.8"> | ||
|
|
||
| <android.support.design.widget.TextInputLayout | ||
| android:id="@+id/email_container" | ||
| android:layout_width="match_parent" | ||
| android:layout_height="wrap_content"> | ||
|
|
||
| <AutoCompleteTextView | ||
| android:id="@+id/email" | ||
| android:layout_width="match_parent" | ||
| android:layout_height="wrap_content" | ||
| android:hint="@string/prompt_email" | ||
| android:inputType="textEmailAddress" | ||
| android:maxLines="1" | ||
| android:singleLine="true" | ||
| android:textColorHint="#00c71b" | ||
| android:textColorHighlight="#9aeffd" /> | ||
|
|
||
| </android.support.design.widget.TextInputLayout> | ||
|
|
||
| <android.support.design.widget.TextInputLayout | ||
| android:id="@+id/password_container" | ||
| android:layout_width="match_parent" | ||
| android:layout_height="wrap_content" | ||
| android:layout_below="@+id/email_container"> | ||
|
|
||
| <EditText | ||
| android:id="@+id/password" | ||
| android:layout_width="match_parent" | ||
| android:layout_height="wrap_content" | ||
| android:hint="@string/prompt_password" | ||
| android:inputType="textPassword" | ||
| android:maxLines="1" | ||
| android:singleLine="true" | ||
| android:textColorHighlight="#9aeffd" | ||
| android:textColorHint="#00c71b" /> | ||
|
|
||
| </android.support.design.widget.TextInputLayout> | ||
|
|
||
|
|
||
| <android.support.design.widget.TextInputLayout | ||
| android:id="@+id/product_key_container" | ||
| android:layout_width="match_parent" | ||
| android:layout_height="wrap_content" | ||
| android:layout_below="@+id/password_container"> | ||
|
|
||
| <EditText | ||
| android:id="@+id/product_key" | ||
| android:layout_width="match_parent" | ||
| android:layout_height="wrap_content" | ||
| android:hint="@string/prompt_product_key" | ||
| android:inputType="textVisiblePassword" | ||
| android:maxLines="1" | ||
| android:singleLine="true" | ||
| android:textColorHint="#00c71b" | ||
| android:textColorHighlight="#9aeffd" /> | ||
|
|
||
| </android.support.design.widget.TextInputLayout> | ||
|
|
||
| <Button | ||
| android:layout_width="match_parent" | ||
| android:layout_height="wrap_content" | ||
| android:text="@string/register" | ||
| android:id="@+id/register_button" | ||
| android:layout_below="@+id/product_key_container" /> | ||
|
|
||
| <Button | ||
| android:layout_width="match_parent" | ||
| android:layout_height="wrap_content" | ||
| android:text="@string/back_to_login" | ||
| android:id="@+id/back_to_login_button" | ||
| android:layout_alignParentBottom="true" | ||
| android:layout_alignParentLeft="true" | ||
| android:layout_alignParentStart="true" /> | ||
|
|
||
| </RelativeLayout> | ||
|
|
||
| </LinearLayout> |
| @@ -1,6 +1,6 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <resources> | ||
| <color name="colorPrimary">#00CC00</color> | ||
| <color name="colorPrimaryDark">#008000</color> | ||
| <color name="colorAccent">#C9A47F</color> | ||
| </resources> |
| @@ -1,7 +1,38 @@ | ||
| <resources> | ||
| <string name="app_name">SoilSmartApp</string> | ||
| <string name="company_name">SoilSmart</string> | ||
|
|
||
| <!-- Strings related to login --> | ||
| <string name="logo_description">SoilSmart</string> | ||
| <string name="login">Sign In</string> | ||
| <string name="create_account_msg">Don\'t have an account?\nRegister here.</string> | ||
| <string name="prompt_email">Email</string> | ||
| <string name="prompt_password">Password</string> | ||
| <string name="action_sign_in">Sign in or register</string> | ||
| <string name="action_sign_in_short">Sign in</string> | ||
| <string name="error_invalid_email">This email address is invalid</string> | ||
| <string name="error_invalid_password">This password must be at least 7 characters long and contain at least one of each of the following:\n* a–z\n* A–Z\n* 0–9</string> | ||
| <string name="error_incorrect_password">Invalid credentials</string> | ||
| <string name="error_field_required">This field is required</string> | ||
| <string name="permission_rationale">"Contacts permissions are needed for providing email completions."</string> | ||
|
|
||
| <!-- Strings related to register --> | ||
| <string name="register">Register</string> | ||
| <string name="back_to_login">Back to Login</string> | ||
| <string name="prompt_product_key">Product Key (Included in purchase)</string> | ||
|
|
||
| <!-- Strings related to Locations --> | ||
| <string name="title_activity_node_locations">Locations</string> | ||
| <string name="google_maps_key">AIzaSyD_kQpy75hRLZLDl7Td_gfKIXkhKfjbhSo</string> | ||
| <string name="google_app_id">752907188352</string> | ||
|
|
||
| <!-- Miscellaneous --> | ||
| <string name="password_regex">^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{7,}$</string> | ||
| <string name="email_regex">^\\w+([-+.\']\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$</string> | ||
| <string name="register_success">Registration Successful!</string> | ||
| <string name="registration_failed">Registration failed.\nDid you use the right product key?\nAlready have an account? Just login and add your product key through our \'Settings\' tab.</string> | ||
|
|
||
|
|
||
| <string name="title_activity_test">test</string> | ||
|
|
||
| </resources> |