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