diff --git a/iNaturalist/build.gradle b/iNaturalist/build.gradle
index f1ea5fae4..435e1b059 100644
--- a/iNaturalist/build.gradle
+++ b/iNaturalist/build.gradle
@@ -14,6 +14,7 @@ repositories {
maven { url 'https://maven.fabric.io/public' }
}
+
android {
compileSdkVersion 'Google Inc.:Google APIs:19'
buildToolsVersion "22.0.1"
@@ -52,11 +53,10 @@ dependencies {
compile 'org.apache.commons:commons-lang3:3.1'
compile 'org.apache.httpcomponents:httpmime:4.3.3'
compile 'org.apache.httpcomponents:httpcore:4.4.1'
- compile 'com.facebook.android:facebook-android-sdk:3.23.1'
+ compile 'com.facebook.android:facebook-android-sdk:4.1.0'
compile 'com.loopj.android:android-async-http:1.4.7'
compile('com.crashlytics.sdk.android:crashlytics:2.4.0@aar') {
transitive = true;
}
compile 'com.melnykov:floatingactionbutton:1.3.0'
-
}
diff --git a/iNaturalist/iNaturalist-release.apk b/iNaturalist/iNaturalist-release.apk
new file mode 100644
index 000000000..6d7fdd927
Binary files /dev/null and b/iNaturalist/iNaturalist-release.apk differ
diff --git a/iNaturalist/iNaturalist.iml b/iNaturalist/iNaturalist.iml
index 00f71403f..e9df79cc8 100644
--- a/iNaturalist/iNaturalist.iml
+++ b/iNaturalist/iNaturalist.iml
@@ -76,7 +76,7 @@
-
+
@@ -100,6 +100,7 @@
+
@@ -107,13 +108,12 @@
-
+
-
diff --git a/iNaturalist/src/main/AndroidManifest.xml b/iNaturalist/src/main/AndroidManifest.xml
index da16049f2..c68f55633 100644
--- a/iNaturalist/src/main/AndroidManifest.xml
+++ b/iNaturalist/src/main/AndroidManifest.xml
@@ -2,7 +2,7 @@
+ android:versionName="1.3.23" >
@@ -100,7 +100,18 @@
android:label="@string/app_name"
android:name=".TaxonSearchActivity" >
-
+
+
+
+
+
+
+
+
+
permissions = new ArrayList();
- permissions.add("email");
- mFacebookLoginButton.setReadPermissions(permissions);
-
- mFacebookLoginButton.setSessionStatusCallback(new StatusCallback() {
+ mFacebookAccessTokenTracker = new AccessTokenTracker() {
@Override
- public void call(Session session, SessionState state, Exception exception) {
- Log.d(TAG, "onSessionStateChange: " + state.toString());
- if ((state == SessionState.CLOSED_LOGIN_FAILED) && (!isNetworkAvailable())) {
- Toast.makeText(getApplicationContext(), R.string.not_connected, Toast.LENGTH_LONG).show();
- return;
- } else if ((state == SessionState.OPENED) || (state == SessionState.OPENED_TOKEN_UPDATED)) {
+ protected void onCurrentAccessTokenChanged(AccessToken oldToken, AccessToken newToken) {
+ Log.e("AAA", "NEW ACCESS TOKEN: " + newToken);
+ if (newToken != null) {
String username = mPreferences.getString("username", null);
if (username == null) {
// First time login
- String accessToken = session.getAccessToken();
-// Log.d(TAG, "FB Login: " + accessToken);
- new SignInTask(INaturalistPrefsActivity.this).execute(null, accessToken, LoginType.FACEBOOK.toString());
+ String accessToken = newToken.getToken();
+ new SignInTask(INaturalistPrefsActivity.this, INaturalistPrefsActivity.this).execute(null, accessToken, LoginType.FACEBOOK.toString());
}
}
}
+ };
+
+ mFacebookCallbackManager = CallbackManager.Factory.create();
+
+ ArrayList permissions = new ArrayList();
+ permissions.add("email");
+ mFacebookLoginButton.setReadPermissions(permissions);
+
+ mFacebookLoginButton.registerCallback(mFacebookCallbackManager, new FacebookCallback() {
+ @Override
+ public void onSuccess(LoginResult loginResult) {
+
+ }
+
+ @Override
+ public void onCancel() {
+ if (!isNetworkAvailable()) {
+ Toast.makeText(getApplicationContext(), R.string.not_connected, Toast.LENGTH_LONG).show();
+ }
+ }
+
+ @Override
+ public void onError(FacebookException exception) {
+ Toast.makeText(getApplicationContext(), R.string.not_connected, Toast.LENGTH_LONG).show();
+ }
});
toggle();
@@ -426,21 +441,22 @@ protected void onResume() {
super.onResume();
mHelper = new ActivityHelper(this);
- mUiHelper.onResume();
}
-
+
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
- mUiHelper.onSaveInstanceState(outState);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
+ if (mFacebookCallbackManager != null) {
+ mFacebookCallbackManager.onActivityResult(requestCode, resultCode, data);
+ }
+
// Log.d(TAG, "onActivityResult " + requestCode + ":" + resultCode + ":" + data);
- mUiHelper.onActivityResult(requestCode, resultCode, data);
-
+
if ((requestCode == REQUEST_CODE_ADD_ACCOUNT) && (resultCode == Activity.RESULT_OK)) {
// User finished adding his account
signIn(LoginType.GOOGLE, mGoogleUsername, null);
@@ -451,18 +467,6 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
}
}
- @Override
- public void onPause() {
- super.onPause();
- mUiHelper.onPause();
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- mUiHelper.onDestroy();
- }
-
private void toggle() {
@@ -495,103 +499,7 @@ private void toggle() {
}
}
- private class SignInTask extends AsyncTask {
- private String mUsername;
- private String mPassword;
- private LoginType mLoginType;
- private Activity mActivity;
- private boolean mInvalidated;
-
- public SignInTask(Activity activity) {
- mActivity = activity;
- }
-
- protected String doInBackground(String... pieces) {
- mUsername = pieces[0];
- mPassword = pieces[1];
- mLoginType = LoginType.valueOf(pieces[2]);
- if (pieces.length > 3) {
- mInvalidated = (pieces[3] == "invalidated");
- } else {
- mInvalidated = false;
- }
-
-// Log.d(TAG, String.format("Verifying credentials for %s login with %s:%s",
-// mLoginType.toString(), (mUsername != null ? mUsername : ""), mPassword));
-
- // TODO - Support for OAuth2 login with Google/Facebook
- if (mLoginType == LoginType.PASSWORD) {
- String result = INaturalistService.verifyCredentials(mUsername, mPassword);
- if (result != null) {
- mUsername = result;
- return "true";
- } else {
- return null;
- }
- } else {
- String[] results = INaturalistService.verifyCredentials(mPassword, mLoginType);
-
- if (results == null) {
- return null;
- }
-
- // Upgrade from FB/Google email to iNat username
- mUsername = results[1];
-
- return results[0];
- }
- }
-
- protected void onPreExecute() {
- mProgressDialog = ProgressDialog.show(mActivity, "", getString(R.string.signing_in), true);
- }
-
- protected void onPostExecute(String result) {
- try {
- mProgressDialog.dismiss();
- } catch (Exception exc) {
- // Ignore
- }
- if (result != null) {
- Toast.makeText(mActivity, getString(R.string.signed_in), Toast.LENGTH_SHORT).show();
- } else {
- if (mLoginType == LoginType.FACEBOOK) {
- // Login failed - need to sign-out of Facebook as well
- Session session = Session.getActiveSession();
- session.closeAndClearTokenInformation();
- } else if (mLoginType == LoginType.GOOGLE && !mInvalidated) {
- AccountManager.get(mActivity).invalidateAuthToken("com.google", mPassword);
- INaturalistPrefsActivity a = (INaturalistPrefsActivity) mActivity;
- a.signIn(LoginType.GOOGLE, mUsername, null, true);
- return;
- }
- mHelper.alert(getString(R.string.signed_in_failed));
- return;
- }
-
- mPrefEditor.putString("username", mUsername);
-
- String credentials;
- if (mLoginType == LoginType.PASSWORD) {
- credentials = Base64.encodeToString(
- (mUsername + ":" + mPassword).getBytes(), Base64.URL_SAFE|Base64.NO_WRAP
- );
- } else {
- credentials = result; // Access token
- }
- mPrefEditor.putString("credentials", credentials);
- mPrefEditor.putString("password", mPassword);
- mPrefEditor.putString("login_type", mLoginType.toString());
- mPrefEditor.commit();
- toggle();
-
- // Run the first observation sync
- Intent serviceIntent = new Intent(INaturalistService.ACTION_FIRST_SYNC, null, INaturalistPrefsActivity.this, INaturalistService.class);
- startService(serviceIntent);
- }
- }
-
public void signIn(LoginType loginType, String username, String password) {
signIn(loginType, username, password, false);
}
@@ -644,7 +552,7 @@ public void run(AccountManagerFuture future) {
final Intent authIntent = result.getParcelable(AccountManager.KEY_INTENT);
if (accountName != null && authToken != null) {
// Log.d(TAG, String.format("Token: %s", authToken));
- new SignInTask(INaturalistPrefsActivity.this).execute(boundUsername, authToken, LoginType.GOOGLE.toString(), boundInvalidated);
+ new SignInTask(INaturalistPrefsActivity.this, INaturalistPrefsActivity.this).execute(boundUsername, authToken, LoginType.GOOGLE.toString(), boundInvalidated);
} else if (authIntent != null) {
int flags = authIntent.getFlags();
@@ -671,7 +579,7 @@ public void run(AccountManagerFuture future) {
} else {
// "Regular" login
- new SignInTask(this).execute(username, password, LoginType.PASSWORD.toString());
+ new SignInTask(this, this).execute(username, password, LoginType.PASSWORD.toString());
}
}
@@ -776,6 +684,11 @@ private void updateRadioButtonState(){
}
}
- }
+ }
+ @Override
+ public void onLoginSuccessful() {
+ // Refresh the login controls
+ toggle();
+ }
}
diff --git a/iNaturalist/src/main/java/org/inaturalist/android/INaturalistService.java b/iNaturalist/src/main/java/org/inaturalist/android/INaturalistService.java
index 06a567b61..750a0009f 100644
--- a/iNaturalist/src/main/java/org/inaturalist/android/INaturalistService.java
+++ b/iNaturalist/src/main/java/org/inaturalist/android/INaturalistService.java
@@ -117,8 +117,6 @@ public class INaturalistService extends IntentService implements ConnectionCallb
public static String TAG = "INaturalistService";
public static String HOST = "https://www.inaturalist.org";
-// public static String HOST = "http://10.0.2.2:3000";
- public static String MEDIA_HOST = HOST;
public static String USER_AGENT = "iNaturalist/" + INaturalistApp.VERSION + " (" +
"Android " + System.getProperty("os.version") + " " + android.os.Build.VERSION.INCREMENTAL + "; " +
"SDK " + android.os.Build.VERSION.SDK_INT + "; " +
diff --git a/iNaturalist/src/main/java/org/inaturalist/android/LoginSignupActivity.java b/iNaturalist/src/main/java/org/inaturalist/android/LoginSignupActivity.java
new file mode 100644
index 000000000..4bf1dff7f
--- /dev/null
+++ b/iNaturalist/src/main/java/org/inaturalist/android/LoginSignupActivity.java
@@ -0,0 +1,347 @@
+package org.inaturalist.android;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.Html;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+import android.widget.ViewFlipper;
+
+import com.facebook.login.widget.LoginButton;
+import com.flurry.android.FlurryAgent;
+
+public class LoginSignupActivity extends Activity implements SignInTask.SignInTaskStatus {
+
+ private static String TAG = "LoginSignupActivity";
+ private INaturalistApp mApp;
+ private ActivityHelper mHelper;
+ private ImageView mBackgroundImage;
+
+ public static final String BACKGROUND_ID = "background_id";
+ public static final String SIGNUP = "signup";
+
+ private ImageView mEmailIcon;
+ private EditText mEmail;
+ private ImageView mPasswordIcon;
+ private EditText mPassword;
+ private ImageView mUsernameIcon;
+ private EditText mUsername;
+ private TextView mPasswordWarning;
+ private TextView mCheckboxDescription;
+ private ImageView mCheckbox;
+ private boolean mUseCCLicense;
+ private Button mSignup;
+ private boolean mIsSignup;
+ private SignInTask mSignInTask;
+ private LoginButton mFacebookLoginButton;
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ FlurryAgent.onStartSession(this, INaturalistApp.getAppContext().getString(R.string.flurry_api_key));
+ FlurryAgent.logEvent(this.getClass().getSimpleName());
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ FlurryAgent.onEndSession(this);
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
+
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+
+ mApp = (INaturalistApp) getApplicationContext();
+ setContentView(R.layout.login_signup);
+
+ mHelper = new ActivityHelper(this);
+
+ mBackgroundImage = (ImageView) findViewById(R.id.background_image);
+
+ int backgroundId = getIntent().getIntExtra(BACKGROUND_ID, 0);
+ mIsSignup = getIntent().getBooleanExtra(SIGNUP, false);
+
+ switch (backgroundId) {
+ case 2:
+ mBackgroundImage.setImageResource(R.drawable.signup_background_3_blurred);
+ break;
+ case 1:
+ mBackgroundImage.setImageResource(R.drawable.signup_background_2_blurred);
+ break;
+ case 0:
+ default:
+ mBackgroundImage.setImageResource(R.drawable.signup_background_1_blurred);
+ break;
+ }
+
+ View backButton = findViewById(R.id.back);
+ backButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ onBackPressed();
+ }
+ });
+
+ mEmailIcon = (ImageView) findViewById(R.id.email_icon);
+ mEmailIcon.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ mEmail.requestFocus();
+ getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+ }
+ });
+ mEmail = (EditText) findViewById(R.id.email);
+ mEmail.setOnFocusChangeListener(new View.OnFocusChangeListener() {
+ @Override
+ public void onFocusChange(View view, boolean focused) {
+ if (focused) {
+ mEmailIcon.getDrawable().setAlpha(0xff);
+ } else {
+ mEmailIcon.getDrawable().setAlpha(0x7f);
+ }
+ }
+ });
+ mEmail.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
+ }
+
+ @Override
+ public void afterTextChanged(Editable editable) {
+ checkFields();
+ }
+ });
+
+ mPasswordIcon = (ImageView) findViewById(R.id.password_icon);
+ mPasswordIcon.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ mPassword.requestFocus();
+ }
+ });
+ mPassword = (EditText) findViewById(R.id.password);
+ mPassword.setOnFocusChangeListener(new View.OnFocusChangeListener() {
+ @Override
+ public void onFocusChange(View view, boolean focused) {
+ if (focused) {
+ mPasswordIcon.getDrawable().setAlpha(0xff);
+ } else {
+ mPasswordIcon.getDrawable().setAlpha(0x7f);
+ }
+ }
+ });
+
+ mUsernameIcon = (ImageView) findViewById(R.id.username_icon);
+ mUsernameIcon.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ mUsername.requestFocus();
+ }
+ });
+ mUsername = (EditText) findViewById(R.id.username);
+ mUsername.setOnFocusChangeListener(new View.OnFocusChangeListener() {
+ @Override
+ public void onFocusChange(View view, boolean focused) {
+ if (focused) {
+ mUsernameIcon.getDrawable().setAlpha(0xff);
+ } else {
+ mUsernameIcon.getDrawable().setAlpha(0x7f);
+ }
+ }
+ });
+ mUsername.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
+ }
+
+ @Override
+ public void afterTextChanged(Editable editable) {
+ checkFields();
+ }
+ });
+
+ mEmailIcon.getDrawable().setAlpha(0x7f);
+ mPasswordIcon.getDrawable().setAlpha(0x7f);
+ mUsernameIcon.getDrawable().setAlpha(0x7f);
+
+ mPasswordWarning = (TextView) findViewById(R.id.password_warning);
+ mPassword.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
+ }
+
+ @Override
+ public void afterTextChanged(Editable editable) {
+ if (mPassword.getText().length() >= (mIsSignup ? 6 : 1)) {
+ mPasswordWarning.setVisibility(View.GONE);
+ } else {
+ mPasswordWarning.setVisibility(View.VISIBLE);
+ }
+
+ checkFields();
+ }
+ });
+
+ mUseCCLicense = true;
+
+ mCheckboxDescription = (TextView) findViewById(R.id.checkbox_description);
+ mCheckboxDescription.setText(Html.fromHtml(mCheckboxDescription.getText().toString()));
+ mCheckboxDescription.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ mHelper.alert(R.string.content_licensing, R.string.content_licensing_description);
+ }
+ });
+
+ mCheckbox = (ImageView) findViewById(R.id.checkbox);
+ mCheckbox.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ mUseCCLicense = !mUseCCLicense;
+ if (mUseCCLicense) {
+ mCheckbox.setImageResource(R.drawable.ic_check_box_white_24dp);
+ } else {
+ mCheckbox.setImageResource(R.drawable.ic_check_box_outline_blank_white_24dp);
+ }
+ }
+ });
+
+ mSignup = (Button) findViewById(R.id.sign_up);
+ mSignup.setEnabled(false);
+
+ if (!mIsSignup) {
+ TextView title = (TextView) findViewById(R.id.action_bar_title);
+ title.setText(R.string.log_in);
+ View emailContainer = (View) findViewById(R.id.email_container);
+ emailContainer.setVisibility(View.GONE);
+ View checkboxContainer = (View) findViewById(R.id.checkbox_container);
+ checkboxContainer.setVisibility(View.GONE);
+ mUsername.setHint(R.string.username_or_email);
+
+ View usernameContainer = (View) findViewById(R.id.username_container);
+ ViewGroup parent = (ViewGroup)usernameContainer.getParent();
+ parent.removeView(usernameContainer);
+ parent.addView(usernameContainer, 0);
+
+ mSignup.setText(R.string.log_in);
+ mPasswordWarning.setText(R.string.forgot);
+ mPasswordWarning.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ // Open the forgot password page on the user's browser
+ String inatNetwork = mApp.getInaturalistNetworkMember();
+ String inatHost = mApp.getStringResourceByName("inat_host_" + inatNetwork);
+
+ Intent i = new Intent(Intent.ACTION_VIEW);
+ i.setData(Uri.parse("http://" + inatHost + "/forgot_password.mobile"));
+ startActivity(i);
+ }
+ });
+ } else {
+ View loginButtons = findViewById(R.id.login_buttons_container);
+ loginButtons.setVisibility(View.GONE);
+ View loginWith = findViewById(R.id.login_with);
+ loginWith.setVisibility(View.GONE);
+ }
+
+ mFacebookLoginButton = (LoginButton) findViewById(R.id.facebook_login_button);
+
+ View loginWithFacebook = findViewById(R.id.login_with_facebook);
+ loginWithFacebook.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ mFacebookLoginButton.performClick();
+ }
+ });
+
+ View loginWithGoogle = findViewById(R.id.login_with_gplus);
+ loginWithGoogle.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (!isNetworkAvailable()) {
+ Toast.makeText(getApplicationContext(), R.string.not_connected, Toast.LENGTH_LONG).show();
+ return;
+ }
+ mSignInTask.signIn(INaturalistService.LoginType.GOOGLE, null, null);
+ }
+ });
+
+ mSignInTask = new SignInTask(this, this, mFacebookLoginButton);
+
+ mSignup.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ mSignInTask.signIn(INaturalistService.LoginType.PASSWORD, mUsername.getText().toString(), mPassword.getText().toString());
+ }
+ });
+
+ }
+
+ private void checkFields() {
+ if (((mEmail.getText().length() == 0) && (mIsSignup)) || (mPassword.getText().length() < (mIsSignup ? 6 : 1)) || (mUsername.getText().length() == 0)) {
+ mSignup.setEnabled(false);
+ } else {
+ mSignup.setEnabled(true);
+ }
+ }
+
+ public void onBackPressed(){
+ mSignInTask.pause();
+ setResult(RESULT_CANCELED);
+ finish();
+ overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ mSignInTask.onActivityResult(requestCode, resultCode, data);
+ }
+
+ @Override
+ public void onLoginSuccessful() {
+ mSignInTask.pause();
+ setResult(RESULT_OK);
+ finish();
+ }
+
+ private boolean isNetworkAvailable() {
+ ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
+ NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
+ return activeNetworkInfo != null && activeNetworkInfo.isConnected();
+ }
+
+}
+
diff --git a/iNaturalist/src/main/java/org/inaturalist/android/ObservationListActivity.java b/iNaturalist/src/main/java/org/inaturalist/android/ObservationListActivity.java
index 90fc1430f..40a8830f7 100644
--- a/iNaturalist/src/main/java/org/inaturalist/android/ObservationListActivity.java
+++ b/iNaturalist/src/main/java/org/inaturalist/android/ObservationListActivity.java
@@ -85,8 +85,9 @@ public class ObservationListActivity extends BaseFragmentActivity implements OnI
private static final int COMMENTS_IDS_REQUEST_CODE = 100;
private static final int OBSERVATION_LIST_LOADER = 0x01;
-
- @Override
+ private INaturalistApp mApp;
+
+ @Override
protected void onStart()
{
super.onStart();
@@ -195,7 +196,11 @@ public void onCreate(Bundle savedInstanceState) {
setTitle(R.string.observations);
mHelper = new ActivityHelper(this);
-
+
+ mApp = (INaturalistApp)getApplication();
+
+
+
mSyncObservations = (TextView) findViewById(R.id.sync_observations);
mSyncObservations.setOnClickListener(new OnClickListener() {
@Override
@@ -222,7 +227,16 @@ public void onClick(View v) {
mLastMessage = savedInstanceState.getString("mLastMessage");
}
-
+ SharedPreferences pref = getSharedPreferences("iNaturalistPreferences", MODE_PRIVATE);
+ String username = pref.getString("username", null);
+ if (username == null) {
+ if (!mApp.shownOnboarding()) {
+ // Show login/onboarding screen
+ mApp.setShownOnboarding(true);
+ //startActivity(new Intent(ObservationListActivity.this, OnboardingActivity.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP));
+ }
+ }
+
refreshSyncBar();
mTopActionBar = getSupportActionBar();
diff --git a/iNaturalist/src/main/java/org/inaturalist/android/OnboardingActivity.java b/iNaturalist/src/main/java/org/inaturalist/android/OnboardingActivity.java
new file mode 100644
index 000000000..2980d315f
--- /dev/null
+++ b/iNaturalist/src/main/java/org/inaturalist/android/OnboardingActivity.java
@@ -0,0 +1,159 @@
+package org.inaturalist.android;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.Bundle;
+import android.view.View;
+import android.view.Window;
+import android.widget.Button;
+import android.widget.Toast;
+import android.widget.ViewFlipper;
+import com.facebook.login.widget.LoginButton;
+import com.flurry.android.FlurryAgent;
+
+public class OnboardingActivity extends Activity implements SignInTask.SignInTaskStatus {
+ private static final int REQUEST_CODE_SIGNUP = 0x10000;
+ private static final int REQUEST_CODE_LOGIN = 0x10001;
+
+ private static String TAG = "OnboardingActivity";
+ private INaturalistApp mApp;
+ private ActivityHelper mHelper;
+ private ViewFlipper mBackgroundImage;
+ private LoginButton mFacebookLoginButton;
+ private SharedPreferences mPreferences;
+ private SharedPreferences.Editor mPrefEditor;
+ private SignInTask mSignInTask;
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ FlurryAgent.onStartSession(this, INaturalistApp.getAppContext().getString(R.string.flurry_api_key));
+ FlurryAgent.logEvent(this.getClass().getSimpleName());
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ FlurryAgent.onEndSession(this);
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+
+ mApp = (INaturalistApp) getApplicationContext();
+ setContentView(R.layout.onboarding);
+
+ mHelper = new ActivityHelper(this);
+ mPreferences = getSharedPreferences("iNaturalistPreferences", MODE_PRIVATE);
+ mPrefEditor = mPreferences.edit();
+
+ mBackgroundImage = (ViewFlipper) findViewById(R.id.background_image);
+ mBackgroundImage.startFlipping();
+
+ View signUpWithEmail = findViewById(R.id.sign_up_with_email);
+ signUpWithEmail.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ mSignInTask.pause();
+
+ Intent intent = new Intent(OnboardingActivity.this, LoginSignupActivity.class);
+ intent.putExtra(LoginSignupActivity.SIGNUP, true);
+ intent.putExtra(LoginSignupActivity.BACKGROUND_ID, mBackgroundImage.indexOfChild(mBackgroundImage.getCurrentView()));
+ startActivityForResult(intent, REQUEST_CODE_SIGNUP);
+
+ overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
+ }
+ });
+
+ View login = (View) findViewById(R.id.login_with_email);
+ login.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ mSignInTask.pause();
+
+ Intent intent = new Intent(OnboardingActivity.this, LoginSignupActivity.class);
+ intent.putExtra(LoginSignupActivity.SIGNUP, false);
+ intent.putExtra(LoginSignupActivity.BACKGROUND_ID, mBackgroundImage.indexOfChild(mBackgroundImage.getCurrentView()));
+ startActivityForResult(intent, REQUEST_CODE_LOGIN);
+
+ overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
+ }
+ });
+
+ Button skip = (Button) findViewById(R.id.skip);
+ skip.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ mSignInTask.pause();
+ finish();
+ }
+ });
+
+ mFacebookLoginButton = (LoginButton) findViewById(R.id.facebook_login_button);
+
+ View loginWithFacebook = findViewById(R.id.login_with_facebook);
+ loginWithFacebook.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ mFacebookLoginButton.performClick();
+ }
+ });
+
+ View loginWithGoogle = findViewById(R.id.login_with_gplus);
+ loginWithGoogle.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (!isNetworkAvailable()) {
+ Toast.makeText(getApplicationContext(), R.string.not_connected, Toast.LENGTH_LONG).show();
+ return;
+ }
+ mSignInTask.signIn(INaturalistService.LoginType.GOOGLE, null, null);
+ }
+ });
+
+ mSignInTask = new SignInTask(this, this, mFacebookLoginButton);
+
+ }
+
+ @Override
+ public void onLoginSuccessful() {
+ // Close this screen
+ mSignInTask.pause();
+ finish();
+ }
+
+ private boolean isNetworkAvailable() {
+ ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
+ NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
+ return activeNetworkInfo != null && activeNetworkInfo.isConnected();
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ mSignInTask.onActivityResult(requestCode, resultCode, data);
+
+ if ((requestCode == REQUEST_CODE_LOGIN) || (requestCode == REQUEST_CODE_SIGNUP)) {
+ if (resultCode == RESULT_OK) {
+ // Successfully registered / logged-in from the sub-activity we've opened
+ mSignInTask.pause();
+ finish();
+ } else {
+ mSignInTask.resume();
+ }
+ }
+ }
+
+ public void onBackPressed(){
+ mSignInTask.pause();
+ }
+
+}
+
diff --git a/iNaturalist/src/main/java/org/inaturalist/android/SignInTask.java b/iNaturalist/src/main/java/org/inaturalist/android/SignInTask.java
new file mode 100644
index 000000000..787731874
--- /dev/null
+++ b/iNaturalist/src/main/java/org/inaturalist/android/SignInTask.java
@@ -0,0 +1,366 @@
+package org.inaturalist.android;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AccountManagerCallback;
+import android.accounts.AccountManagerFuture;
+import android.app.Activity;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.text.InputType;
+import android.util.Base64;
+import android.util.Log;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.Toast;
+
+import com.facebook.AccessToken;
+import com.facebook.AccessTokenTracker;
+import com.facebook.CallbackManager;
+import com.facebook.FacebookCallback;
+import com.facebook.FacebookException;
+import com.facebook.login.LoginManager;
+import com.facebook.login.LoginResult;
+import com.facebook.login.widget.LoginButton;
+
+import java.util.ArrayList;
+
+public class SignInTask extends AsyncTask {
+ private static final String TAG = "SignInTask";
+
+ private static final String GOOGLE_AUTH_TOKEN_TYPE = "oauth2:https://www.googleapis.com/auth/plus.login https://www.googleapis.com/auth/plus.me https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile";
+ private AccessTokenTracker mFacebookAccessTokenTracker = null;
+
+ private SharedPreferences mPreferences;
+ private ActivityHelper mHelper;
+ private SharedPreferences.Editor mPrefEditor;
+ private CallbackManager mFacebookCallbackManager;
+ private LoginButton mFacebookLoginButton;
+ private String mUsername;
+ private String mPassword;
+ private INaturalistService.LoginType mLoginType;
+ private Activity mActivity;
+ private boolean mInvalidated;
+ private ProgressDialog mProgressDialog;
+ private SignInTaskStatus mCallback;
+
+ private static final int REQUEST_CODE_LOGIN = 0x1000;
+ private static final int REQUEST_CODE_ADD_ACCOUNT = 0x1001;
+
+ private String mGoogleUsername;
+
+ public interface SignInTaskStatus {
+ void onLoginSuccessful();
+ }
+
+ public SignInTask(Activity activity, SignInTaskStatus callback) {
+ mActivity = activity;
+ mPreferences = mActivity.getSharedPreferences("iNaturalistPreferences", Activity.MODE_PRIVATE);
+ mPrefEditor = mPreferences.edit();
+ mHelper = new ActivityHelper(mActivity);
+ mCallback = callback;
+ mFacebookLoginButton = null;
+ mFacebookCallbackManager = null;
+ }
+
+ public SignInTask(Activity activity, SignInTaskStatus callback, LoginButton facebookLoginButton) {
+ this(activity, callback);
+ mFacebookLoginButton = facebookLoginButton;
+
+ mFacebookAccessTokenTracker = new AccessTokenTracker() {
+ @Override
+ protected void onCurrentAccessTokenChanged(AccessToken oldToken, AccessToken newToken) {
+ Log.e("AAA", "ACCESS TOKEN CHANGE: " + mActivity + "::" + (newToken == null ? "null" : newToken.getToken()));
+ if (newToken != null) {
+ String username = mPreferences.getString("username", null);
+ if (username == null) {
+ // First time login
+ String accessToken = newToken.getToken();
+ execute(null, accessToken, INaturalistService.LoginType.FACEBOOK.toString());
+ }
+ }
+ }
+ };
+
+ mFacebookCallbackManager = CallbackManager.Factory.create();
+
+ ArrayList permissions = new ArrayList();
+ permissions.add("email");
+ mFacebookLoginButton.setReadPermissions(permissions);
+
+ mFacebookLoginButton.registerCallback(mFacebookCallbackManager, new FacebookCallback() {
+ @Override
+ public void onSuccess(LoginResult loginResult) {
+
+ }
+
+ @Override
+ public void onCancel() {
+ if (!isNetworkAvailable()) {
+ Toast.makeText(mActivity.getApplicationContext(), R.string.not_connected, Toast.LENGTH_LONG).show();
+ }
+ }
+
+ @Override
+ public void onError(FacebookException exception) {
+ Toast.makeText(mActivity.getApplicationContext(), R.string.not_connected, Toast.LENGTH_LONG).show();
+ }
+ });
+ }
+
+ private boolean isNetworkAvailable() {
+ ConnectivityManager connectivityManager = (ConnectivityManager) mActivity.getSystemService(Context.CONNECTIVITY_SERVICE);
+ NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
+ return activeNetworkInfo != null && activeNetworkInfo.isConnected();
+ }
+
+
+
+ protected String doInBackground(String... pieces) {
+ mUsername = pieces[0];
+ mPassword = pieces[1];
+ mLoginType = INaturalistService.LoginType.valueOf(pieces[2]);
+ if (pieces.length > 3) {
+ mInvalidated = (pieces[3] == "invalidated");
+ } else {
+ mInvalidated = false;
+ }
+
+ if (mLoginType == INaturalistService.LoginType.PASSWORD) {
+ String result = INaturalistService.verifyCredentials(mUsername, mPassword);
+ if (result != null) {
+ mUsername = result;
+ return "true";
+ } else {
+ return null;
+ }
+ } else {
+ String[] results = INaturalistService.verifyCredentials(mPassword, mLoginType);
+
+ if (results == null) {
+ return null;
+ }
+
+ // Upgrade from FB/Google email to iNat username
+ mUsername = results[1];
+
+ return results[0];
+ }
+ }
+
+ protected void onPreExecute() {
+ mProgressDialog = ProgressDialog.show(mActivity, "", mActivity.getString(R.string.signing_in), true);
+ }
+
+ protected void onPostExecute(String result) {
+ try {
+ mProgressDialog.dismiss();
+ } catch (Exception exc) {
+ // Ignore
+ }
+ if (result != null) {
+ Toast.makeText(mActivity, mActivity.getString(R.string.signed_in), Toast.LENGTH_SHORT).show();
+ } else {
+ if (mLoginType == INaturalistService.LoginType.FACEBOOK) {
+ // Login failed - need to sign-out of Facebook as well
+ LoginManager.getInstance().logOut();
+ } else if (mLoginType == INaturalistService.LoginType.GOOGLE && !mInvalidated) {
+ AccountManager.get(mActivity).invalidateAuthToken("com.google", mPassword);
+ INaturalistPrefsActivity a = (INaturalistPrefsActivity) mActivity;
+ a.signIn(INaturalistService.LoginType.GOOGLE, mUsername, null, true);
+ return;
+ }
+ mHelper.alert(mActivity.getString(R.string.signed_in_failed));
+ return;
+ }
+
+ mPrefEditor.putString("username", mUsername);
+
+ String credentials;
+ if (mLoginType == INaturalistService.LoginType.PASSWORD) {
+ credentials = Base64.encodeToString(
+ (mUsername + ":" + mPassword).getBytes(), Base64.URL_SAFE | Base64.NO_WRAP
+ );
+ } else {
+ credentials = result; // Access token
+ }
+ mPrefEditor.putString("credentials", credentials);
+ mPrefEditor.putString("password", mPassword);
+ mPrefEditor.putString("login_type", mLoginType.toString());
+ mPrefEditor.commit();
+
+ mCallback.onLoginSuccessful();
+
+ // Run the first observation sync
+ Intent serviceIntent = new Intent(INaturalistService.ACTION_FIRST_SYNC, null, mActivity, INaturalistService.class);
+ mActivity.startService(serviceIntent);
+ }
+
+
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (mFacebookCallbackManager != null) {
+ mFacebookCallbackManager.onActivityResult(requestCode, resultCode, data);
+ }
+
+ if ((requestCode == REQUEST_CODE_ADD_ACCOUNT) && (resultCode == Activity.RESULT_OK)) {
+ // User finished adding his account
+ signIn(INaturalistService.LoginType.GOOGLE, mGoogleUsername, null);
+
+ } else if ((requestCode == REQUEST_CODE_LOGIN) && (resultCode == Activity.RESULT_OK)) {
+ // User finished entering his password
+ signIn(INaturalistService.LoginType.GOOGLE, mGoogleUsername, null);
+ }
+ }
+
+
+ public void signIn(INaturalistService.LoginType loginType, String username, String password) {
+ signIn(loginType, username, password, false);
+ }
+
+ public void signIn(INaturalistService.LoginType loginType, String username, String password, boolean invalidated) {
+ boolean googleLogin = (loginType == INaturalistService.LoginType.GOOGLE);
+
+ if (googleLogin) {
+ String googleUsername = null;
+ Account account = null;
+
+ // See if given account exists
+ Account[] availableAccounts = AccountManager.get(mActivity).getAccountsByType("com.google");
+ boolean accountFound = false;
+
+ if (username != null) {
+ googleUsername = username.toLowerCase();
+ for (int i = 0; i < availableAccounts.length; i++) {
+ if (availableAccounts[i].name.equalsIgnoreCase(googleUsername)) {
+ // Found the account
+// Log.d(TAG, "googleUsername: " + googleUsername);
+ accountFound = true;
+ break;
+ }
+ }
+ }
+
+ if (availableAccounts.length > 0) {
+ accountFound = true;
+ account = availableAccounts[0];
+ } else if (googleUsername == null) {
+ askForGoogleEmail();
+ return;
+ } else {
+ // Redirect user to add account dialog
+ mGoogleUsername = googleUsername;
+ mActivity.startActivityForResult(new Intent(Settings.ACTION_ADD_ACCOUNT), REQUEST_CODE_ADD_ACCOUNT);
+ return;
+ }
+
+ // Google account login
+ final String boundUsername = googleUsername;
+ final String boundInvalidated = invalidated ? "invalidated" : null;
+ final AccountManagerCallback cb = new AccountManagerCallback() {
+ public void run(AccountManagerFuture future) {
+ try {
+ final Bundle result = future.getResult();
+ final String accountName = result.getString(AccountManager.KEY_ACCOUNT_NAME);
+ final String authToken = result.getString(AccountManager.KEY_AUTHTOKEN);
+ final Intent authIntent = result.getParcelable(AccountManager.KEY_INTENT);
+ if (accountName != null && authToken != null) {
+// Log.d(TAG, String.format("Token: %s", authToken));
+ execute(boundUsername, authToken, INaturalistService.LoginType.GOOGLE.toString(), boundInvalidated);
+
+ } else if (authIntent != null) {
+ int flags = authIntent.getFlags();
+ flags &= ~Intent.FLAG_ACTIVITY_NEW_TASK;
+ authIntent.setFlags(flags);
+ mActivity.startActivityForResult(authIntent, REQUEST_CODE_LOGIN);
+ } else {
+ Log.e(TAG, "AccountManager was unable to obtain an authToken.");
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Auth Error", e);
+ }
+ }
+ };
+ if (account == null) {
+ account = new Account(googleUsername, "com.google");
+ }
+ AccountManager.get(mActivity).getAuthToken(account,
+ GOOGLE_AUTH_TOKEN_TYPE,
+ null,
+ mActivity,
+ cb,
+ null);
+
+ } else {
+ // "Regular" login
+ execute(username, password, INaturalistService.LoginType.PASSWORD.toString());
+ }
+ }
+
+ private void signOut() {
+ String login = mPreferences.getString("username", null);
+
+ mPrefEditor.remove("username");
+ mPrefEditor.remove("credentials");
+ mPrefEditor.remove("password");
+ mPrefEditor.remove("login_type");
+ mPrefEditor.remove("last_sync_time");
+ mPrefEditor.commit();
+
+ int count1 = mActivity.getContentResolver().delete(Observation.CONTENT_URI, "((_updated_at > _synced_at AND _synced_at IS NOT NULL) OR (_synced_at IS NULL))", null);
+ int count2 = mActivity.getContentResolver().delete(ObservationPhoto.CONTENT_URI, "((_updated_at > _synced_at AND _synced_at IS NOT NULL) OR (_synced_at IS NULL))", null);
+ int count3 = mActivity.getContentResolver().delete(ProjectObservation.CONTENT_URI, "(is_new = 1) OR (is_deleted = 1)", null);
+ int count4 = mActivity.getContentResolver().delete(ProjectFieldValue.CONTENT_URI, "((_updated_at > _synced_at AND _synced_at IS NOT NULL) OR (_synced_at IS NULL))", null);
+
+ Log.d(TAG, String.format("Deleted %d / %d / %d / %d unsynced observations", count1, count2, count3, count4));
+
+ // TODO
+ //toggle();
+ }
+
+ private void askForGoogleEmail() {
+ final EditText input = new EditText(mActivity);
+ // Specify the type of input expected; this, for example, sets the input as a password, and will mask the text
+ input.setInputType(InputType.TYPE_CLASS_TEXT);
+ input.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT, LinearLayout.LayoutParams.FILL_PARENT));
+
+ mHelper.confirm(R.string.email_address, input, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialogInterface, int i) {
+ String username = input.getText().toString();
+
+ if (username.trim().length() == 0) {
+ return;
+ }
+
+ signIn(INaturalistService.LoginType.GOOGLE, username.trim().toLowerCase(), null);
+ }
+ },
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialogInterface, int i) {
+ dialogInterface.cancel();
+ }
+ });
+ }
+
+
+ public void pause() {
+ if (mFacebookAccessTokenTracker != null) {
+ mFacebookAccessTokenTracker.stopTracking();
+ }
+ }
+ public void resume() {
+ if (mFacebookAccessTokenTracker != null) {
+ mFacebookAccessTokenTracker.startTracking();
+ }
+ }
+}
+
diff --git a/iNaturalist/src/main/res/anim/fade_in.xml b/iNaturalist/src/main/res/anim/fade_in.xml
new file mode 100644
index 000000000..c7b90b186
--- /dev/null
+++ b/iNaturalist/src/main/res/anim/fade_in.xml
@@ -0,0 +1,6 @@
+
+
+
diff --git a/iNaturalist/src/main/res/anim/fade_out.xml b/iNaturalist/src/main/res/anim/fade_out.xml
new file mode 100644
index 000000000..03f7d5cab
--- /dev/null
+++ b/iNaturalist/src/main/res/anim/fade_out.xml
@@ -0,0 +1,6 @@
+
+
+
diff --git a/iNaturalist/src/main/res/drawable-hdpi/ic_action_email.png b/iNaturalist/src/main/res/drawable-hdpi/ic_action_email.png
new file mode 100644
index 000000000..2e5855137
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-hdpi/ic_action_email.png differ
diff --git a/iNaturalist/src/main/res/drawable-hdpi/ic_action_email_white.png b/iNaturalist/src/main/res/drawable-hdpi/ic_action_email_white.png
new file mode 100644
index 000000000..7cfc49ec7
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-hdpi/ic_action_email_white.png differ
diff --git a/iNaturalist/src/main/res/drawable-hdpi/ic_check_box_outline_blank_white_24dp.png b/iNaturalist/src/main/res/drawable-hdpi/ic_check_box_outline_blank_white_24dp.png
new file mode 100644
index 000000000..443e73f2e
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-hdpi/ic_check_box_outline_blank_white_24dp.png differ
diff --git a/iNaturalist/src/main/res/drawable-hdpi/ic_check_box_white_24dp.png b/iNaturalist/src/main/res/drawable-hdpi/ic_check_box_white_24dp.png
new file mode 100644
index 000000000..9f3bc735a
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-hdpi/ic_check_box_white_24dp.png differ
diff --git a/iNaturalist/src/main/res/drawable-hdpi/ic_email_white_24dp.png b/iNaturalist/src/main/res/drawable-hdpi/ic_email_white_24dp.png
new file mode 100644
index 000000000..852774d3f
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-hdpi/ic_email_white_24dp.png differ
diff --git a/iNaturalist/src/main/res/drawable-hdpi/ic_fa_chevron_left.png b/iNaturalist/src/main/res/drawable-hdpi/ic_fa_chevron_left.png
new file mode 100644
index 000000000..ad81f5901
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-hdpi/ic_fa_chevron_left.png differ
diff --git a/iNaturalist/src/main/res/drawable-hdpi/ic_fa_chevron_left_dark.png b/iNaturalist/src/main/res/drawable-hdpi/ic_fa_chevron_left_dark.png
new file mode 100644
index 000000000..cd863df4e
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-hdpi/ic_fa_chevron_left_dark.png differ
diff --git a/iNaturalist/src/main/res/drawable-hdpi/ic_fa_facebook.png b/iNaturalist/src/main/res/drawable-hdpi/ic_fa_facebook.png
new file mode 100644
index 000000000..5a785b5f9
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-hdpi/ic_fa_facebook.png differ
diff --git a/iNaturalist/src/main/res/drawable-hdpi/ic_fa_facebook_white.png b/iNaturalist/src/main/res/drawable-hdpi/ic_fa_facebook_white.png
new file mode 100644
index 000000000..05c711a7b
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-hdpi/ic_fa_facebook_white.png differ
diff --git a/iNaturalist/src/main/res/drawable-hdpi/ic_fa_google_plus.png b/iNaturalist/src/main/res/drawable-hdpi/ic_fa_google_plus.png
new file mode 100644
index 000000000..a6563b488
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-hdpi/ic_fa_google_plus.png differ
diff --git a/iNaturalist/src/main/res/drawable-hdpi/ic_fa_google_plus_white.png b/iNaturalist/src/main/res/drawable-hdpi/ic_fa_google_plus_white.png
new file mode 100644
index 000000000..8cf8bd409
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-hdpi/ic_fa_google_plus_white.png differ
diff --git a/iNaturalist/src/main/res/drawable-hdpi/ic_lock_white_24dp.png b/iNaturalist/src/main/res/drawable-hdpi/ic_lock_white_24dp.png
new file mode 100644
index 000000000..cd4f04aa1
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-hdpi/ic_lock_white_24dp.png differ
diff --git a/iNaturalist/src/main/res/drawable-hdpi/ic_person_white_24dp.png b/iNaturalist/src/main/res/drawable-hdpi/ic_person_white_24dp.png
new file mode 100644
index 000000000..56708b0ba
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-hdpi/ic_person_white_24dp.png differ
diff --git a/iNaturalist/src/main/res/drawable-hdpi/logo_inat_signup.png b/iNaturalist/src/main/res/drawable-hdpi/logo_inat_signup.png
new file mode 100644
index 000000000..26a29a9b1
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-hdpi/logo_inat_signup.png differ
diff --git a/iNaturalist/src/main/res/drawable-ldpi/ic_fa_chevron_left.png b/iNaturalist/src/main/res/drawable-ldpi/ic_fa_chevron_left.png
new file mode 100644
index 000000000..01588f202
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-ldpi/ic_fa_chevron_left.png differ
diff --git a/iNaturalist/src/main/res/drawable-ldpi/ic_fa_chevron_left_dark.png b/iNaturalist/src/main/res/drawable-ldpi/ic_fa_chevron_left_dark.png
new file mode 100644
index 000000000..e5ee5b434
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-ldpi/ic_fa_chevron_left_dark.png differ
diff --git a/iNaturalist/src/main/res/drawable-ldpi/ic_fa_facebook.png b/iNaturalist/src/main/res/drawable-ldpi/ic_fa_facebook.png
new file mode 100644
index 000000000..7fbcd9348
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-ldpi/ic_fa_facebook.png differ
diff --git a/iNaturalist/src/main/res/drawable-ldpi/ic_fa_facebook_white.png b/iNaturalist/src/main/res/drawable-ldpi/ic_fa_facebook_white.png
new file mode 100644
index 000000000..37e201d7b
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-ldpi/ic_fa_facebook_white.png differ
diff --git a/iNaturalist/src/main/res/drawable-ldpi/ic_fa_google_plus.png b/iNaturalist/src/main/res/drawable-ldpi/ic_fa_google_plus.png
new file mode 100644
index 000000000..a471ef43b
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-ldpi/ic_fa_google_plus.png differ
diff --git a/iNaturalist/src/main/res/drawable-ldpi/ic_fa_google_plus_white.png b/iNaturalist/src/main/res/drawable-ldpi/ic_fa_google_plus_white.png
new file mode 100644
index 000000000..7b0cdddf1
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-ldpi/ic_fa_google_plus_white.png differ
diff --git a/iNaturalist/src/main/res/drawable-mdpi/ic_action_email.png b/iNaturalist/src/main/res/drawable-mdpi/ic_action_email.png
new file mode 100644
index 000000000..1fc444346
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-mdpi/ic_action_email.png differ
diff --git a/iNaturalist/src/main/res/drawable-mdpi/ic_action_email_white.png b/iNaturalist/src/main/res/drawable-mdpi/ic_action_email_white.png
new file mode 100644
index 000000000..fe2d1603a
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-mdpi/ic_action_email_white.png differ
diff --git a/iNaturalist/src/main/res/drawable-mdpi/ic_check_box_outline_blank_white_24dp.png b/iNaturalist/src/main/res/drawable-mdpi/ic_check_box_outline_blank_white_24dp.png
new file mode 100644
index 000000000..c3c14ddd8
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-mdpi/ic_check_box_outline_blank_white_24dp.png differ
diff --git a/iNaturalist/src/main/res/drawable-mdpi/ic_check_box_white_24dp.png b/iNaturalist/src/main/res/drawable-mdpi/ic_check_box_white_24dp.png
new file mode 100644
index 000000000..fa2290786
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-mdpi/ic_check_box_white_24dp.png differ
diff --git a/iNaturalist/src/main/res/drawable-mdpi/ic_email_white_24dp.png b/iNaturalist/src/main/res/drawable-mdpi/ic_email_white_24dp.png
new file mode 100644
index 000000000..7b65a3d52
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-mdpi/ic_email_white_24dp.png differ
diff --git a/iNaturalist/src/main/res/drawable-mdpi/ic_fa_chevron_left.png b/iNaturalist/src/main/res/drawable-mdpi/ic_fa_chevron_left.png
new file mode 100644
index 000000000..09d76994d
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-mdpi/ic_fa_chevron_left.png differ
diff --git a/iNaturalist/src/main/res/drawable-mdpi/ic_fa_chevron_left_dark.png b/iNaturalist/src/main/res/drawable-mdpi/ic_fa_chevron_left_dark.png
new file mode 100644
index 000000000..c2dde1713
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-mdpi/ic_fa_chevron_left_dark.png differ
diff --git a/iNaturalist/src/main/res/drawable-mdpi/ic_fa_facebook.png b/iNaturalist/src/main/res/drawable-mdpi/ic_fa_facebook.png
new file mode 100644
index 000000000..3bff0a015
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-mdpi/ic_fa_facebook.png differ
diff --git a/iNaturalist/src/main/res/drawable-mdpi/ic_fa_facebook_white.png b/iNaturalist/src/main/res/drawable-mdpi/ic_fa_facebook_white.png
new file mode 100644
index 000000000..92bbb40d9
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-mdpi/ic_fa_facebook_white.png differ
diff --git a/iNaturalist/src/main/res/drawable-mdpi/ic_fa_google_plus.png b/iNaturalist/src/main/res/drawable-mdpi/ic_fa_google_plus.png
new file mode 100644
index 000000000..514bc0950
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-mdpi/ic_fa_google_plus.png differ
diff --git a/iNaturalist/src/main/res/drawable-mdpi/ic_fa_google_plus_white.png b/iNaturalist/src/main/res/drawable-mdpi/ic_fa_google_plus_white.png
new file mode 100644
index 000000000..826ab8691
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-mdpi/ic_fa_google_plus_white.png differ
diff --git a/iNaturalist/src/main/res/drawable-mdpi/ic_lock_white_24dp.png b/iNaturalist/src/main/res/drawable-mdpi/ic_lock_white_24dp.png
new file mode 100644
index 000000000..1127f87f7
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-mdpi/ic_lock_white_24dp.png differ
diff --git a/iNaturalist/src/main/res/drawable-mdpi/ic_person_white_24dp.png b/iNaturalist/src/main/res/drawable-mdpi/ic_person_white_24dp.png
new file mode 100644
index 000000000..f0b1c725d
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-mdpi/ic_person_white_24dp.png differ
diff --git a/iNaturalist/src/main/res/drawable-mdpi/logo_inat_signup.png b/iNaturalist/src/main/res/drawable-mdpi/logo_inat_signup.png
new file mode 100644
index 000000000..d66e71479
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-mdpi/logo_inat_signup.png differ
diff --git a/iNaturalist/src/main/res/drawable-xhdpi/ic_action_email.png b/iNaturalist/src/main/res/drawable-xhdpi/ic_action_email.png
new file mode 100644
index 000000000..b91e4b26a
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-xhdpi/ic_action_email.png differ
diff --git a/iNaturalist/src/main/res/drawable-xhdpi/ic_action_email_white.png b/iNaturalist/src/main/res/drawable-xhdpi/ic_action_email_white.png
new file mode 100644
index 000000000..043b7b4dc
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-xhdpi/ic_action_email_white.png differ
diff --git a/iNaturalist/src/main/res/drawable-xhdpi/ic_check_box_outline_blank_white_24dp.png b/iNaturalist/src/main/res/drawable-xhdpi/ic_check_box_outline_blank_white_24dp.png
new file mode 100644
index 000000000..6c335dc22
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-xhdpi/ic_check_box_outline_blank_white_24dp.png differ
diff --git a/iNaturalist/src/main/res/drawable-xhdpi/ic_check_box_white_24dp.png b/iNaturalist/src/main/res/drawable-xhdpi/ic_check_box_white_24dp.png
new file mode 100644
index 000000000..d15985579
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-xhdpi/ic_check_box_white_24dp.png differ
diff --git a/iNaturalist/src/main/res/drawable-xhdpi/ic_email_white_24dp.png b/iNaturalist/src/main/res/drawable-xhdpi/ic_email_white_24dp.png
new file mode 100644
index 000000000..9756b8015
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-xhdpi/ic_email_white_24dp.png differ
diff --git a/iNaturalist/src/main/res/drawable-xhdpi/ic_fa_chevron_left.png b/iNaturalist/src/main/res/drawable-xhdpi/ic_fa_chevron_left.png
new file mode 100644
index 000000000..532b3c355
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-xhdpi/ic_fa_chevron_left.png differ
diff --git a/iNaturalist/src/main/res/drawable-xhdpi/ic_fa_chevron_left_dark.png b/iNaturalist/src/main/res/drawable-xhdpi/ic_fa_chevron_left_dark.png
new file mode 100644
index 000000000..30355f0f5
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-xhdpi/ic_fa_chevron_left_dark.png differ
diff --git a/iNaturalist/src/main/res/drawable-xhdpi/ic_fa_facebook.png b/iNaturalist/src/main/res/drawable-xhdpi/ic_fa_facebook.png
new file mode 100644
index 000000000..99f5f4c34
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-xhdpi/ic_fa_facebook.png differ
diff --git a/iNaturalist/src/main/res/drawable-xhdpi/ic_fa_facebook_white.png b/iNaturalist/src/main/res/drawable-xhdpi/ic_fa_facebook_white.png
new file mode 100644
index 000000000..09dac84ca
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-xhdpi/ic_fa_facebook_white.png differ
diff --git a/iNaturalist/src/main/res/drawable-xhdpi/ic_fa_google_plus.png b/iNaturalist/src/main/res/drawable-xhdpi/ic_fa_google_plus.png
new file mode 100644
index 000000000..d5b3fccf7
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-xhdpi/ic_fa_google_plus.png differ
diff --git a/iNaturalist/src/main/res/drawable-xhdpi/ic_fa_google_plus_white.png b/iNaturalist/src/main/res/drawable-xhdpi/ic_fa_google_plus_white.png
new file mode 100644
index 000000000..fb6ba9e1a
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-xhdpi/ic_fa_google_plus_white.png differ
diff --git a/iNaturalist/src/main/res/drawable-xhdpi/ic_lock_white_24dp.png b/iNaturalist/src/main/res/drawable-xhdpi/ic_lock_white_24dp.png
new file mode 100644
index 000000000..ad8d91a99
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-xhdpi/ic_lock_white_24dp.png differ
diff --git a/iNaturalist/src/main/res/drawable-xhdpi/ic_person_white_24dp.png b/iNaturalist/src/main/res/drawable-xhdpi/ic_person_white_24dp.png
new file mode 100644
index 000000000..aea15f0be
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-xhdpi/ic_person_white_24dp.png differ
diff --git a/iNaturalist/src/main/res/drawable-xhdpi/logo_inat_signup.png b/iNaturalist/src/main/res/drawable-xhdpi/logo_inat_signup.png
new file mode 100644
index 000000000..59fe3c55d
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-xhdpi/logo_inat_signup.png differ
diff --git a/iNaturalist/src/main/res/drawable-xxhdpi/ic_action_email.png b/iNaturalist/src/main/res/drawable-xxhdpi/ic_action_email.png
new file mode 100644
index 000000000..b137ccf1d
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-xxhdpi/ic_action_email.png differ
diff --git a/iNaturalist/src/main/res/drawable-xxhdpi/ic_action_email_white.png b/iNaturalist/src/main/res/drawable-xxhdpi/ic_action_email_white.png
new file mode 100644
index 000000000..4f9ff7703
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-xxhdpi/ic_action_email_white.png differ
diff --git a/iNaturalist/src/main/res/drawable-xxhdpi/ic_check_box_outline_blank_white_24dp.png b/iNaturalist/src/main/res/drawable-xxhdpi/ic_check_box_outline_blank_white_24dp.png
new file mode 100644
index 000000000..339e57c5a
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-xxhdpi/ic_check_box_outline_blank_white_24dp.png differ
diff --git a/iNaturalist/src/main/res/drawable-xxhdpi/ic_check_box_white_24dp.png b/iNaturalist/src/main/res/drawable-xxhdpi/ic_check_box_white_24dp.png
new file mode 100644
index 000000000..3287ddfd3
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-xxhdpi/ic_check_box_white_24dp.png differ
diff --git a/iNaturalist/src/main/res/drawable-xxhdpi/ic_email_white_24dp.png b/iNaturalist/src/main/res/drawable-xxhdpi/ic_email_white_24dp.png
new file mode 100644
index 000000000..fa89f4716
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-xxhdpi/ic_email_white_24dp.png differ
diff --git a/iNaturalist/src/main/res/drawable-xxhdpi/ic_fa_chevron_left.png b/iNaturalist/src/main/res/drawable-xxhdpi/ic_fa_chevron_left.png
new file mode 100644
index 000000000..9c883a505
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-xxhdpi/ic_fa_chevron_left.png differ
diff --git a/iNaturalist/src/main/res/drawable-xxhdpi/ic_fa_chevron_left_dark.png b/iNaturalist/src/main/res/drawable-xxhdpi/ic_fa_chevron_left_dark.png
new file mode 100644
index 000000000..18bc26152
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-xxhdpi/ic_fa_chevron_left_dark.png differ
diff --git a/iNaturalist/src/main/res/drawable-xxhdpi/ic_fa_facebook.png b/iNaturalist/src/main/res/drawable-xxhdpi/ic_fa_facebook.png
new file mode 100644
index 000000000..ba8165b84
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-xxhdpi/ic_fa_facebook.png differ
diff --git a/iNaturalist/src/main/res/drawable-xxhdpi/ic_fa_facebook_white.png b/iNaturalist/src/main/res/drawable-xxhdpi/ic_fa_facebook_white.png
new file mode 100644
index 000000000..5b268241e
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-xxhdpi/ic_fa_facebook_white.png differ
diff --git a/iNaturalist/src/main/res/drawable-xxhdpi/ic_fa_google_plus.png b/iNaturalist/src/main/res/drawable-xxhdpi/ic_fa_google_plus.png
new file mode 100644
index 000000000..ec2c1f8cc
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-xxhdpi/ic_fa_google_plus.png differ
diff --git a/iNaturalist/src/main/res/drawable-xxhdpi/ic_fa_google_plus_white.png b/iNaturalist/src/main/res/drawable-xxhdpi/ic_fa_google_plus_white.png
new file mode 100644
index 000000000..3f78551de
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-xxhdpi/ic_fa_google_plus_white.png differ
diff --git a/iNaturalist/src/main/res/drawable-xxhdpi/ic_lock_white_24dp.png b/iNaturalist/src/main/res/drawable-xxhdpi/ic_lock_white_24dp.png
new file mode 100644
index 000000000..0e52c7c75
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-xxhdpi/ic_lock_white_24dp.png differ
diff --git a/iNaturalist/src/main/res/drawable-xxhdpi/ic_person_white_24dp.png b/iNaturalist/src/main/res/drawable-xxhdpi/ic_person_white_24dp.png
new file mode 100644
index 000000000..184f7418d
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-xxhdpi/ic_person_white_24dp.png differ
diff --git a/iNaturalist/src/main/res/drawable-xxhdpi/logo_inat_signup.png b/iNaturalist/src/main/res/drawable-xxhdpi/logo_inat_signup.png
new file mode 100644
index 000000000..5d64f58a5
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-xxhdpi/logo_inat_signup.png differ
diff --git a/iNaturalist/src/main/res/drawable-xxxhdpi/ic_check_box_outline_blank_white_24dp.png b/iNaturalist/src/main/res/drawable-xxxhdpi/ic_check_box_outline_blank_white_24dp.png
new file mode 100644
index 000000000..f5b45f2ed
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-xxxhdpi/ic_check_box_outline_blank_white_24dp.png differ
diff --git a/iNaturalist/src/main/res/drawable-xxxhdpi/ic_check_box_white_24dp.png b/iNaturalist/src/main/res/drawable-xxxhdpi/ic_check_box_white_24dp.png
new file mode 100644
index 000000000..769210ebd
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-xxxhdpi/ic_check_box_white_24dp.png differ
diff --git a/iNaturalist/src/main/res/drawable-xxxhdpi/ic_email_white_24dp.png b/iNaturalist/src/main/res/drawable-xxxhdpi/ic_email_white_24dp.png
new file mode 100644
index 000000000..6947be051
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-xxxhdpi/ic_email_white_24dp.png differ
diff --git a/iNaturalist/src/main/res/drawable-xxxhdpi/ic_lock_white_24dp.png b/iNaturalist/src/main/res/drawable-xxxhdpi/ic_lock_white_24dp.png
new file mode 100644
index 000000000..a55147be1
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-xxxhdpi/ic_lock_white_24dp.png differ
diff --git a/iNaturalist/src/main/res/drawable-xxxhdpi/ic_person_white_24dp.png b/iNaturalist/src/main/res/drawable-xxxhdpi/ic_person_white_24dp.png
new file mode 100644
index 000000000..33d40d8b6
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-xxxhdpi/ic_person_white_24dp.png differ
diff --git a/iNaturalist/src/main/res/drawable-xxxhdpi/logo_inat_signup.png b/iNaturalist/src/main/res/drawable-xxxhdpi/logo_inat_signup.png
new file mode 100644
index 000000000..b24e6675e
Binary files /dev/null and b/iNaturalist/src/main/res/drawable-xxxhdpi/logo_inat_signup.png differ
diff --git a/iNaturalist/src/main/res/drawable/circular_button.xml b/iNaturalist/src/main/res/drawable/circular_button.xml
index fba8763a3..3b6cc4bf3 100644
--- a/iNaturalist/src/main/res/drawable/circular_button.xml
+++ b/iNaturalist/src/main/res/drawable/circular_button.xml
@@ -3,44 +3,62 @@
xmlns:android="http://schemas.android.com/apk/res/android">
-
-
+
+
+ android:left="5dp"
+ android:top="5dp"
+ android:right="5dp"
+ android:bottom="5dp" />
- -
-
+
-
+
+
+
+
+
+ -
+
+
+
+ android:left="5dp"
+ android:top="5dp"
+ android:right="5dp"
+ android:bottom="5dp" />
- -
-
+
-
+
+
+ android:left="5dp"
+ android:top="5dp"
+ android:right="5dp"
+ android:bottom="5dp" />
+
\ No newline at end of file
diff --git a/iNaturalist/src/main/res/drawable/circular_button_text.xml b/iNaturalist/src/main/res/drawable/circular_button_text.xml
new file mode 100644
index 000000000..95cbc6501
--- /dev/null
+++ b/iNaturalist/src/main/res/drawable/circular_button_text.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/iNaturalist/src/main/res/drawable/email_button_icon.xml b/iNaturalist/src/main/res/drawable/email_button_icon.xml
new file mode 100644
index 000000000..c16a5b134
--- /dev/null
+++ b/iNaturalist/src/main/res/drawable/email_button_icon.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/iNaturalist/src/main/res/drawable/facebook_button_icon.xml b/iNaturalist/src/main/res/drawable/facebook_button_icon.xml
new file mode 100644
index 000000000..21003a4c1
--- /dev/null
+++ b/iNaturalist/src/main/res/drawable/facebook_button_icon.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/iNaturalist/src/main/res/drawable/gplus_button_icon.xml b/iNaturalist/src/main/res/drawable/gplus_button_icon.xml
new file mode 100644
index 000000000..03a682759
--- /dev/null
+++ b/iNaturalist/src/main/res/drawable/gplus_button_icon.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/iNaturalist/src/main/res/drawable/login_signup_back_button.xml b/iNaturalist/src/main/res/drawable/login_signup_back_button.xml
new file mode 100644
index 000000000..1798b7760
--- /dev/null
+++ b/iNaturalist/src/main/res/drawable/login_signup_back_button.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/iNaturalist/src/main/res/drawable/oval_button.xml b/iNaturalist/src/main/res/drawable/oval_button.xml
new file mode 100644
index 000000000..6c60cc55b
--- /dev/null
+++ b/iNaturalist/src/main/res/drawable/oval_button.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/iNaturalist/src/main/res/drawable/oval_button_normal.xml b/iNaturalist/src/main/res/drawable/oval_button_normal.xml
new file mode 100644
index 000000000..42d3ccd06
--- /dev/null
+++ b/iNaturalist/src/main/res/drawable/oval_button_normal.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
diff --git a/iNaturalist/src/main/res/drawable/semi_transparent_button_text.xml b/iNaturalist/src/main/res/drawable/semi_transparent_button_text.xml
new file mode 100644
index 000000000..204f7509a
--- /dev/null
+++ b/iNaturalist/src/main/res/drawable/semi_transparent_button_text.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/iNaturalist/src/main/res/drawable/semi_transparent_button_text2.xml b/iNaturalist/src/main/res/drawable/semi_transparent_button_text2.xml
new file mode 100644
index 000000000..ec4c5ae6a
--- /dev/null
+++ b/iNaturalist/src/main/res/drawable/semi_transparent_button_text2.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/iNaturalist/src/main/res/drawable/signup_background_1.jpg b/iNaturalist/src/main/res/drawable/signup_background_1.jpg
new file mode 100644
index 000000000..f596d7b78
Binary files /dev/null and b/iNaturalist/src/main/res/drawable/signup_background_1.jpg differ
diff --git a/iNaturalist/src/main/res/drawable/signup_background_1_blurred.jpg b/iNaturalist/src/main/res/drawable/signup_background_1_blurred.jpg
new file mode 100644
index 000000000..ba564fe88
Binary files /dev/null and b/iNaturalist/src/main/res/drawable/signup_background_1_blurred.jpg differ
diff --git a/iNaturalist/src/main/res/drawable/signup_background_2.jpg b/iNaturalist/src/main/res/drawable/signup_background_2.jpg
new file mode 100644
index 000000000..f8561e549
Binary files /dev/null and b/iNaturalist/src/main/res/drawable/signup_background_2.jpg differ
diff --git a/iNaturalist/src/main/res/drawable/signup_background_2_blurred.jpg b/iNaturalist/src/main/res/drawable/signup_background_2_blurred.jpg
new file mode 100644
index 000000000..30c97b6a8
Binary files /dev/null and b/iNaturalist/src/main/res/drawable/signup_background_2_blurred.jpg differ
diff --git a/iNaturalist/src/main/res/drawable/signup_background_3.jpg b/iNaturalist/src/main/res/drawable/signup_background_3.jpg
new file mode 100644
index 000000000..7ff71743b
Binary files /dev/null and b/iNaturalist/src/main/res/drawable/signup_background_3.jpg differ
diff --git a/iNaturalist/src/main/res/drawable/signup_background_3_blurred.jpg b/iNaturalist/src/main/res/drawable/signup_background_3_blurred.jpg
new file mode 100644
index 000000000..ac543e4d9
Binary files /dev/null and b/iNaturalist/src/main/res/drawable/signup_background_3_blurred.jpg differ
diff --git a/iNaturalist/src/main/res/layout/login_signup.xml b/iNaturalist/src/main/res/layout/login_signup.xml
new file mode 100644
index 000000000..3be94c479
--- /dev/null
+++ b/iNaturalist/src/main/res/layout/login_signup.xml
@@ -0,0 +1,312 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/iNaturalist/src/main/res/layout/onboarding.xml b/iNaturalist/src/main/res/layout/onboarding.xml
new file mode 100644
index 000000000..4637beee2
--- /dev/null
+++ b/iNaturalist/src/main/res/layout/onboarding.xml
@@ -0,0 +1,246 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/iNaturalist/src/main/res/layout/preferences.xml b/iNaturalist/src/main/res/layout/preferences.xml
index 30a6d6559..794a9a3b5 100644
--- a/iNaturalist/src/main/res/layout/preferences.xml
+++ b/iNaturalist/src/main/res/layout/preferences.xml
@@ -22,7 +22,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent" >
-
+ #33669900
#669900
#ff006616
diff --git a/iNaturalist/src/main/res/values/strings.xml b/iNaturalist/src/main/res/values/strings.xml
index 1943282e4..3a0bacd3d 100644
--- a/iNaturalist/src/main/res/values/strings.xml
+++ b/iNaturalist/src/main/res/values/strings.xml
@@ -377,5 +377,21 @@
Select Source
Help with iNaturalist (version %1$s - %2$s)
Send email...
+ Log In with
+ Facebook
+ Google+
+ Email
+ Sign Up with
+ Skip >
+ Already have an account?
+ Log In
+ Sign Up
+ Min. 6 characters
+ Learn More]]>
+ Content Licensing
+ Check this box if you want to apply a Create Commons Attribution-NonCommercial license to your photos. You can choose a different license or remove the license later, but this is the best license for sharing with researchers.
+ Username or Email
+ Forgot?
+ Or Log In With: