type) {
+ return objectGraph.get(type);
+ }
+}
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/RootModule.java b/app/src/main/java/com/donnfelker/android/bootstrap/RootModule.java
new file mode 100644
index 0000000..e8e1e5d
--- /dev/null
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/RootModule.java
@@ -0,0 +1,15 @@
+package com.donnfelker.android.bootstrap;
+
+import dagger.Module;
+
+/**
+ * Add all the other modules to this one.
+ */
+@Module(
+ includes = {
+ AndroidModule.class,
+ BootstrapModule.class
+ }
+)
+public class RootModule {
+}
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/authenticator/AccountAuthenticatorService.java b/app/src/main/java/com/donnfelker/android/bootstrap/authenticator/AccountAuthenticatorService.java
index e9eb16c..1dcd71d 100644
--- a/app/src/main/java/com/donnfelker/android/bootstrap/authenticator/AccountAuthenticatorService.java
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/authenticator/AccountAuthenticatorService.java
@@ -1,26 +1,31 @@
package com.donnfelker.android.bootstrap.authenticator;
-import static android.accounts.AccountManager.ACTION_AUTHENTICATOR_INTENT;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
+import static android.accounts.AccountManager.ACTION_AUTHENTICATOR_INTENT;
+
/**
- * Authenticator service that returns a subclass of AbstractAccountAuthenticator in onBind()
+ * Authenticator service that returns a subclass of AbstractAccountAuthenticator in onBind().
*/
public class AccountAuthenticatorService extends Service {
- private static BootstrapAccountAuthenticator AUTHENTICATOR = null;
+ private static BootstrapAccountAuthenticator authenticator = null;
@Override
- public IBinder onBind(Intent intent) {
- return intent.getAction().equals(ACTION_AUTHENTICATOR_INTENT) ? getAuthenticator().getIBinder() : null;
+ public IBinder onBind(final Intent intent) {
+ if (intent != null && ACTION_AUTHENTICATOR_INTENT.equals(intent.getAction())) {
+ return getAuthenticator().getIBinder();
+ }
+ return null;
}
private BootstrapAccountAuthenticator getAuthenticator() {
- if (AUTHENTICATOR == null)
- AUTHENTICATOR = new BootstrapAccountAuthenticator(this);
- return AUTHENTICATOR;
+ if (authenticator == null) {
+ authenticator = new BootstrapAccountAuthenticator(this);
+ }
+ return authenticator;
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/authenticator/ActionBarAccountAuthenticatorActivity.java b/app/src/main/java/com/donnfelker/android/bootstrap/authenticator/ActionBarAccountAuthenticatorActivity.java
new file mode 100644
index 0000000..d554a38
--- /dev/null
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/authenticator/ActionBarAccountAuthenticatorActivity.java
@@ -0,0 +1,72 @@
+package com.donnfelker.android.bootstrap.authenticator;
+
+import android.accounts.AccountAuthenticatorResponse;
+import android.accounts.AccountManager;
+import android.os.Bundle;
+import android.support.v7.app.ActionBarActivity;
+
+
+/**
+ * Base class for implementing an Activity that is used to help implement an
+ * AbstractAccountAuthenticator. If the AbstractAccountAuthenticator needs to use an activity
+ * to handle the request then it can have the activity extend ActionBarAccountAuthenticatorActivity.
+ * The AbstractAccountAuthenticator passes in the response to the intent using the following:
+ *
+ * intent.putExtra({@link android.accounts.AccountManager#KEY_ACCOUNT_AUTHENTICATOR_RESPONSE}, response);
+ *
+ * The activity then sets the result that is to be handed to the response via
+ * {@link #setAccountAuthenticatorResult(android.os.Bundle)}.
+ * This result will be sent as the result of the request when the activity finishes. If this
+ * is never set or if it is set to null then error
+ * {@link android.accounts.AccountManager#ERROR_CODE_CANCELED}
+ * will be called on the response.
+ */
+public class ActionBarAccountAuthenticatorActivity extends ActionBarActivity {
+ private AccountAuthenticatorResponse accountAuthenticatorResponse = null;
+ private Bundle resultBundle = null;
+
+ /**
+ * Set the result that is to be sent as the result of the request that caused this
+ * Activity to be launched. If result is null or this method is never called then
+ * the request will be canceled.
+ *
+ * @param result this is returned as the result of the AbstractAccountAuthenticator request
+ */
+ public final void setAccountAuthenticatorResult(Bundle result) {
+ resultBundle = result;
+ }
+
+ /**
+ * Retreives the AccountAuthenticatorResponse from either the intent of the icicle, if the
+ * icicle is non-zero.
+ *
+ * @param icicle the save instance data of this Activity, may be null
+ */
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ accountAuthenticatorResponse =
+ getIntent().getParcelableExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE);
+
+ if (accountAuthenticatorResponse != null) {
+ accountAuthenticatorResponse.onRequestContinued();
+ }
+ }
+
+ /**
+ * Sends the result or a Constants.ERROR_CODE_CANCELED error if a result isn't present.
+ */
+ public void finish() {
+ if (accountAuthenticatorResponse != null) {
+ // send the result bundle back if set, otherwise send an error.
+ if (resultBundle != null) {
+ accountAuthenticatorResponse.onResult(resultBundle);
+ } else {
+ accountAuthenticatorResponse.onError(AccountManager.ERROR_CODE_CANCELED,
+ "canceled");
+ }
+ accountAuthenticatorResponse = null;
+ }
+ super.finish();
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/authenticator/ApiKeyProvider.java b/app/src/main/java/com/donnfelker/android/bootstrap/authenticator/ApiKeyProvider.java
index 500a6f0..ae4acf3 100644
--- a/app/src/main/java/com/donnfelker/android/bootstrap/authenticator/ApiKeyProvider.java
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/authenticator/ApiKeyProvider.java
@@ -2,37 +2,51 @@
package com.donnfelker.android.bootstrap.authenticator;
-import static android.accounts.AccountManager.KEY_AUTHTOKEN;
-
import android.accounts.AccountManager;
import android.accounts.AccountManagerFuture;
import android.accounts.AccountsException;
import android.app.Activity;
import android.os.Bundle;
-import com.donnfelker.android.bootstrap.core.Constants;
-import com.google.inject.Inject;
-
import java.io.IOException;
+import javax.inject.Inject;
+
+import static android.accounts.AccountManager.KEY_AUTHTOKEN;
+import static com.donnfelker.android.bootstrap.core.Constants.Auth.AUTHTOKEN_TYPE;
+import static com.donnfelker.android.bootstrap.core.Constants.Auth.BOOTSTRAP_ACCOUNT_TYPE;
+
/**
* Bridge class that obtains a API key for the currently configured account
*/
public class ApiKeyProvider {
- @Inject private Activity activity;
- @Inject private AccountManager accountManager;
+ private AccountManager accountManager;
+
+ public ApiKeyProvider(AccountManager accountManager) {
+ this.accountManager = accountManager;
+ }
/**
- * This call blocks, so shouldn't be called on the UI thread
+ * This call blocks, so shouldn't be called on the UI thread.
+ * This call is what makes the login screen pop up. If the user has
+ * not logged in there will no accounts in the {@link android.accounts.AccountManager}
+ * and therefore the Activity that is referenced in the
+ * {@link com.donnfelker.android.bootstrap.authenticator.BootstrapAccountAuthenticator} will get started.
+ * If you want to remove the authentication then you can comment out the code below and return a string such as
+ * "foo" and the authentication process will not be kicked off. Alternatively, you can remove this class
+ * completely and clean up any references to the authenticator.
+ *
*
- * @return API key to be used for authorization with a {@link com.donnfelker.android.bootstrap.core.BootstrapService} instance
+ * @return API key to be used for authorization with a
+ * {@link com.donnfelker.android.bootstrap.core.BootstrapService} instance
* @throws AccountsException
* @throws IOException
*/
- public String getAuthKey() throws AccountsException, IOException {
- AccountManagerFuture accountManagerFuture = accountManager.getAuthTokenByFeatures(Constants.Auth.BOOTSTRAP_ACCOUNT_TYPE,
- Constants.Auth.AUTHTOKEN_TYPE, new String[0], activity, null, null, null, null);
+ public String getAuthKey(final Activity activity) throws AccountsException, IOException {
+ final AccountManagerFuture accountManagerFuture
+ = accountManager.getAuthTokenByFeatures(BOOTSTRAP_ACCOUNT_TYPE,
+ AUTHTOKEN_TYPE, new String[0], activity, null, null, null, null);
return accountManagerFuture.getResult().getString(KEY_AUTHTOKEN);
}
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/authenticator/BootstrapAccountAuthenticator.java b/app/src/main/java/com/donnfelker/android/bootstrap/authenticator/BootstrapAccountAuthenticator.java
index 1913d2b..81e9d18 100644
--- a/app/src/main/java/com/donnfelker/android/bootstrap/authenticator/BootstrapAccountAuthenticator.java
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/authenticator/BootstrapAccountAuthenticator.java
@@ -1,13 +1,6 @@
package com.donnfelker.android.bootstrap.authenticator;
-import static android.accounts.AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE;
-import static android.accounts.AccountManager.KEY_ACCOUNT_NAME;
-import static android.accounts.AccountManager.KEY_ACCOUNT_TYPE;
-import static android.accounts.AccountManager.KEY_AUTHTOKEN;
-import static android.accounts.AccountManager.KEY_BOOLEAN_RESULT;
-import static android.accounts.AccountManager.KEY_INTENT;
-import static com.donnfelker.android.bootstrap.authenticator.BootstrapAuthenticatorActivity.PARAM_AUTHTOKEN_TYPE;
import android.accounts.AbstractAccountAuthenticator;
import android.accounts.Account;
import android.accounts.AccountAuthenticatorResponse;
@@ -16,9 +9,17 @@
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
-import android.util.Log;
import com.donnfelker.android.bootstrap.core.Constants;
+import com.donnfelker.android.bootstrap.util.Ln;
+
+import static android.accounts.AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE;
+import static android.accounts.AccountManager.KEY_ACCOUNT_NAME;
+import static android.accounts.AccountManager.KEY_ACCOUNT_TYPE;
+import static android.accounts.AccountManager.KEY_AUTHTOKEN;
+import static android.accounts.AccountManager.KEY_BOOLEAN_RESULT;
+import static android.accounts.AccountManager.KEY_INTENT;
+import static com.donnfelker.android.bootstrap.authenticator.BootstrapAuthenticatorActivity.PARAM_AUTHTOKEN_TYPE;
class BootstrapAccountAuthenticator extends AbstractAccountAuthenticator {
@@ -26,42 +27,49 @@ class BootstrapAccountAuthenticator extends AbstractAccountAuthenticator {
private final Context context;
- public BootstrapAccountAuthenticator(Context context) {
+ public BootstrapAccountAuthenticator(final Context context) {
super(context);
this.context = context;
}
/*
- * The user has requested to add a new account to the system. We return an intent that will launch our login screen
- * if the user has not logged in yet, otherwise our activity will just pass the user's credentials on to the account
- * manager.
+ * The user has requested to add a new account to the system. We return an intent that will
+ * launch our login screen if the user has not logged in yet, otherwise our activity will
+ * just pass the user's credentials on to the account manager.
*/
@Override
- public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType,
- String[] requiredFeatures, Bundle options) throws NetworkErrorException {
+ public Bundle addAccount(final AccountAuthenticatorResponse response, final String accountType,
+ final String authTokenType, final String[] requiredFeatures,
+ final Bundle options) throws NetworkErrorException {
final Intent intent = new Intent(context, BootstrapAuthenticatorActivity.class);
intent.putExtra(PARAM_AUTHTOKEN_TYPE, authTokenType);
intent.putExtra(KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
+
final Bundle bundle = new Bundle();
bundle.putParcelable(KEY_INTENT, intent);
+
return bundle;
}
@Override
- public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) {
+ public Bundle confirmCredentials(final AccountAuthenticatorResponse response,
+ final Account account, final Bundle options) {
return null;
}
@Override
- public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
+ public Bundle editProperties(final AccountAuthenticatorResponse response,
+ final String accountType) {
return null;
}
/**
* This method gets called when the
- * {@link com.donnfelker.android.bootstrap.authenticator.ApiKeyProvider#getAuthKey()} methods gets invoked.
+ * {@link com.donnfelker.android.bootstrap.authenticator.ApiKeyProvider#getAuthKey()}
+ * methods gets invoked.
* This happens on a different process, so debugging it can be a beast.
+ *
* @param response
* @param account
* @param authTokenType
@@ -70,35 +78,39 @@ public Bundle editProperties(AccountAuthenticatorResponse response, String accou
* @throws NetworkErrorException
*/
@Override
- public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType,
- Bundle options) throws NetworkErrorException {
+ public Bundle getAuthToken(final AccountAuthenticatorResponse response,
+ final Account account, final String authTokenType,
+ final Bundle options) throws NetworkErrorException {
+
+ Ln.d("Attempting to get authToken");
- Log.d("AccountAuthenticator", "Attempting to get authToken");
+ final String authToken = AccountManager.get(context).peekAuthToken(account, authTokenType);
- String authToken = AccountManager.get(context).peekAuthToken(account, authTokenType);
- Bundle bundle = new Bundle();
+ final Bundle bundle = new Bundle();
bundle.putString(KEY_ACCOUNT_NAME, account.name);
bundle.putString(KEY_ACCOUNT_TYPE, Constants.Auth.BOOTSTRAP_ACCOUNT_TYPE);
bundle.putString(KEY_AUTHTOKEN, authToken);
+
return bundle;
}
@Override
- public String getAuthTokenLabel(String authTokenType) {
+ public String getAuthTokenLabel(final String authTokenType) {
return authTokenType.equals(Constants.Auth.AUTHTOKEN_TYPE) ? authTokenType : null;
}
@Override
- public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features)
- throws NetworkErrorException {
+ public Bundle hasFeatures(final AccountAuthenticatorResponse response, final Account account,
+ final String[] features) throws NetworkErrorException {
final Bundle result = new Bundle();
result.putBoolean(KEY_BOOLEAN_RESULT, false);
return result;
}
@Override
- public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType,
- Bundle options) {
+ public Bundle updateCredentials(final AccountAuthenticatorResponse response,
+ final Account account, final String authTokenType,
+ final Bundle options) {
return null;
}
}
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/authenticator/BootstrapAuthenticatorActivity.java b/app/src/main/java/com/donnfelker/android/bootstrap/authenticator/BootstrapAuthenticatorActivity.java
index 84d5627..4d39073 100644
--- a/app/src/main/java/com/donnfelker/android/bootstrap/authenticator/BootstrapAuthenticatorActivity.java
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/authenticator/BootstrapAuthenticatorActivity.java
@@ -1,4 +1,3 @@
-
package com.donnfelker.android.bootstrap.authenticator;
import static android.R.layout.simple_dropdown_item_1line;
@@ -9,13 +8,6 @@
import static android.view.KeyEvent.ACTION_DOWN;
import static android.view.KeyEvent.KEYCODE_ENTER;
import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
-import static com.donnfelker.android.bootstrap.core.Constants.Http.HEADER_PARSE_APP_ID;
-import static com.donnfelker.android.bootstrap.core.Constants.Http.HEADER_PARSE_REST_API_KEY;
-import static com.donnfelker.android.bootstrap.core.Constants.Http.PARSE_APP_ID;
-import static com.donnfelker.android.bootstrap.core.Constants.Http.PARSE_REST_API_KEY;
-import static com.donnfelker.android.bootstrap.core.Constants.Http.URL_AUTH;
-import static com.github.kevinsawicki.http.HttpRequest.get;
-import static com.github.kevinsawicki.http.HttpRequest.post;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.Dialog;
@@ -27,7 +19,6 @@
import android.text.Html;
import android.text.TextWatcher;
import android.text.method.LinkMovementMethod;
-import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnKeyListener;
@@ -38,39 +29,39 @@
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
-import com.donnfelker.android.bootstrap.core.Constants;
-import com.donnfelker.android.bootstrap.core.User;
-import com.github.kevinsawicki.http.HttpRequest;
-import com.github.kevinsawicki.wishlist.Toaster;
+import com.donnfelker.android.bootstrap.Injector;
+import com.donnfelker.android.bootstrap.R;
import com.donnfelker.android.bootstrap.R.id;
import com.donnfelker.android.bootstrap.R.layout;
import com.donnfelker.android.bootstrap.R.string;
+import com.donnfelker.android.bootstrap.core.BootstrapService;
+import com.donnfelker.android.bootstrap.core.Constants;
+import com.donnfelker.android.bootstrap.core.User;
+import com.donnfelker.android.bootstrap.events.UnAuthorizedErrorEvent;
import com.donnfelker.android.bootstrap.ui.TextWatcherAdapter;
-import com.github.rtyley.android.sherlock.roboguice.activity.RoboSherlockAccountAuthenticatorActivity;
-import com.google.gson.Gson;
+import com.donnfelker.android.bootstrap.util.Ln;
+import com.donnfelker.android.bootstrap.util.SafeAsyncTask;
+import com.github.kevinsawicki.wishlist.Toaster;
+import com.squareup.otto.Bus;
+import com.squareup.otto.Subscribe;
-import java.net.URLEncoder;
+import javax.inject.Inject;
import java.util.ArrayList;
import java.util.List;
-import roboguice.inject.InjectView;
-import roboguice.util.Ln;
-import roboguice.util.RoboAsyncTask;
-import roboguice.util.Strings;
-
-import static com.donnfelker.android.bootstrap.core.Constants.Http.USERNAME;
-import static com.donnfelker.android.bootstrap.core.Constants.Http.PASSWORD;
+import butterknife.InjectView;
+import butterknife.Views;
+import retrofit.RetrofitError;
/**
* Activity to authenticate the user against an API (example API on Parse.com)
*/
-public class BootstrapAuthenticatorActivity extends
- RoboSherlockAccountAuthenticatorActivity {
+public class BootstrapAuthenticatorActivity extends ActionBarAccountAuthenticatorActivity {
/**
- * PARAM_CONFIRMCREDENTIALS
+ * PARAM_CONFIRM_CREDENTIALS
*/
- public static final String PARAM_CONFIRMCREDENTIALS = "confirmCredentials";
+ public static final String PARAM_CONFIRM_CREDENTIALS = "confirmCredentials";
/**
* PARAM_PASSWORD
@@ -90,18 +81,16 @@ public class BootstrapAuthenticatorActivity extends
private AccountManager accountManager;
- @InjectView(id.et_email)
- private AutoCompleteTextView emailText;
+ @Inject BootstrapService bootstrapService;
+ @Inject Bus bus;
- @InjectView(id.et_password)
- private EditText passwordText;
+ @InjectView(id.et_email) protected AutoCompleteTextView emailText;
+ @InjectView(id.et_password) protected EditText passwordText;
+ @InjectView(id.b_signin) protected Button signInButton;
- @InjectView(id.b_signin)
- private Button signinButton;
+ private final TextWatcher watcher = validationTextWatcher();
- private TextWatcher watcher = validationTextWatcher();
-
- private RoboAsyncTask authenticationTask;
+ private SafeAsyncTask authenticationTask;
private String authToken;
private String authTokenType;
@@ -132,25 +121,30 @@ public class BootstrapAuthenticatorActivity extends
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
+ Injector.inject(this);
+
accountManager = AccountManager.get(this);
+
final Intent intent = getIntent();
email = intent.getStringExtra(PARAM_USERNAME);
authTokenType = intent.getStringExtra(PARAM_AUTHTOKEN_TYPE);
+ confirmCredentials = intent.getBooleanExtra(PARAM_CONFIRM_CREDENTIALS, false);
+
requestNewAccount = email == null;
- confirmCredentials = intent.getBooleanExtra(PARAM_CONFIRMCREDENTIALS,
- false);
setContentView(layout.login_activity);
+ Views.inject(this);
+
emailText.setAdapter(new ArrayAdapter(this,
simple_dropdown_item_1line, userEmailAccounts()));
passwordText.setOnKeyListener(new OnKeyListener() {
- public boolean onKey(View v, int keyCode, KeyEvent event) {
+ public boolean onKey(final View v, final int keyCode, final KeyEvent event) {
if (event != null && ACTION_DOWN == event.getAction()
- && keyCode == KEYCODE_ENTER && signinButton.isEnabled()) {
- handleLogin(signinButton);
+ && keyCode == KEYCODE_ENTER && signInButton.isEnabled()) {
+ handleLogin(signInButton);
return true;
}
return false;
@@ -159,10 +153,10 @@ public boolean onKey(View v, int keyCode, KeyEvent event) {
passwordText.setOnEditorActionListener(new OnEditorActionListener() {
- public boolean onEditorAction(TextView v, int actionId,
- KeyEvent event) {
- if (actionId == IME_ACTION_DONE && signinButton.isEnabled()) {
- handleLogin(signinButton);
+ public boolean onEditorAction(final TextView v, final int actionId,
+ final KeyEvent event) {
+ if (actionId == IME_ACTION_DONE && signInButton.isEnabled()) {
+ handleLogin(signInButton);
return true;
}
return false;
@@ -172,22 +166,23 @@ public boolean onEditorAction(TextView v, int actionId,
emailText.addTextChangedListener(watcher);
passwordText.addTextChangedListener(watcher);
- TextView signupText = (TextView) findViewById(id.tv_signup);
- signupText.setMovementMethod(LinkMovementMethod.getInstance());
- signupText.setText(Html.fromHtml(getString(string.signup_link)));
+ final TextView signUpText = (TextView) findViewById(id.tv_signup);
+ signUpText.setMovementMethod(LinkMovementMethod.getInstance());
+ signUpText.setText(Html.fromHtml(getString(string.signup_link)));
}
private List userEmailAccounts() {
- Account[] accounts = accountManager.getAccountsByType("com.google");
- List emailAddresses = new ArrayList(accounts.length);
- for (Account account : accounts)
+ final Account[] accounts = accountManager.getAccountsByType("com.google");
+ final List emailAddresses = new ArrayList(accounts.length);
+ for (final Account account : accounts) {
emailAddresses.add(account.name);
+ }
return emailAddresses;
}
private TextWatcher validationTextWatcher() {
return new TextWatcherAdapter() {
- public void afterTextChanged(Editable gitDirEditText) {
+ public void afterTextChanged(final Editable gitDirEditText) {
updateUIWithValidation();
}
@@ -197,15 +192,22 @@ public void afterTextChanged(Editable gitDirEditText) {
@Override
protected void onResume() {
super.onResume();
+ bus.register(this);
updateUIWithValidation();
}
+ @Override
+ protected void onPause() {
+ super.onPause();
+ bus.unregister(this);
+ }
+
private void updateUIWithValidation() {
- boolean populated = populated(emailText) && populated(passwordText);
- signinButton.setEnabled(populated);
+ final boolean populated = populated(emailText) && populated(passwordText);
+ signInButton.setEnabled(populated);
}
- private boolean populated(EditText editText) {
+ private boolean populated(final EditText editText) {
return editText.length() > 0;
}
@@ -216,14 +218,21 @@ protected Dialog onCreateDialog(int id) {
dialog.setIndeterminate(true);
dialog.setCancelable(true);
dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
- public void onCancel(DialogInterface dialog) {
- if (authenticationTask != null)
+ public void onCancel(final DialogInterface dialog) {
+ if (authenticationTask != null) {
authenticationTask.cancel(true);
+ }
}
});
return dialog;
}
+ @Subscribe
+ public void onUnAuthorizedErrorEvent(UnAuthorizedErrorEvent unAuthorizedErrorEvent) {
+ // Could not authorize for some reason.
+ Toaster.showLong(BootstrapAuthenticatorActivity.this, R.string.message_bad_credentials);
+ }
+
/**
* Handles onClick event on the Submit button. Sends username/password to
* the server for authentication.
@@ -232,53 +241,43 @@ public void onCancel(DialogInterface dialog) {
*
* @param view
*/
- public void handleLogin(View view) {
- if (authenticationTask != null)
+ public void handleLogin(final View view) {
+ if (authenticationTask != null) {
return;
+ }
- if (requestNewAccount)
+ if (requestNewAccount) {
email = emailText.getText().toString();
+ }
+
password = passwordText.getText().toString();
showProgress();
- authenticationTask = new RoboAsyncTask(this) {
+ authenticationTask = new SafeAsyncTask() {
public Boolean call() throws Exception {
- final String query = String.format("%s=%s&%s=%s", PARAM_USERNAME, email, PARAM_PASSWORD, password);
-
- HttpRequest request = get(URL_AUTH + "?" + query)
- .header(HEADER_PARSE_APP_ID, PARSE_APP_ID)
- .header(HEADER_PARSE_REST_API_KEY, PARSE_REST_API_KEY);
+ final String query = String.format("%s=%s&%s=%s",
+ PARAM_USERNAME, email, PARAM_PASSWORD, password);
+ User loginResponse = bootstrapService.authenticate(email, password);
+ token = loginResponse.getSessionToken();
- Log.d("Auth", "response=" + request.code());
-
- if(request.ok()) {
- final User model = new Gson().fromJson(Strings.toString(request.buffer()), User.class);
- token = model.getSessionToken();
- }
-
- return request.ok();
+ return true;
}
@Override
- protected void onException(Exception e) throws RuntimeException {
- Throwable cause = e.getCause() != null ? e.getCause() : e;
-
- String message;
- // A 404 is returned as an Exception with this message
- if ("Received authentication challenge is null".equals(cause
- .getMessage()))
- message = getResources().getString(
- string.message_bad_credentials);
- else
- message = cause.getMessage();
-
- Toaster.showLong(BootstrapAuthenticatorActivity.this, message);
+ protected void onException(final Exception e) throws RuntimeException {
+ // Retrofit Errors are handled inside of the {
+ if(!(e instanceof RetrofitError)) {
+ final Throwable cause = e.getCause() != null ? e.getCause() : e;
+ if(cause != null) {
+ Toaster.showLong(BootstrapAuthenticatorActivity.this, cause.getMessage());
+ }
+ }
}
@Override
- public void onSuccess(Boolean authSuccess) {
+ public void onSuccess(final Boolean authSuccess) {
onAuthenticationResult(authSuccess);
}
@@ -298,7 +297,7 @@ protected void onFinally() throws RuntimeException {
*
* @param result
*/
- protected void finishConfirmCredentials(boolean result) {
+ protected void finishConfirmCredentials(final boolean result) {
final Account account = new Account(email, Constants.Auth.BOOTSTRAP_ACCOUNT_TYPE);
accountManager.setPassword(account, password);
@@ -319,17 +318,23 @@ protected void finishConfirmCredentials(boolean result) {
protected void finishLogin() {
final Account account = new Account(email, Constants.Auth.BOOTSTRAP_ACCOUNT_TYPE);
- if (requestNewAccount)
+ if (requestNewAccount) {
accountManager.addAccountExplicitly(account, password, null);
- else
+ } else {
accountManager.setPassword(account, password);
- final Intent intent = new Intent();
+ }
+
authToken = token;
+
+ final Intent intent = new Intent();
intent.putExtra(KEY_ACCOUNT_NAME, email);
intent.putExtra(KEY_ACCOUNT_TYPE, Constants.Auth.BOOTSTRAP_ACCOUNT_TYPE);
+
if (authTokenType != null
- && authTokenType.equals(Constants.Auth.AUTHTOKEN_TYPE))
+ && authTokenType.equals(Constants.Auth.AUTHTOKEN_TYPE)) {
intent.putExtra(KEY_AUTHTOKEN, authToken);
+ }
+
setAccountAuthenticatorResult(intent.getExtras());
setResult(RESULT_OK, intent);
finish();
@@ -356,20 +361,22 @@ protected void showProgress() {
*
* @param result
*/
- public void onAuthenticationResult(boolean result) {
- if (result)
- if (!confirmCredentials)
+ public void onAuthenticationResult(final boolean result) {
+ if (result) {
+ if (!confirmCredentials) {
finishLogin();
- else
+ } else {
finishConfirmCredentials(true);
- else {
+ }
+ } else {
Ln.d("onAuthenticationResult: failed to authenticate");
- if (requestNewAccount)
+ if (requestNewAccount) {
Toaster.showLong(BootstrapAuthenticatorActivity.this,
string.message_auth_failed_new_account);
- else
+ } else {
Toaster.showLong(BootstrapAuthenticatorActivity.this,
string.message_auth_failed);
+ }
}
}
}
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/authenticator/LogoutService.java b/app/src/main/java/com/donnfelker/android/bootstrap/authenticator/LogoutService.java
index 871a018..603dd97 100644
--- a/app/src/main/java/com/donnfelker/android/bootstrap/authenticator/LogoutService.java
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/authenticator/LogoutService.java
@@ -4,70 +4,76 @@
import android.accounts.AccountManager;
import android.accounts.AccountManagerFuture;
import android.content.Context;
-import android.util.Log;
import com.donnfelker.android.bootstrap.core.Constants;
-import com.google.inject.Inject;
+import com.donnfelker.android.bootstrap.util.Ln;
+import com.donnfelker.android.bootstrap.util.SafeAsyncTask;
-import java.util.concurrent.Callable;
-import java.util.concurrent.Executor;
+import javax.inject.Inject;
-import roboguice.inject.ContextSingleton;
-import roboguice.util.RoboAsyncTask;
-@ContextSingleton
+/**
+ * Class used for logging a user out.
+ */
public class LogoutService {
- @Inject protected Context context;
- @Inject protected AccountManager accountManager;
-
+ protected final Context context;
+ protected final AccountManager accountManager;
+ @Inject
+ public LogoutService(final Context context, final AccountManager accountManager) {
+ this.context = context;
+ this.accountManager = accountManager;
+ }
public void logout(final Runnable onSuccess) {
-
new LogoutTask(context, onSuccess).execute();
}
- private static class LogoutTask extends RoboAsyncTask {
+ private static class LogoutTask extends SafeAsyncTask {
- private Runnable onSuccess;
+ private final Context taskContext;
+ private final Runnable onSuccess;
- protected LogoutTask(Context context, Runnable onSuccess) {
- super(context);
+ protected LogoutTask(final Context context, final Runnable onSuccess) {
+ this.taskContext = context;
this.onSuccess = onSuccess;
}
@Override
public Boolean call() throws Exception {
- final Account[] accounts = AccountManager.get(context).getAccountsByType(Constants.Auth.BOOTSTRAP_ACCOUNT_TYPE);
- if(accounts.length > 0) {
- AccountManagerFuture removeAccountFuture = AccountManager.get(context).removeAccount
- (accounts[0], null, null);
- if(removeAccountFuture.getResult() == true) {
- return true;
- } else {
- return false;
+ final AccountManager accountManagerWithContext = AccountManager.get(taskContext);
+ if (accountManagerWithContext != null) {
+ final Account[] accounts = accountManagerWithContext
+ .getAccountsByType(Constants.Auth.BOOTSTRAP_ACCOUNT_TYPE);
+ if (accounts.length > 0) {
+ final AccountManagerFuture removeAccountFuture
+ = accountManagerWithContext.removeAccount(accounts[0], null, null);
+
+ return removeAccountFuture.getResult();
}
+ } else {
+ Ln.w("accountManagerWithContext is null");
}
return false;
}
@Override
- protected void onSuccess(Boolean accountWasRemoved) throws Exception {
+ protected void onSuccess(final Boolean accountWasRemoved) throws Exception {
super.onSuccess(accountWasRemoved);
- Log.d("LOGOUT_SERVICE", "Logout succeeded:" + accountWasRemoved);
+ Ln.d("Logout succeeded: %s", accountWasRemoved);
onSuccess.run();
}
@Override
- protected void onException(Exception e) throws RuntimeException {
+ protected void onException(final Exception e) throws RuntimeException {
super.onException(e);
- Log.e("LOGOUT_SERVICE", "Logout failed.", e.getCause());
- }
- };
+ Ln.e(e.getCause(), "Logout failed.");
+ }
+ }
}
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/core/ApiError.java b/app/src/main/java/com/donnfelker/android/bootstrap/core/ApiError.java
new file mode 100644
index 0000000..317447a
--- /dev/null
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/core/ApiError.java
@@ -0,0 +1,24 @@
+package com.donnfelker.android.bootstrap.core;
+
+
+public class ApiError {
+ private int code;
+ private String error;
+
+
+ public int getCode() {
+ return code;
+ }
+
+ public void setCode(int code) {
+ this.code = code;
+ }
+
+ public String getError() {
+ return error;
+ }
+
+ public void setError(String error) {
+ this.error = error;
+ }
+}
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/core/AvatarLoader.java b/app/src/main/java/com/donnfelker/android/bootstrap/core/AvatarLoader.java
deleted file mode 100644
index 2d1a3b2..0000000
--- a/app/src/main/java/com/donnfelker/android/bootstrap/core/AvatarLoader.java
+++ /dev/null
@@ -1,408 +0,0 @@
-package com.donnfelker.android.bootstrap.core;
-
-import static android.graphics.Bitmap.CompressFormat.PNG;
-import static android.graphics.Bitmap.Config.ARGB_8888;
-import static android.view.View.VISIBLE;
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.text.TextUtils;
-import android.util.Log;
-import android.widget.ImageView;
-
-import com.actionbarsherlock.app.ActionBar;
-import com.donnfelker.android.bootstrap.R;
-import com.github.kevinsawicki.http.HttpRequest;
-import com.google.inject.Inject;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-import java.util.concurrent.atomic.AtomicReference;
-
-import roboguice.util.RoboAsyncTask;
-
-/**
- * Avatar utilities
- */
-public class AvatarLoader {
-
- private static final String TAG = "AvatarLoader";
-
- private static final float CORNER_RADIUS_IN_DIP = 3;
-
- private static final int CACHE_SIZE = 75;
-
- private static abstract class FetchAvatarTask extends
- RoboAsyncTask {
-
- private static final Executor EXECUTOR = Executors
- .newFixedThreadPool(1);
-
- private FetchAvatarTask(Context context) {
- super(context, EXECUTOR);
- }
-
- @Override
- protected void onException(Exception e) throws RuntimeException {
- Log.d(TAG, "Avatar load failed", e);
- }
- }
-
- private final float cornerRadius;
-
- private final Map loaded = new LinkedHashMap(
- CACHE_SIZE, 1.0F) {
-
- private static final long serialVersionUID = -4191624209581976720L;
-
- @Override
- protected boolean removeEldestEntry(
- Map.Entry eldest) {
- return size() >= CACHE_SIZE;
- }
- };
-
- private final Context context;
-
- private final File avatarDir;
-
- private final Drawable loadingAvatar;
-
- private final BitmapFactory.Options options;
-
- /**
- * Create avatar helper
- *
- * @param context
- */
- @Inject
- public AvatarLoader(final Context context) {
- this.context = context;
-
- loadingAvatar = context.getResources().getDrawable(R.drawable.gravatar_icon);
-
- avatarDir = new File(context.getCacheDir(), "avatars/" + context.getPackageName());
- if (!avatarDir.isDirectory())
- avatarDir.mkdirs();
-
- float density = context.getResources().getDisplayMetrics().density;
- cornerRadius = CORNER_RADIUS_IN_DIP * density;
-
- options = new BitmapFactory.Options();
- options.inDither = false;
- options.inPreferredConfig = ARGB_8888;
- }
-
- /**
- * Get image for user
- *
- * @param user
- * @return image
- */
- protected BitmapDrawable getImage(final User user) {
- File avatarFile = new File(avatarDir, user.getObjectId());
-
- if (!avatarFile.exists() || avatarFile.length() == 0)
- return null;
-
- Bitmap bitmap = decode(avatarFile);
- if (bitmap != null)
- return new BitmapDrawable(context.getResources(), bitmap);
- else {
- avatarFile.delete();
- return null;
- }
- }
-
-// /**
-// * Get image for user
-// *
-// * @param user
-// * @return image
-// */
-// protected BitmapDrawable getImage(final CommitUser user) {
-// File avatarFile = new File(avatarDir, user.getEmail());
-//
-// if (!avatarFile.exists() || avatarFile.length() == 0)
-// return null;
-//
-// Bitmap bitmap = decode(avatarFile);
-// if (bitmap != null)
-// return new BitmapDrawable(context.getResources(), bitmap);
-// else {
-// avatarFile.delete();
-// return null;
-// }
-// }
-
- /**
- * Decode file to bitmap
- *
- * @param file
- * @return bitmap
- */
- protected Bitmap decode(final File file) {
- return BitmapFactory.decodeFile(file.getAbsolutePath(), options);
- }
-
- /**
- * Fetch avatar from URL
- *
- * @param url
- * @param userId
- * @return bitmap
- */
- protected BitmapDrawable fetchAvatar(final String url, final String userId) {
- File rawAvatar = new File(avatarDir, userId + "-raw");
- HttpRequest request = HttpRequest.get(url);
- if (request.ok())
- request.receive(rawAvatar);
-
- if (!rawAvatar.exists() || rawAvatar.length() == 0)
- return null;
-
- Bitmap bitmap = decode(rawAvatar);
- if (bitmap == null) {
- rawAvatar.delete();
- return null;
- }
-
- bitmap = ImageUtils.roundCorners(bitmap, cornerRadius);
- if (bitmap == null) {
- rawAvatar.delete();
- return null;
- }
-
- File roundedAvatar = new File(avatarDir, userId.toString());
- FileOutputStream output = null;
- try {
- output = new FileOutputStream(roundedAvatar);
- if (bitmap.compress(PNG, 100, output))
- return new BitmapDrawable(context.getResources(), bitmap);
- else
- return null;
- } catch (IOException e) {
- Log.d(TAG, "Exception writing rounded avatar", e);
- return null;
- } finally {
- if (output != null)
- try {
- output.close();
- } catch (IOException e) {
- // Ignored
- }
- rawAvatar.delete();
- }
- }
-
- /**
- * Sets the logo on the {@link com.actionbarsherlock.app.ActionBar} to the user's avatar.
- *
- * @param actionBar
- * @param user
- * @return this helper
- */
- public AvatarLoader bind(final ActionBar actionBar, final User user) {
- return bind(actionBar, new AtomicReference(user));
- }
-
- /**
- * Sets the logo on the {@link ActionBar} to the user's avatar.
- *
- * @param actionBar
- * @param userReference
- * @return this helper
- */
- public AvatarLoader bind(final ActionBar actionBar,
- final AtomicReference userReference) {
- if (userReference == null)
- return this;
-
- final User user = userReference.get();
- if (user == null)
- return this;
-
- final String avatarUrl = user.getAvatarUrl();
- if (TextUtils.isEmpty(avatarUrl))
- return this;
-
- final String userId = user.getObjectId();
-
- BitmapDrawable loadedImage = loaded.get(userId);
- if (loadedImage != null) {
- actionBar.setLogo(loadedImage);
- return this;
- }
-
- new FetchAvatarTask(context) {
-
- @Override
- public BitmapDrawable call() throws Exception {
- final BitmapDrawable image = getImage(user);
- if (image != null)
- return image;
- else
- return fetchAvatar(avatarUrl, userId.toString());
- }
-
- @Override
- protected void onSuccess(BitmapDrawable image) throws Exception {
- final User current = userReference.get();
- if (current != null && userId.equals(current.getObjectId()))
- actionBar.setLogo(image);
- }
- }.execute();
-
- return this;
- }
-
- private AvatarLoader setImage(final Drawable image, final ImageView view) {
- return setImage(image, view, null);
- }
-
- private AvatarLoader setImage(final Drawable image, final ImageView view,
- Object tag) {
- view.setImageDrawable(image);
- view.setTag(R.id.iv_avatar, tag);
- view.setVisibility(VISIBLE);
- return this;
- }
-
- private String getAvatarUrl(String id) {
- if (!TextUtils.isEmpty(id))
- return "https://secure.gravatar.com/avatar/" + id + "?d=404";
- else
- return null;
- }
-
- private String getAvatarUrl(User user) {
- String avatarUrl = user.getAvatarUrl();
- if (TextUtils.isEmpty(avatarUrl)) {
- String gravatarId = user.getGravatarId();
- if (TextUtils.isEmpty(gravatarId))
- gravatarId = GravatarUtils.getHash(user.getUsername());
- avatarUrl = getAvatarUrl(gravatarId);
- }
- return avatarUrl;
- }
-
-// private String getAvatarUrl(CommitUser user) {
-// return getAvatarUrl(GravatarUtils.getHash(user.getEmail()));
-// }
-
- /**
- * Bind view to image at URL
- *
- * @param view
- * @param user
- * @return this helper
- */
- public AvatarLoader bind(final ImageView view, final User user) {
- if (user == null)
- return setImage(loadingAvatar, view);
-
- String avatarUrl = getAvatarUrl(user);
-
- if (TextUtils.isEmpty(avatarUrl))
- return setImage(loadingAvatar, view);
-
- final String userId = user.getObjectId();
-
- BitmapDrawable loadedImage = loaded.get(userId);
- if (loadedImage != null)
- return setImage(loadedImage, view);
-
- setImage(loadingAvatar, view, userId);
-
- final String loadUrl = avatarUrl;
- new FetchAvatarTask(context) {
-
- @Override
- public BitmapDrawable call() throws Exception {
- if (!userId.equals(view.getTag(R.id.iv_avatar)))
- return null;
-
- final BitmapDrawable image = getImage(user);
- if (image != null)
- return image;
- else
- return fetchAvatar(loadUrl, userId.toString());
- }
-
- @Override
- protected void onSuccess(final BitmapDrawable image)
- throws Exception {
- if (image == null)
- return;
- loaded.put(userId, image);
- if (userId.equals(view.getTag(R.id.iv_avatar)))
- setImage(image, view);
- }
-
- }.execute();
-
- return this;
- }
-
-// /**
-// * Bind view to image at URL
-// *
-// * @param view
-// * @param user
-// * @return this helper
-// */
-// public AvatarLoader bind(final ImageView view, final CommitUser user) {
-// if (user == null)
-// return setImage(loadingAvatar, view);
-//
-// String avatarUrl = getAvatarUrl(user);
-//
-// if (TextUtils.isEmpty(avatarUrl))
-// return setImage(loadingAvatar, view);
-//
-// final String userId = user.getEmail();
-//
-// BitmapDrawable loadedImage = loaded.get(userId);
-// if (loadedImage != null)
-// return setImage(loadedImage, view);
-//
-// setImage(loadingAvatar, view, userId);
-//
-// final String loadUrl = avatarUrl;
-// new FetchAvatarTask(context) {
-//
-// @Override
-// public BitmapDrawable call() throws Exception {
-// if (!userId.equals(view.getTag(id.iv_avatar)))
-// return null;
-//
-// final BitmapDrawable image = getImage(user);
-// if (image != null)
-// return image;
-// else
-// return fetchAvatar(loadUrl, userId);
-// }
-//
-// @Override
-// protected void onSuccess(final BitmapDrawable image)
-// throws Exception {
-// if (image == null)
-// return;
-// loaded.put(userId, image);
-// if (userId.equals(view.getTag(id.iv_avatar)))
-// setImage(image, view);
-// }
-//
-// }.execute();
-//
-// return this;
-// }
-}
-
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/core/BootstrapService.java b/app/src/main/java/com/donnfelker/android/bootstrap/core/BootstrapService.java
index 8ef2917..e592312 100644
--- a/app/src/main/java/com/donnfelker/android/bootstrap/core/BootstrapService.java
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/core/BootstrapService.java
@@ -1,232 +1,71 @@
package com.donnfelker.android.bootstrap.core;
-import static com.donnfelker.android.bootstrap.core.Constants.Http.HEADER_PARSE_APP_ID;
-import static com.donnfelker.android.bootstrap.core.Constants.Http.HEADER_PARSE_REST_API_KEY;
-import static com.donnfelker.android.bootstrap.core.Constants.Http.PARSE_APP_ID;
-import static com.donnfelker.android.bootstrap.core.Constants.Http.PARSE_REST_API_KEY;
-import static com.donnfelker.android.bootstrap.core.Constants.Http.URL_CHECKINS;
-import static com.donnfelker.android.bootstrap.core.Constants.Http.URL_NEWS;
-import static com.donnfelker.android.bootstrap.core.Constants.Http.URL_USERS;
-
-import com.github.kevinsawicki.http.HttpRequest;
-import com.github.kevinsawicki.http.HttpRequest.HttpRequestException;
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import com.google.gson.JsonParseException;
-
-import java.io.IOException;
-import java.io.Reader;
-import java.util.Collections;
import java.util.List;
+import retrofit.RestAdapter;
+
/**
* Bootstrap API service
*/
public class BootstrapService {
- private UserAgentProvider userAgentProvider;
-
- /**
- * GSON instance to use for all request with date format set up for proper parsing.
- */
- public static final Gson GSON = new GsonBuilder().setDateFormat("yyyy-MM-dd").create();
-
- /**
- * You can also configure GSON with different naming policies for your API. Maybe your api is Rails
- * api and all json values are lower case with an underscore, like this "first_name" instead of "firstName".
- * You can configure GSON as such below.
- *
- * public static final Gson GSON = new GsonBuilder().setDateFormat("yyyy-MM-dd").setFieldNamingPolicy(LOWER_CASE_WITH_UNDERSCORES).create();
- *
- */
-
-
- /**
- * Read and connect timeout in milliseconds
- */
- private static final int TIMEOUT = 30 * 1000;
-
-
- private static class UsersWrapper {
-
- private List results;
- }
-
- private static class NewsWrapper {
-
- private List results;
- }
-
- private static class CheckInWrapper {
-
- private List results;
-
- }
-
- private static class JsonException extends IOException {
-
- private static final long serialVersionUID = 3774706606129390273L;
-
- /**
- * Create exception from {@link JsonParseException}
- *
- * @param cause
- */
- public JsonException(JsonParseException cause) {
- super(cause.getMessage());
- initCause(cause);
- }
- }
-
-
- private final String apiKey;
- private final String username;
- private final String password;
+ private RestAdapter restAdapter;
/**
* Create bootstrap service
- *
- * @param username
- * @param password
+ * Default CTOR
*/
- public BootstrapService(final String username, final String password) {
- this.username = username;
- this.password = password;
- this.apiKey = null;
+ public BootstrapService() {
}
/**
* Create bootstrap service
*
- * @param userAgentProvider
- * @param apiKey
+ * @param restAdapter The RestAdapter that allows HTTP Communication.
*/
- public BootstrapService(final String apiKey, final UserAgentProvider userAgentProvider) {
- this.userAgentProvider = userAgentProvider;
- this.username = null;
- this.password = null;
- this.apiKey = apiKey;
+ public BootstrapService(RestAdapter restAdapter) {
+ this.restAdapter = restAdapter;
}
- /**
- * Execute request
- *
- * @param request
- * @return request
- * @throws IOException
- */
- protected HttpRequest execute(HttpRequest request) throws IOException {
- if (!configure(request).ok())
- throw new IOException("Unexpected response code: " + request.code());
- return request;
- }
-
- private HttpRequest configure(final HttpRequest request) {
- request.connectTimeout(TIMEOUT).readTimeout(TIMEOUT);
- request.userAgent(userAgentProvider.get());
-
- if(isPostOrPut(request))
- request.contentType(Constants.Http.CONTENT_TYPE_JSON); // All PUT & POST requests to Parse.com api must be in JSON - https://www.parse.com/docs/rest#general-requests
-
- return addCredentialsTo(request);
+ private UserService getUserService() {
+ return getRestAdapter().create(UserService.class);
}
- private boolean isPostOrPut(HttpRequest request) {
- return request.getConnection().getRequestMethod().equals(HttpRequest.METHOD_POST)
- || request.getConnection().getRequestMethod().equals(HttpRequest.METHOD_PUT);
-
+ private NewsService getNewsService() {
+ return getRestAdapter().create(NewsService.class);
}
- private HttpRequest addCredentialsTo(HttpRequest request) {
-
- // Required params for
- request.header(HEADER_PARSE_REST_API_KEY, PARSE_REST_API_KEY );
- request.header(HEADER_PARSE_APP_ID, PARSE_APP_ID);
-
- /**
- * NOTE: This may be where you want to add a header for the api token that was saved when you
- * logged in. In the bootstrap sample this is where we are saving the session id as the token.
- * If you actually had received a token you'd take the "apiKey" (aka: token) and add it to the
- * header or form values before you make your requests.
- */
-
- /**
- * Add the user name and password to the request here if your service needs username or password for each
- * request. You can do this like this:
- * request.basic("myusername", "mypassword");
- */
-
- return request;
+ private CheckInService getCheckInService() {
+ return getRestAdapter().create(CheckInService.class);
}
- private V fromJson(HttpRequest request, Class target) throws IOException {
- Reader reader = request.bufferedReader();
- try {
- return GSON.fromJson(reader, target);
- } catch (JsonParseException e) {
- throw new JsonException(e);
- } finally {
- try {
- reader.close();
- } catch (IOException ignored) {
- // Ignored
- }
- }
+ private RestAdapter getRestAdapter() {
+ return restAdapter;
}
/**
- * Get all bootstrap Users that exist on Parse.com
- *
- * @return non-null but possibly empty list of bootstrap
- * @throws IOException
+ * Get all bootstrap News that exists on Parse.com
*/
- public List getUsers() throws IOException {
- try {
- HttpRequest request = execute(HttpRequest.get(URL_USERS));
- UsersWrapper response = fromJson(request, UsersWrapper.class);
- if (response != null && response.results != null)
- return response.results;
- return Collections.emptyList();
- } catch (HttpRequestException e) {
- throw e.getCause();
- }
+ public List getNews() {
+ return getNewsService().getNews().getResults();
}
/**
- * Get all bootstrap News that exists on Parse.com
- *
- * @return non-null but possibly empty list of bootstrap
- * @throws IOException
+ * Get all bootstrap Users that exist on Parse.com
*/
- public List getNews() throws IOException {
- try {
- HttpRequest request = execute(HttpRequest.get(URL_NEWS));
- NewsWrapper response = fromJson(request, NewsWrapper.class);
- if (response != null && response.results != null)
- return response.results;
- return Collections.emptyList();
- } catch (HttpRequestException e) {
- throw e.getCause();
- }
+ public List getUsers() {
+ return getUserService().getUsers().getResults();
}
/**
* Get all bootstrap Checkins that exists on Parse.com
- *
- * @return non-null but possibly empty list of bootstrap
- * @throws IOException
*/
- public List getCheckIns() throws IOException {
- try {
- HttpRequest request = execute(HttpRequest.get(URL_CHECKINS));
- CheckInWrapper response = fromJson(request, CheckInWrapper.class);
- if (response != null && response.results != null)
- return response.results;
- return Collections.emptyList();
- } catch (HttpRequestException e) {
- throw e.getCause();
- }
+ public List getCheckIns() {
+ return getCheckInService().getCheckIns().getResults();
}
-}
+ public User authenticate(String email, String password) {
+ return getUserService().authenticate(email, password);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/core/CheckIn.java b/app/src/main/java/com/donnfelker/android/bootstrap/core/CheckIn.java
index 4bee35e..fda0cd9 100644
--- a/app/src/main/java/com/donnfelker/android/bootstrap/core/CheckIn.java
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/core/CheckIn.java
@@ -6,12 +6,11 @@ public class CheckIn {
private String name;
private String objectId;
-
public Location getLocation() {
return location;
}
- public void setLocation(Location location) {
+ public void setLocation(final Location location) {
this.location = location;
}
@@ -19,7 +18,7 @@ public String getName() {
return name;
}
- public void setName(String name) {
+ public void setName(final String name) {
this.name = name;
}
@@ -27,7 +26,7 @@ public String getObjectId() {
return objectId;
}
- public void setObjectId(String objectId) {
+ public void setObjectId(final String objectId) {
this.objectId = objectId;
}
}
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/core/CheckInService.java b/app/src/main/java/com/donnfelker/android/bootstrap/core/CheckInService.java
new file mode 100644
index 0000000..a455c2c
--- /dev/null
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/core/CheckInService.java
@@ -0,0 +1,11 @@
+package com.donnfelker.android.bootstrap.core;
+
+import java.util.List;
+
+import retrofit.http.GET;
+
+public interface CheckInService {
+
+ @GET(Constants.Http.URL_CHECKINS_FRAG)
+ CheckInWrapper getCheckIns();
+}
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/core/CheckInWrapper.java b/app/src/main/java/com/donnfelker/android/bootstrap/core/CheckInWrapper.java
new file mode 100644
index 0000000..0478eae
--- /dev/null
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/core/CheckInWrapper.java
@@ -0,0 +1,11 @@
+package com.donnfelker.android.bootstrap.core;
+
+import java.util.List;
+
+public class CheckInWrapper {
+ private List results;
+
+ public List getResults() {
+ return results;
+ }
+}
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/core/Constants.java b/app/src/main/java/com/donnfelker/android/bootstrap/core/Constants.java
index 81954ce..67a8a57 100644
--- a/app/src/main/java/com/donnfelker/android/bootstrap/core/Constants.java
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/core/Constants.java
@@ -5,9 +5,10 @@
/**
* Bootstrap constants
*/
-public class Constants {
+public final class Constants {
+ private Constants() {}
- public static class Auth {
+ public static final class Auth {
private Auth() {}
/**
@@ -35,35 +36,48 @@ private Auth() {}
* All HTTP is done through a REST style API built for demonstration purposes on Parse.com
* Thanks to the nice people at Parse for creating such a nice system for us to use for bootstrap!
*/
- public static class Http {
+ public static final class Http {
private Http() {}
-
/**
* Base URL for all requests
*/
public static final String URL_BASE = "https://api.parse.com";
+
/**
* Authentication URL
*/
- public static final String URL_AUTH = URL_BASE + "/1/login";
+ public static final String URL_AUTH_FRAG = "/1/login";
+ public static final String URL_AUTH = URL_BASE + URL_AUTH_FRAG;
/**
* List Users URL
*/
- public static final String URL_USERS = URL_BASE + "/1/users";
+ public static final String URL_USERS_FRAG = "/1/users";
+ public static final String URL_USERS = URL_BASE + URL_USERS_FRAG;
+
/**
* List News URL
*/
- public static final String URL_NEWS = URL_BASE + "/1/classes/News";
+ public static final String URL_NEWS_FRAG = "/1/classes/News";
+ public static final String URL_NEWS = URL_BASE + URL_NEWS_FRAG;
+
/**
* List Checkin's URL
*/
- public static final String URL_CHECKINS = URL_BASE + "/1/classes/Locations";
+ public static final String URL_CHECKINS_FRAG = "/1/classes/Locations";
+ public static final String URL_CHECKINS = URL_BASE + URL_CHECKINS_FRAG;
+
+ /**
+ * PARAMS for auth
+ */
+ public static final String PARAM_USERNAME = "username";
+ public static final String PARAM_PASSWORD = "password";
+
public static final String PARSE_APP_ID = "zHb2bVia6kgilYRWWdmTiEJooYA17NnkBSUVsr4H";
public static final String PARSE_REST_API_KEY = "N2kCY1T3t3Jfhf9zpJ5MCURn3b25UpACILhnf5u9";
@@ -78,7 +92,7 @@ private Http() {}
}
- public static class Extra {
+ public static final class Extra {
private Extra() {}
public static final String NEWS_ITEM = "news_item";
@@ -87,7 +101,7 @@ private Extra() {}
}
- public static class Intent {
+ public static final class Intent {
private Intent() {}
/**
@@ -97,6 +111,13 @@ private Intent() {}
}
+ public static class Notification {
+ private Notification() {
+ }
+
+ public static final int TIMER_NOTIFICATION_ID = 1000; // Why 1000? Why not? :)
+ }
+
}
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/core/GravatarUtils.java b/app/src/main/java/com/donnfelker/android/bootstrap/core/GravatarUtils.java
index 5ebb517..0b2cd19 100644
--- a/app/src/main/java/com/donnfelker/android/bootstrap/core/GravatarUtils.java
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/core/GravatarUtils.java
@@ -1,8 +1,6 @@
package com.donnfelker.android.bootstrap.core;
-
-import static java.util.Locale.US;
import android.text.TextUtils;
import java.io.UnsupportedEncodingException;
@@ -11,6 +9,8 @@
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
+import static java.util.Locale.US;
+
/**
* Helper to get a gravatar hash for an email
*/
@@ -32,22 +32,23 @@ public class GravatarUtils {
public static final String CHARSET = "CP1252"; //$NON-NLS-1$
private static String digest(final String value) {
- byte[] digested;
+ final byte[] digested;
try {
digested = MessageDigest.getInstance(HASH_ALGORITHM).digest(
value.getBytes(CHARSET));
- } catch (NoSuchAlgorithmException e) {
+ } catch (final NoSuchAlgorithmException e) {
return null;
- } catch (UnsupportedEncodingException e) {
+ } catch (final UnsupportedEncodingException e) {
return null;
}
- String hashed = new BigInteger(1, digested).toString(16);
- int padding = HASH_LENGTH - hashed.length();
- if (padding == 0)
+ final String hashed = new BigInteger(1, digested).toString(16);
+ final int padding = HASH_LENGTH - hashed.length();
+ if (padding == 0) {
return hashed;
+ }
- char[] zeros = new char[padding];
+ final char[] zeros = new char[padding];
Arrays.fill(zeros, '0');
return new StringBuilder(HASH_LENGTH).append(zeros).append(hashed)
.toString();
@@ -59,10 +60,11 @@ private static String digest(final String value) {
* @param email
* @return hash
*/
- public static String getHash(String email) {
- if (TextUtils.isEmpty(email))
+ public static String getHash(final String email) {
+ if (TextUtils.isEmpty(email)) {
return null;
- email = email.trim().toLowerCase(US);
- return email.length() > 0 ? digest(email) : null;
+ }
+ final String tmpEmail = email.trim().toLowerCase(US);
+ return tmpEmail.length() > 0 ? digest(tmpEmail) : null;
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/core/ImageUtils.java b/app/src/main/java/com/donnfelker/android/bootstrap/core/ImageUtils.java
index 08f9543..e56cc6c 100644
--- a/app/src/main/java/com/donnfelker/android/bootstrap/core/ImageUtils.java
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/core/ImageUtils.java
@@ -1,10 +1,6 @@
package com.donnfelker.android.bootstrap.core;
-
-import static android.graphics.Bitmap.Config.ARGB_8888;
-import static android.graphics.Color.WHITE;
-import static android.graphics.PorterDuff.Mode.DST_IN;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
@@ -12,19 +8,29 @@
import android.graphics.Point;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
-import android.util.Log;
import android.widget.ImageView;
+import com.donnfelker.android.bootstrap.util.Ln;
+
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
+import static android.graphics.Bitmap.Config.ARGB_8888;
+import static android.graphics.Color.WHITE;
+import static android.graphics.PorterDuff.Mode.DST_IN;
+
/**
* Image utilities
*/
-public class ImageUtils {
+public final class ImageUtils {
- private static final String TAG = "ImageUtils";
+ /**
+ * This is a utility class.
+ */
+ private ImageUtils() {
+ //never called
+ }
/**
* Get a bitmap from the image path
@@ -43,7 +49,7 @@ public static Bitmap getBitmap(final String imagePath) {
* @param sampleSize
* @return bitmap or null if read fails
*/
- public static Bitmap getBitmap(final String imagePath, int sampleSize) {
+ public static Bitmap getBitmap(final String imagePath, final int sampleSize) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inDither = false;
options.inSampleSize = sampleSize;
@@ -54,14 +60,14 @@ public static Bitmap getBitmap(final String imagePath, int sampleSize) {
return BitmapFactory.decodeFileDescriptor(file.getFD(), null,
options);
} catch (IOException e) {
- Log.d(TAG, e.getMessage(), e);
+ Ln.d(e, "Could not get cached bitmap.");
return null;
} finally {
if (file != null)
try {
file.close();
} catch (IOException e) {
- Log.d(TAG, e.getMessage(), e);
+ Ln.d(e, "Could not get cached bitmap.");
}
}
}
@@ -81,16 +87,17 @@ public static Point getSize(final String imagePath) {
file = new RandomAccessFile(imagePath, "r");
BitmapFactory.decodeFileDescriptor(file.getFD(), null, options);
return new Point(options.outWidth, options.outHeight);
- } catch (IOException e) {
- Log.d(TAG, e.getMessage(), e);
+ } catch (final IOException e) {
+ Ln.d(e, "Could not get size.");
return null;
} finally {
- if (file != null)
+ if (file != null) {
try {
file.close();
- } catch (IOException e) {
- Log.d(TAG, e.getMessage(), e);
+ } catch (final IOException e) {
+ Ln.d(e, "Could not get size.");
}
+ }
}
}
@@ -102,8 +109,8 @@ public static Point getSize(final String imagePath) {
* @param height
* @return image
*/
- public static Bitmap getBitmap(final String imagePath, int width, int height) {
- Point size = getSize(imagePath);
+ public static Bitmap getBitmap(final String imagePath, final int width, final int height) {
+ final Point size = getSize(imagePath);
int currWidth = size.x;
int currHeight = size.y;
@@ -125,7 +132,7 @@ public static Bitmap getBitmap(final String imagePath, int width, int height) {
* @param height
* @return image
*/
- public static Bitmap getBitmap(final File image, int width, int height) {
+ public static Bitmap getBitmap(final File image, final int width, final int height) {
return getBitmap(image.getAbsolutePath(), width, height);
}
@@ -158,9 +165,10 @@ public static void setImage(final String imagePath, final ImageView view) {
* @param view
*/
public static void setImage(final File image, final ImageView view) {
- Bitmap bitmap = getBitmap(image);
- if (bitmap != null)
+ final Bitmap bitmap = getBitmap(image);
+ if (bitmap != null) {
view.setImageBitmap(bitmap);
+ }
}
/**
@@ -171,20 +179,20 @@ public static void setImage(final File image, final ImageView view) {
* @return rounded corner bitmap
*/
public static Bitmap roundCorners(final Bitmap source, final float radius) {
- int width = source.getWidth();
- int height = source.getHeight();
+ final int width = source.getWidth();
+ final int height = source.getHeight();
- Paint paint = new Paint();
+ final Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(WHITE);
- Bitmap clipped = Bitmap.createBitmap(width, height, ARGB_8888);
+ final Bitmap clipped = Bitmap.createBitmap(width, height, ARGB_8888);
Canvas canvas = new Canvas(clipped);
canvas.drawRoundRect(new RectF(0, 0, width, height), radius, radius,
paint);
paint.setXfermode(new PorterDuffXfermode(DST_IN));
- Bitmap rounded = Bitmap.createBitmap(width, height, ARGB_8888);
+ final Bitmap rounded = Bitmap.createBitmap(width, height, ARGB_8888);
canvas = new Canvas(rounded);
canvas.drawBitmap(source, 0, 0, null);
canvas.drawBitmap(clipped, 0, 0, paint);
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/core/IntentFactory.java b/app/src/main/java/com/donnfelker/android/bootstrap/core/IntentFactory.java
new file mode 100644
index 0000000..f7504b2
--- /dev/null
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/core/IntentFactory.java
@@ -0,0 +1,5 @@
+package com.donnfelker.android.bootstrap.core;
+
+public class IntentFactory {
+ //TODO implement an Activity and Fragment delegate pattern
+}
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/core/Location.java b/app/src/main/java/com/donnfelker/android/bootstrap/core/Location.java
index 3f511af..fe9f6e3 100644
--- a/app/src/main/java/com/donnfelker/android/bootstrap/core/Location.java
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/core/Location.java
@@ -9,7 +9,7 @@ public double getLatitude() {
return latitude;
}
- public void setLatitude(double latitude) {
+ public void setLatitude(final double latitude) {
this.latitude = latitude;
}
@@ -17,7 +17,7 @@ public double getLongitude() {
return longitude;
}
- public void setLongitude(double longitude) {
+ public void setLongitude(final double longitude) {
this.longitude = longitude;
}
}
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/core/News.java b/app/src/main/java/com/donnfelker/android/bootstrap/core/News.java
index 391b517..7036bc4 100644
--- a/app/src/main/java/com/donnfelker/android/bootstrap/core/News.java
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/core/News.java
@@ -14,7 +14,7 @@ public String getTitle() {
return title;
}
- public void setTitle(String title) {
+ public void setTitle(final String title) {
this.title = title;
}
@@ -22,7 +22,7 @@ public String getContent() {
return content;
}
- public void setContent(String content) {
+ public void setContent(final String content) {
this.content = content;
}
@@ -30,7 +30,7 @@ public String getObjectId() {
return objectId;
}
- public void setObjectId(String objectId) {
+ public void setObjectId(final String objectId) {
this.objectId = objectId;
}
}
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/core/NewsService.java b/app/src/main/java/com/donnfelker/android/bootstrap/core/NewsService.java
new file mode 100644
index 0000000..cee18c4
--- /dev/null
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/core/NewsService.java
@@ -0,0 +1,14 @@
+package com.donnfelker.android.bootstrap.core;
+
+import retrofit.http.GET;
+
+
+/**
+ * Interface for defining the news service to communicate with Parse.com
+ */
+public interface NewsService {
+
+ @GET(Constants.Http.URL_NEWS_FRAG)
+ NewsWrapper getNews();
+
+}
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/core/NewsWrapper.java b/app/src/main/java/com/donnfelker/android/bootstrap/core/NewsWrapper.java
new file mode 100644
index 0000000..4d85792
--- /dev/null
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/core/NewsWrapper.java
@@ -0,0 +1,12 @@
+package com.donnfelker.android.bootstrap.core;
+
+
+import java.util.List;
+
+public class NewsWrapper {
+ private List results;
+
+ public List getResults() {
+ return results;
+ }
+}
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/core/PauseTimerEvent.java b/app/src/main/java/com/donnfelker/android/bootstrap/core/PauseTimerEvent.java
new file mode 100644
index 0000000..bd98769
--- /dev/null
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/core/PauseTimerEvent.java
@@ -0,0 +1,7 @@
+package com.donnfelker.android.bootstrap.core;
+
+/**
+ * Marker class for Otto for a pause event for the timer.
+ */
+public class PauseTimerEvent {
+}
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/core/PostFromAnyThreadBus.java b/app/src/main/java/com/donnfelker/android/bootstrap/core/PostFromAnyThreadBus.java
new file mode 100644
index 0000000..e83a76e
--- /dev/null
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/core/PostFromAnyThreadBus.java
@@ -0,0 +1,57 @@
+package com.donnfelker.android.bootstrap.core;
+
+import android.os.Handler;
+import android.os.Looper;
+
+import com.donnfelker.android.bootstrap.util.Ln;
+import com.squareup.otto.Bus;
+import com.squareup.otto.ThreadEnforcer;
+
+/**
+ * This message bus allows you to post a message from any thread and it will get handled and then
+ * posted to the main thread for you.
+ */
+public class PostFromAnyThreadBus extends Bus
+{
+ public PostFromAnyThreadBus()
+ {
+ super(ThreadEnforcer.MAIN);
+ }
+
+ @Override
+ public void post(final Object event)
+ {
+ if (Looper.myLooper() != Looper.getMainLooper())
+ {
+ // We're not in the main loop, so we need to get into it.
+ (new Handler(Looper.getMainLooper())).post(new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ // We're now in the main loop, we can post now
+ PostFromAnyThreadBus.super.post(event);
+ }
+ });
+ }
+ else
+ {
+ super.post(event);
+ }
+ }
+
+ @Override
+ public void unregister(final Object object)
+ {
+ // Lots of edge cases with register/unregister that sometimes throw.
+ try
+ {
+ super.unregister(object);
+ }
+ catch (IllegalArgumentException e)
+ {
+ // TODO: use Crashlytics unhandled exception logging
+ Ln.e(e);
+ }
+ }
+}
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/core/RestAdapterRequestInterceptor.java b/app/src/main/java/com/donnfelker/android/bootstrap/core/RestAdapterRequestInterceptor.java
new file mode 100644
index 0000000..3097088
--- /dev/null
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/core/RestAdapterRequestInterceptor.java
@@ -0,0 +1,32 @@
+package com.donnfelker.android.bootstrap.core;
+
+
+import static com.donnfelker.android.bootstrap.core.Constants.Http.HEADER_PARSE_APP_ID;
+import static com.donnfelker.android.bootstrap.core.Constants.Http.HEADER_PARSE_REST_API_KEY;
+import static com.donnfelker.android.bootstrap.core.Constants.Http.PARSE_APP_ID;
+import static com.donnfelker.android.bootstrap.core.Constants.Http.PARSE_REST_API_KEY;
+
+import retrofit.RequestInterceptor;
+
+public class RestAdapterRequestInterceptor implements RequestInterceptor {
+
+ private UserAgentProvider userAgentProvider;
+
+ public RestAdapterRequestInterceptor(UserAgentProvider userAgentProvider) {
+ this.userAgentProvider = userAgentProvider;
+ }
+
+ @Override
+ public void intercept(RequestFacade request) {
+
+ // Add header to set content type of JSON
+ request.addHeader("Content-Type", "application/json");
+
+ // Add auth info for PARSE, normally this is where you'd add your auth info for this request (if needed).
+ request.addHeader(HEADER_PARSE_REST_API_KEY, PARSE_REST_API_KEY);
+ request.addHeader(HEADER_PARSE_APP_ID, PARSE_APP_ID);
+
+ // Add the user agent to the request.
+ request.addHeader("User-Agent", userAgentProvider.get());
+ }
+}
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/core/RestErrorHandler.java b/app/src/main/java/com/donnfelker/android/bootstrap/core/RestErrorHandler.java
new file mode 100644
index 0000000..6cc812e
--- /dev/null
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/core/RestErrorHandler.java
@@ -0,0 +1,75 @@
+package com.donnfelker.android.bootstrap.core;
+
+import com.donnfelker.android.bootstrap.events.NetworkErrorEvent;
+import com.donnfelker.android.bootstrap.events.RestAdapterErrorEvent;
+import com.donnfelker.android.bootstrap.events.UnAuthorizedErrorEvent;
+import com.squareup.otto.Bus;
+
+import retrofit.ErrorHandler;
+import retrofit.RetrofitError;
+
+public class RestErrorHandler implements ErrorHandler {
+
+ public static final int HTTP_NOT_FOUND = 404;
+ public static final int INVALID_LOGIN_PARAMETERS = 101;
+
+ private Bus bus;
+
+ public RestErrorHandler(Bus bus) {
+ this.bus = bus;
+ }
+
+ @Override
+ public Throwable handleError(RetrofitError cause) {
+ if(cause != null) {
+ if (cause.isNetworkError()) {
+ bus.post(new NetworkErrorEvent(cause));
+ } else if(isUnAuthorized(cause)) {
+ bus.post(new UnAuthorizedErrorEvent(cause));
+ } else {
+ bus.post(new RestAdapterErrorEvent(cause));
+ }
+ }
+
+ // Example of how you'd check for a unauthorized result
+ // if (cause != null && cause.getStatus() == 401) {
+ // return new UnauthorizedException(cause);
+ // }
+
+ // You could also put some generic error handling in here so you can start
+ // getting analytics on error rates/etc. Perhaps ship your logs off to
+ // Splunk, Loggly, etc
+
+ return cause;
+ }
+
+ /**
+ * If a user passes an incorrect username/password combo in we could
+ * get a unauthorized error back from the API. On parse.com this means
+ * we get back a HTTP 404 with an error as JSON in the body as such:
+ *
+ * {
+ * code: 101,
+ * error: "invalid login parameters"
+ * }
+ *
+ * }
+ *
+ * Therefore we need to check for the 101 and the 404.
+ *
+ * @param cause The initial error.
+ * @return
+ */
+ private boolean isUnAuthorized(RetrofitError cause) {
+ boolean authFailed = false;
+
+ if(cause.getResponse().getStatus() == HTTP_NOT_FOUND) {
+ final ApiError err = (ApiError) cause.getBodyAs(ApiError.class);
+ if(err != null && err.getCode() == INVALID_LOGIN_PARAMETERS) {
+ authFailed = true;
+ }
+ }
+
+ return authFailed;
+ }
+}
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/core/ResumeTimerEvent.java b/app/src/main/java/com/donnfelker/android/bootstrap/core/ResumeTimerEvent.java
new file mode 100644
index 0000000..c767c71
--- /dev/null
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/core/ResumeTimerEvent.java
@@ -0,0 +1,7 @@
+package com.donnfelker.android.bootstrap.core;
+
+/**
+ * Marker class for resuming a timer through Otto
+ */
+public class ResumeTimerEvent {
+}
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/core/StopTimerEvent.java b/app/src/main/java/com/donnfelker/android/bootstrap/core/StopTimerEvent.java
new file mode 100644
index 0000000..672e2c4
--- /dev/null
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/core/StopTimerEvent.java
@@ -0,0 +1,7 @@
+package com.donnfelker.android.bootstrap.core;
+
+/**
+ * Marker class for the stop timer event in Otto.
+ */
+public class StopTimerEvent {
+}
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/core/TimerPausedEvent.java b/app/src/main/java/com/donnfelker/android/bootstrap/core/TimerPausedEvent.java
new file mode 100644
index 0000000..a419c17
--- /dev/null
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/core/TimerPausedEvent.java
@@ -0,0 +1,14 @@
+package com.donnfelker.android.bootstrap.core;
+
+public class TimerPausedEvent {
+
+ private final boolean timerIsPaused;
+
+ public TimerPausedEvent(boolean timerIsPaused) {
+ this.timerIsPaused = timerIsPaused;
+ }
+
+ public boolean isTimerIsPaused() {
+ return timerIsPaused;
+ }
+}
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/core/TimerService.java b/app/src/main/java/com/donnfelker/android/bootstrap/core/TimerService.java
new file mode 100644
index 0000000..7df94a2
--- /dev/null
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/core/TimerService.java
@@ -0,0 +1,212 @@
+package com.donnfelker.android.bootstrap.core;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.SystemClock;
+import android.support.v4.app.NotificationCompat;
+
+import com.donnfelker.android.bootstrap.Injector;
+import com.donnfelker.android.bootstrap.R;
+import com.donnfelker.android.bootstrap.ui.BootstrapTimerActivity;
+import com.donnfelker.android.bootstrap.util.Ln;
+import com.squareup.otto.Bus;
+import com.squareup.otto.Produce;
+import com.squareup.otto.Subscribe;
+
+import javax.inject.Inject;
+
+import static com.donnfelker.android.bootstrap.core.Constants.Notification.TIMER_NOTIFICATION_ID;
+
+public class TimerService extends Service {
+
+ @Inject protected Bus eventBus;
+ @Inject NotificationManager notificationManager;
+
+ private boolean timerRunning = false;
+ private boolean timerStarted;
+ private long base;
+ private long currentRunningTimeInMillis;
+ private long pausedBaseTime;
+ private boolean isPaused;
+
+ public static final int TICK_WHAT = 2;
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ Injector.inject(this);
+
+ // Register the bus so we can send notifications.
+ eventBus.register(this);
+
+ }
+
+ @Override
+ public void onDestroy() {
+
+ // Unregister bus, since its not longer needed as the service is shutting down
+ eventBus.unregister(this);
+
+ notificationManager.cancel(TIMER_NOTIFICATION_ID);
+
+ Ln.d("Service has been destroyed");
+
+ super.onDestroy();
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+
+ if (!timerStarted) {
+
+ timerStarted = true;
+
+ startTimer();
+
+ // Run as foreground service: http://stackoverflow.com/a/3856940/5210
+ // Another example: https://github.com/commonsguy/cw-android/blob/master/Notifications/FakePlayer/src/com/commonsware/android/fakeplayerfg/PlayerService.java
+ startForeground(TIMER_NOTIFICATION_ID, getNotification(getString(R.string.timer_running)));
+ }
+
+ return START_NOT_STICKY;
+ }
+
+ @Produce
+ public TimerTickEvent produceTickEvent() {
+ return new TimerTickEvent(currentRunningTimeInMillis);
+ }
+
+ @Produce
+ public TimerPausedEvent produceTimerIsPausedEvent() {
+ return new TimerPausedEvent(isPaused);
+ }
+
+ @Subscribe
+ public void onStopEvent(StopTimerEvent stopEvent) {
+
+ timerHandler.removeMessages(TICK_WHAT);
+ stopSelf();
+ }
+
+ @Subscribe
+ public void onPauseEvent(PauseTimerEvent pauseEvent) {
+ pauseTimer();
+ }
+
+ /**
+ * Pauses the active running timer and updates the notification in the status bar.
+ */
+ private void pauseTimer() {
+
+ updateNotification(getString(R.string.timer_is_paused));
+
+ timerHandler.removeMessages(TICK_WHAT);
+ pausedBaseTime = SystemClock.elapsedRealtime() - base;
+ timerRunning = false;
+ isPaused = true;
+
+ produceTimerIsPausedEvent();
+ }
+
+ @Subscribe
+ public void onResumeTimerEvent(ResumeTimerEvent resumeTimerEvent) {
+ startTimer();
+ }
+
+ private void startTimer() {
+ startChronoTimer();
+ notifyTimerRunning();
+ }
+
+ private void startChronoTimer() {
+ base = SystemClock.elapsedRealtime();
+
+ // If coming from a paused state, then find our true base.
+ if (pausedBaseTime > 0)
+ base = base - pausedBaseTime;
+
+ isPaused = false;
+
+ updateRunning();
+ }
+
+ /**
+ * Starts the generic timer.
+ */
+ private void updateRunning() {
+ if (timerStarted != timerRunning) {
+ if (timerStarted) {
+ dispatchTimerUpdate(SystemClock.elapsedRealtime());
+ timerHandler.sendMessageDelayed(Message.obtain(timerHandler, TICK_WHAT), 1000);
+ } else {
+ timerHandler.removeMessages(TICK_WHAT);
+ }
+ timerRunning = timerStarted;
+ }
+ }
+
+ private Handler timerHandler = new Handler() {
+ public void handleMessage(Message m) {
+ if (timerRunning) {
+ dispatchTimerUpdate(SystemClock.elapsedRealtime());
+ sendMessageDelayed(Message.obtain(this, TICK_WHAT), 1000);
+ }
+ }
+ };
+
+ private void dispatchTimerUpdate(long now) {
+
+ currentRunningTimeInMillis = now - base;
+ Ln.d("Elapsed Seconds: " + currentRunningTimeInMillis / 1000);
+
+ eventBus.post(produceTickEvent());
+
+ }
+
+
+ private void notifyTimerRunning() {
+ updateNotification(getString(R.string.timer_running));
+ produceTimerIsPausedEvent();
+ }
+
+
+ private void updateNotification(String message) {
+ notificationManager.notify(TIMER_NOTIFICATION_ID, getNotification(message));
+
+ }
+
+ /**
+ * Creates a notification to show in the notification bar
+ *
+ * @param message the message to display in the notification bar
+ * @return a new {@link Notification}
+ */
+ private Notification getNotification(String message) {
+ final Intent i = new Intent(this, BootstrapTimerActivity.class);
+
+ PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, i, 0);
+
+ return new NotificationCompat.Builder(this)
+ .setContentTitle(getString(R.string.app_name))
+ .setSmallIcon(R.drawable.ic_stat_ab_notification)
+ .setContentText(message)
+ .setAutoCancel(false)
+ .setOnlyAlertOnce(true)
+ .setOngoing(true)
+ .setWhen(System.currentTimeMillis())
+ .setContentIntent(pendingIntent)
+ .getNotification();
+ }
+}
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/core/TimerTickEvent.java b/app/src/main/java/com/donnfelker/android/bootstrap/core/TimerTickEvent.java
new file mode 100644
index 0000000..6b03e37
--- /dev/null
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/core/TimerTickEvent.java
@@ -0,0 +1,35 @@
+package com.donnfelker.android.bootstrap.core;
+
+
+/**
+ * Event used to pass tick events around through the message bus.
+ * This is mainly used in the {@link BootstrapTimer} to show the updates on the timer
+ * as the background service runs the timer.
+ */
+public class TimerTickEvent {
+
+ private final long millis;
+
+ public TimerTickEvent(long millis) {
+ this.millis = millis;
+ }
+
+ public long getMillis() {
+ return millis;
+ }
+
+ public long getSeconds() {
+ return (millis / 1000);
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder("")
+ .append("Millis: ").append(getMillis())
+ .append(", ")
+ .append("Seconds: ").append(getSeconds())
+ .toString();
+ }
+
+}
+
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/core/User.java b/app/src/main/java/com/donnfelker/android/bootstrap/core/User.java
index 4dfcb44..44dd480 100644
--- a/app/src/main/java/com/donnfelker/android/bootstrap/core/User.java
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/core/User.java
@@ -1,10 +1,11 @@
package com.donnfelker.android.bootstrap.core;
+import android.text.TextUtils;
+
import java.io.Serializable;
public class User implements Serializable {
-
private static final long serialVersionUID = -7495897652017488896L;
protected String firstName;
@@ -21,7 +22,7 @@ public String getUsername() {
return username;
}
- public void setUsername(String username) {
+ public void setUsername(final String username) {
this.username = username;
}
@@ -29,7 +30,7 @@ public String getPhone() {
return phone;
}
- public void setPhone(String phone) {
+ public void setPhone(final String phone) {
this.phone = phone;
}
@@ -37,7 +38,7 @@ public String getObjectId() {
return objectId;
}
- public void setObjectId(String objectId) {
+ public void setObjectId(final String objectId) {
this.objectId = objectId;
}
@@ -53,7 +54,7 @@ public String getFirstName() {
return firstName;
}
- public void setFirstName(String firstName) {
+ public void setFirstName(final String firstName) {
this.firstName = firstName;
}
@@ -61,7 +62,7 @@ public String getLastName() {
return lastName;
}
- public void setLastName(String lastName) {
+ public void setLastName(final String lastName) {
this.lastName = lastName;
}
@@ -70,6 +71,19 @@ public String getGravatarId() {
}
public String getAvatarUrl() {
+ if (TextUtils.isEmpty(avatarUrl)) {
+ String gravatarId = getGravatarId();
+ if (TextUtils.isEmpty(gravatarId))
+ gravatarId = GravatarUtils.getHash(getUsername());
+ avatarUrl = getAvatarUrl(gravatarId);
+ }
return avatarUrl;
}
+
+ private String getAvatarUrl(String id) {
+ if (!TextUtils.isEmpty(id))
+ return "https://secure.gravatar.com/avatar/" + id + "?d=404";
+ else
+ return null;
+ }
}
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/core/UserAgentProvider.java b/app/src/main/java/com/donnfelker/android/bootstrap/core/UserAgentProvider.java
index f6b1fb2..5fefb13 100644
--- a/app/src/main/java/com/donnfelker/android/bootstrap/core/UserAgentProvider.java
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/core/UserAgentProvider.java
@@ -1,35 +1,57 @@
package com.donnfelker.android.bootstrap.core;
-import android.app.Application;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.os.Build;
import android.telephony.TelephonyManager;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
+import com.donnfelker.android.bootstrap.util.Ln;
+import com.donnfelker.android.bootstrap.util.Strings;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Locale;
-import roboguice.util.Ln;
-import roboguice.util.Strings;
+import javax.inject.Inject;
+import javax.inject.Provider;
+
+/**
+ * Class that builds a User-Agent that is set on all HTTP calls.
+ *
+ * The user agent will change depending on the version of Android that
+ * the user is running, the device their running and the version of the
+ * app that they're running. This will allow your remote API to perform
+ * User-Agent inspection to provide different logic routes or analytics
+ * based upon the User-Agent.
+ *
+ * Example of what is generated when running the Genymotion Nexus 4 Emulator:
+ *
+ * Android Bootstrap/1.0 (Android 4.2.2; Genymotion Vbox86p / Generic Galaxy Nexus - 4.2.2 - API 17 - 720x1280; )[preload=false;locale=en_US;clientidbase=]
+ *
+ * The value "preload" means that the app has been preloaded by the manufacturer.
+ * Instances of when this might happen is if you partner with a telecom company
+ * to ship your app with their new device.
+ *
+ * If clientidbase is available you "should" be getting the telecom that is operating
+ * the device. This is not reliable, but is still useful.
+ */
public class UserAgentProvider implements Provider {
- @Inject protected Application app;
+
+ private static final String APP_NAME = "Android Bootstrap";
+
+ @Inject protected ApplicationInfo appInfo;
@Inject protected PackageInfo info;
@Inject protected TelephonyManager telephonyManager;
+ @Inject protected ClassLoader classLoader;
protected String userAgent;
- private static final String APP_NAME = "Android Bootstrap";
-
@Override
public String get() {
- if( userAgent==null ) {
+ if (userAgent == null) {
synchronized (UserAgentProvider.class) {
- if( userAgent==null ) {
+ if (userAgent == null) {
userAgent = String.format("%s/%s (Android %s; %s %s / %s %s; %s)",
APP_NAME,
info.versionName,
@@ -38,26 +60,26 @@ public String get() {
Strings.capitalize(Build.DEVICE),
Strings.capitalize(Build.BRAND),
Strings.capitalize(Build.MODEL),
- Strings.capitalize( telephonyManager == null ? "not-found" : telephonyManager.getSimOperatorName())
+ Strings.capitalize(telephonyManager == null ? "not-found" : telephonyManager.getSimOperatorName())
);
final ArrayList params = new ArrayList();
- params.add( "preload=" + ((app.getApplicationInfo().flags& ApplicationInfo.FLAG_SYSTEM)==1) ); // Determine if this app was a preloaded app
- params.add( "locale=" + Locale.getDefault() );
+ params.add("preload=" + ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 1)); // Determine if this app was a preloaded app
+ params.add("locale=" + Locale.getDefault());
// http://stackoverflow.com/questions/2641111/where-is-android-os-systemproperties
- try{
- final Class SystemProperties = app.getClassLoader().loadClass("android.os.SystemProperties");
+ try {
+ final Class SystemProperties = classLoader.loadClass("android.os.SystemProperties");
final Method get = SystemProperties.getMethod("get", String.class);
- params.add( "clientidbase=" + get.invoke(SystemProperties, "ro.com.google.clientidbase"));
- }catch( Exception ignored ){
+ params.add("clientidbase=" + get.invoke(SystemProperties, "ro.com.google.clientidbase"));
+ } catch (Exception ignored) {
Ln.d(ignored);
}
- if( params.size()>0 )
- userAgent += "["+ Strings.join(";", params) +"]";
+ if (params.size() > 0)
+ userAgent += "[" + Strings.join(";", params) + "]";
}
}
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/core/UserService.java b/app/src/main/java/com/donnfelker/android/bootstrap/core/UserService.java
new file mode 100644
index 0000000..a0a040d
--- /dev/null
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/core/UserService.java
@@ -0,0 +1,28 @@
+package com.donnfelker.android.bootstrap.core;
+
+import java.util.List;
+
+import retrofit.http.GET;
+import retrofit.http.Query;
+
+/**
+ * User service for connecting the the REST API and
+ * getting the users.
+ */
+public interface UserService {
+
+ @GET(Constants.Http.URL_USERS_FRAG)
+ UsersWrapper getUsers();
+
+ /**
+ * The {@link retrofit.http.Query} values will be transform into query string paramters
+ * via Retrofit
+ *
+ * @param email The users email
+ * @param password The users password
+ * @return A login response.
+ */
+ @GET(Constants.Http.URL_AUTH_FRAG)
+ User authenticate(@Query(Constants.Http.PARAM_USERNAME) String email,
+ @Query(Constants.Http.PARAM_PASSWORD) String password);
+}
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/core/UsersWrapper.java b/app/src/main/java/com/donnfelker/android/bootstrap/core/UsersWrapper.java
new file mode 100644
index 0000000..7b898a5
--- /dev/null
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/core/UsersWrapper.java
@@ -0,0 +1,14 @@
+package com.donnfelker.android.bootstrap.core;
+
+import com.google.gson.annotations.SerializedName;
+
+import java.util.List;
+
+public class UsersWrapper {
+
+ private List results;
+
+ public List getResults() {
+ return results;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/events/NavItemSelectedEvent.java b/app/src/main/java/com/donnfelker/android/bootstrap/events/NavItemSelectedEvent.java
new file mode 100644
index 0000000..d0462c4
--- /dev/null
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/events/NavItemSelectedEvent.java
@@ -0,0 +1,17 @@
+package com.donnfelker.android.bootstrap.events;
+
+/**
+ * Pub/Sub event used to communicate between fragment and activity.
+ * Subscription occurs in the {@link com.donnfelker.android.bootstrap.ui.MainActivity}
+ */
+public class NavItemSelectedEvent {
+ private int itemPosition;
+
+ public NavItemSelectedEvent(int itemPosition) {
+ this.itemPosition = itemPosition;
+ }
+
+ public int getItemPosition() {
+ return itemPosition;
+ }
+}
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/events/NetworkErrorEvent.java b/app/src/main/java/com/donnfelker/android/bootstrap/events/NetworkErrorEvent.java
new file mode 100644
index 0000000..40fff4d
--- /dev/null
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/events/NetworkErrorEvent.java
@@ -0,0 +1,20 @@
+package com.donnfelker.android.bootstrap.events;
+
+import retrofit.RetrofitError;
+
+/**
+ * The event that is posted when a network error event occurs.
+ * TODO: Consume this event in the {@link com.donnfelker.android.bootstrap.ui.BootstrapActivity} and
+ * show a dialog that something went wrong.
+ */
+public class NetworkErrorEvent {
+ private RetrofitError cause;
+
+ public NetworkErrorEvent(RetrofitError cause) {
+ this.cause = cause;
+ }
+
+ public RetrofitError getCause() {
+ return cause;
+ }
+}
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/events/RestAdapterErrorEvent.java b/app/src/main/java/com/donnfelker/android/bootstrap/events/RestAdapterErrorEvent.java
new file mode 100644
index 0000000..795cdb2
--- /dev/null
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/events/RestAdapterErrorEvent.java
@@ -0,0 +1,18 @@
+package com.donnfelker.android.bootstrap.events;
+
+import retrofit.RetrofitError;
+
+/**
+ * Error that is posted when a non-network error event occurs in the {@link retrofit.RestAdapter}
+ */
+public class RestAdapterErrorEvent {
+ private RetrofitError cause;
+
+ public RestAdapterErrorEvent(RetrofitError cause) {
+ this.cause = cause;
+ }
+
+ public RetrofitError getCause() {
+ return cause;
+ }
+}
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/events/UnAuthorizedErrorEvent.java b/app/src/main/java/com/donnfelker/android/bootstrap/events/UnAuthorizedErrorEvent.java
new file mode 100644
index 0000000..9444b76
--- /dev/null
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/events/UnAuthorizedErrorEvent.java
@@ -0,0 +1,17 @@
+package com.donnfelker.android.bootstrap.events;
+
+import java.io.Serializable;
+
+import retrofit.RetrofitError;
+
+public class UnAuthorizedErrorEvent {
+ private Serializable cause;
+
+ public UnAuthorizedErrorEvent(Serializable cause) {
+ this.cause = cause;
+ }
+
+ public Serializable getCause() {
+ return cause;
+ }
+}
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/ui/AlternatingColorListAdapter.java b/app/src/main/java/com/donnfelker/android/bootstrap/ui/AlternatingColorListAdapter.java
index 3a94b5a..aab0835 100644
--- a/app/src/main/java/com/donnfelker/android/bootstrap/ui/AlternatingColorListAdapter.java
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/ui/AlternatingColorListAdapter.java
@@ -3,9 +3,9 @@
import android.view.LayoutInflater;
-import com.actionbarsherlock.R.color;
-import com.github.kevinsawicki.wishlist.SingleTypeAdapter;
+import com.donnfelker.android.bootstrap.R;
import com.donnfelker.android.bootstrap.R.drawable;
+import com.github.kevinsawicki.wishlist.SingleTypeAdapter;
import java.util.List;
@@ -28,8 +28,8 @@ public abstract class AlternatingColorListAdapter extends
* @param inflater
* @param items
*/
- public AlternatingColorListAdapter(final int layoutId,
- final LayoutInflater inflater, final List items) {
+ public AlternatingColorListAdapter(final int layoutId, final LayoutInflater inflater,
+ final List items) {
this(layoutId, inflater, items, true);
}
@@ -41,16 +41,16 @@ public AlternatingColorListAdapter(final int layoutId,
* @param items
* @param selectable
*/
- public AlternatingColorListAdapter(final int layoutId,
- LayoutInflater inflater, final List items, boolean selectable) {
+ public AlternatingColorListAdapter(final int layoutId, final LayoutInflater inflater,
+ final List items, final boolean selectable) {
super(inflater, layoutId);
if (selectable) {
primaryResource = drawable.table_background_selector;
secondaryResource = drawable.table_background_alternate_selector;
} else {
- primaryResource = color.pager_background;
- secondaryResource = color.pager_background_alternate;
+ primaryResource = R.color.pager_background;
+ secondaryResource = R.color.pager_background_alternate;
}
setItems(items);
@@ -59,8 +59,8 @@ public AlternatingColorListAdapter(final int layoutId,
@Override
protected void update(final int position, final V item) {
if (position % 2 != 0)
- view.setBackgroundResource(primaryResource);
+ updater.view.setBackgroundResource(primaryResource);
else
- view.setBackgroundResource(secondaryResource);
+ updater.view.setBackgroundResource(secondaryResource);
}
}
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/ui/AsyncLoader.java b/app/src/main/java/com/donnfelker/android/bootstrap/ui/AsyncLoader.java
index 9371d00..46db305 100644
--- a/app/src/main/java/com/donnfelker/android/bootstrap/ui/AsyncLoader.java
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/ui/AsyncLoader.java
@@ -11,11 +11,11 @@
*
* Based on CursorLoader.java in the Fragment compatibility package
*
- * @param
- * data type
+ * @param data type
* @author Alexander Blom (me@alexanderblom.se)
*/
public abstract class AsyncLoader extends AsyncTaskLoader {
+
private D data;
/**
@@ -23,15 +23,16 @@ public abstract class AsyncLoader extends AsyncTaskLoader {
*
* @param context
*/
- public AsyncLoader(Context context) {
+ public AsyncLoader(final Context context) {
super(context);
}
@Override
- public void deliverResult(D data) {
- if (isReset())
+ public void deliverResult(final D data) {
+ if (isReset()) {
// An async query came in while the loader is stopped
return;
+ }
this.data = data;
@@ -40,11 +41,13 @@ public void deliverResult(D data) {
@Override
protected void onStartLoading() {
- if (data != null)
+ if (data != null) {
deliverResult(data);
+ }
- if (takeContentChanged() || data == null)
+ if (takeContentChanged() || data == null) {
forceLoad();
+ }
}
@Override
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/ui/BarGraphDrawable.java b/app/src/main/java/com/donnfelker/android/bootstrap/ui/BarGraphDrawable.java
index 8ff4502..01a9401 100644
--- a/app/src/main/java/com/donnfelker/android/bootstrap/ui/BarGraphDrawable.java
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/ui/BarGraphDrawable.java
@@ -32,9 +32,11 @@ public BarGraphDrawable(final long[][] data, final int[][] colors) {
super(android.R.color.transparent);
this.data = data;
this.colors = colors;
- for (int i = 0; i < data.length; i++)
- for (int j = 0; j < data[i].length; j++)
+ for (int i = 0; i < data.length; i++) {
+ for (int j = 0; j < data[i].length; j++) {
max = Math.max(max, data[i][j]);
+ }
+ }
}
@Override
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/ui/BootstrapActivity.java b/app/src/main/java/com/donnfelker/android/bootstrap/ui/BootstrapActivity.java
index 52be6dc..9d5665f 100644
--- a/app/src/main/java/com/donnfelker/android/bootstrap/ui/BootstrapActivity.java
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/ui/BootstrapActivity.java
@@ -1,23 +1,45 @@
package com.donnfelker.android.bootstrap.ui;
-import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
-import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
import android.content.Intent;
+import android.os.Bundle;
+import android.support.v7.app.ActionBarActivity;
+import android.view.MenuItem;
-import com.actionbarsherlock.view.MenuItem;
-import com.github.rtyley.android.sherlock.roboguice.activity.RoboSherlockActivity;
+import com.donnfelker.android.bootstrap.Injector;
+
+import butterknife.Views;
+
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
+import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
/**
* Base activity for a Bootstrap activity which does not use fragments.
*/
-public abstract class BootstrapActivity extends RoboSherlockActivity {
+public abstract class BootstrapActivity extends ActionBarActivity {
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Injector.inject(this);
+ }
+
+ @Override
+ public void setContentView(final int layoutResId) {
+ super.setContentView(layoutResId);
+
+ // Used to inject views with the Butterknife library
+ Views.inject(this);
+ }
@Override
- public boolean onOptionsItemSelected(MenuItem item) {
+ public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
- case android.R.id.home: // This is the home button in the top left corner of the screen.
- // Dont call finish! Because activity could have been started by an outside activity and the home button would not operated as expected!
- Intent homeIntent = new Intent(this, CarouselActivity.class);
+ // This is the home button in the top left corner of the screen.
+ case android.R.id.home:
+ // Don't call finish! Because activity could have been started by an
+ // outside activity and the home button would not operated as expected!
+ final Intent homeIntent = new Intent(this, MainActivity.class);
homeIntent.addFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP);
startActivity(homeIntent);
return true;
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/ui/BootstrapFragmentActivity.java b/app/src/main/java/com/donnfelker/android/bootstrap/ui/BootstrapFragmentActivity.java
new file mode 100644
index 0000000..d1e096a
--- /dev/null
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/ui/BootstrapFragmentActivity.java
@@ -0,0 +1,47 @@
+package com.donnfelker.android.bootstrap.ui;
+
+import android.os.Bundle;
+import android.support.v7.app.ActionBarActivity;
+
+import com.donnfelker.android.bootstrap.Injector;
+import com.squareup.otto.Bus;
+
+import javax.inject.Inject;
+
+import butterknife.InjectView;
+import butterknife.Views;
+
+/**
+ * Base class for all Bootstrap Activities that need fragments.
+ */
+public class BootstrapFragmentActivity extends ActionBarActivity {
+
+ @Inject
+ protected Bus eventBus;
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Injector.inject(this);
+ }
+
+ @Override
+ public void setContentView(final int layoutResId) {
+ super.setContentView(layoutResId);
+
+ Views.inject(this);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ eventBus.register(this);
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ eventBus.unregister(this);
+ }
+}
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/ui/BootstrapPagerAdapter.java b/app/src/main/java/com/donnfelker/android/bootstrap/ui/BootstrapPagerAdapter.java
index f896477..cf81fa1 100644
--- a/app/src/main/java/com/donnfelker/android/bootstrap/ui/BootstrapPagerAdapter.java
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/ui/BootstrapPagerAdapter.java
@@ -23,7 +23,7 @@ public class BootstrapPagerAdapter extends FragmentPagerAdapter {
* @param resources
* @param fragmentManager
*/
- public BootstrapPagerAdapter(Resources resources, FragmentManager fragmentManager) {
+ public BootstrapPagerAdapter(final Resources resources, final FragmentManager fragmentManager) {
super(fragmentManager);
this.resources = resources;
}
@@ -34,37 +34,39 @@ public int getCount() {
}
@Override
- public Fragment getItem(int position) {
- Bundle bundle = new Bundle();
+ public Fragment getItem(final int position) {
+ final Fragment result;
switch (position) {
- case 0:
- NewsListFragment newsFragment = new NewsListFragment();
- newsFragment.setArguments(bundle);
- return newsFragment;
- case 1:
- UserListFragment userListFragment = new UserListFragment();
- userListFragment.setArguments(bundle);
- return userListFragment;
- case 2:
- CheckInsListFragment checkInsFragment = new CheckInsListFragment();
- checkInsFragment.setArguments(bundle);
- return checkInsFragment;
- default:
- return null;
+ case 0:
+ result = new NewsListFragment();
+ break;
+ case 1:
+ result = new UserListFragment();
+ break;
+ case 2:
+ result = new CheckInsListFragment();
+ break;
+ default:
+ result = null;
+ break;
}
+ if (result != null) {
+ result.setArguments(new Bundle()); //TODO do we need this?
+ }
+ return result;
}
@Override
- public CharSequence getPageTitle(int position) {
+ public CharSequence getPageTitle(final int position) {
switch (position) {
- case 0:
- return resources.getString(R.string.page_news);
- case 1:
- return resources.getString(R.string.page_users);
- case 2:
- return resources.getString(R.string.page_checkins);
- default:
- return null;
+ case 0:
+ return resources.getString(R.string.page_news);
+ case 1:
+ return resources.getString(R.string.page_users);
+ case 2:
+ return resources.getString(R.string.page_checkins);
+ default:
+ return null;
}
}
}
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/ui/BootstrapTimerActivity.java b/app/src/main/java/com/donnfelker/android/bootstrap/ui/BootstrapTimerActivity.java
new file mode 100644
index 0000000..1520f86
--- /dev/null
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/ui/BootstrapTimerActivity.java
@@ -0,0 +1,237 @@
+package com.donnfelker.android.bootstrap.ui;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v4.app.NavUtils;
+import android.support.v4.app.TaskStackBuilder;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+import com.donnfelker.android.bootstrap.R;
+import com.donnfelker.android.bootstrap.core.PauseTimerEvent;
+import com.donnfelker.android.bootstrap.core.ResumeTimerEvent;
+import com.donnfelker.android.bootstrap.core.StopTimerEvent;
+import com.donnfelker.android.bootstrap.core.TimerPausedEvent;
+import com.donnfelker.android.bootstrap.core.TimerService;
+import com.donnfelker.android.bootstrap.core.TimerTickEvent;
+import com.squareup.otto.Bus;
+import com.squareup.otto.Subscribe;
+
+import javax.inject.Inject;
+
+import butterknife.InjectView;
+
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
+
+public class BootstrapTimerActivity extends BootstrapFragmentActivity implements View.OnClickListener {
+
+ @Inject Bus eventBus;
+
+ @InjectView(R.id.chronometer) protected TextView chronometer;
+ @InjectView(R.id.start) protected Button start;
+ @InjectView(R.id.stop) protected Button stop;
+ @InjectView(R.id.pause) protected Button pause;
+ @InjectView(R.id.resume) protected Button resume;
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.bootstrap_timer);
+
+ setTitle(R.string.title_timer);
+
+ getSupportActionBar().setHomeButtonEnabled(true);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+
+ start.setOnClickListener(this);
+ stop.setOnClickListener(this);
+ pause.setOnClickListener(this);
+ resume.setOnClickListener(this);
+
+ }
+
+ @Override
+ public void onClick(final View v) {
+ switch (v.getId()) {
+ case R.id.start:
+ startTimer();
+ break;
+ case R.id.stop:
+ produceStopEvent();
+ break;
+ case R.id.pause:
+ producePauseEvent();
+ break;
+ case R.id.resume:
+ produceResumeEvent();
+ break;
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ switch (item.getItemId()) {
+ // Source:
+ // http://developer.android.com/training/implementing-navigation/ancestral.html
+ // This is the home button in the top left corner of the screen.
+ case android.R.id.home:
+ final Intent upIntent = NavUtils.getParentActivityIntent(this);
+ // If parent is not properly defined in AndroidManifest.xml upIntent will be null
+ // TODO hanlde upIntent == null
+ if (NavUtils.shouldUpRecreateTask(this, upIntent)) {
+ // This activity is NOT part of this app's task, so create a new task
+ // when navigating up, with a synthesized back stack.
+ TaskStackBuilder.create(this)
+ // Add all of this activity's parents to the back stack
+ .addNextIntentWithParentStack(upIntent)
+ // Navigate up to the closest parent
+ .startActivities();
+ } else {
+ // This activity is part of this app's task, so simply
+ // navigate up to the logical parent activity.
+ NavUtils.navigateUpTo(this, upIntent);
+ }
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ /**
+ * Starts the timer service
+ */
+ private void startTimer() {
+ if (!isTimerServiceRunning()) {
+ final Intent i = new Intent(this, TimerService.class);
+ startService(i);
+
+ setButtonVisibility(GONE, VISIBLE, GONE, VISIBLE);
+ }
+ }
+
+ /**
+ * Posts a {@link StopTimerEvent} message to the {@link Bus}
+ */
+ private void produceStopEvent() {
+ eventBus.post(new StopTimerEvent());
+ }
+
+ /**
+ * Posts a {@link PauseTimerEvent} message to the {@link Bus}
+ */
+ private void producePauseEvent() {
+ eventBus.post(new PauseTimerEvent());
+ }
+
+ /**
+ * Posts a {@link ResumeTimerEvent} message to the {@link Bus}
+ */
+ private void produceResumeEvent() {
+ eventBus.post(new ResumeTimerEvent());
+ }
+
+ @Subscribe
+ public void onTimerPausedEvent(final TimerPausedEvent event) {
+ if (event.isTimerIsPaused()) {
+ setButtonVisibility(GONE, VISIBLE, VISIBLE, GONE);
+ } else if (isTimerServiceRunning()) {
+ setButtonVisibility(GONE, VISIBLE, GONE, VISIBLE);
+ }
+ }
+
+ /**
+ * Called by {@link Bus} when a tick event occurs.
+ *
+ * @param event The event
+ */
+ @Subscribe
+ public void onTickEvent(final TimerTickEvent event) {
+ setFormattedTime(event.getMillis());
+ }
+
+ /**
+ * Called by {@link Bus} when a tick event occurs.
+ *
+ * @param event The event
+ */
+ @Subscribe
+ public void onPauseEvent(final PauseTimerEvent event) {
+ setButtonVisibility(GONE, VISIBLE, VISIBLE, GONE);
+ }
+
+ /**
+ * Called by {@link Bus} when a tick event occurs.
+ *
+ * @param event The event
+ */
+ @Subscribe
+ public void onResumeEvent(final ResumeTimerEvent event) {
+ setButtonVisibility(GONE, VISIBLE, GONE, VISIBLE);
+ }
+
+ /**
+ * Called by {@link Bus} when a tick event occurs.
+ *
+ * @param event The event
+ */
+ @Subscribe
+ public void onStopEvent(final StopTimerEvent event) {
+ setButtonVisibility(VISIBLE, GONE, GONE, GONE);
+ setFormattedTime(0); // Since its stopped, zero out the timer.
+ }
+
+ /**
+ * Checks to see if the timer service is running or not.
+ *
+ * @return true if the service is running otherwise false.
+ */
+ private boolean isTimerServiceRunning() {
+ final ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
+ for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
+ if (TimerService.class.getName().equals(service.service.getClassName())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void setButtonVisibility(final int start, final int stop,
+ final int resume, final int pause) {
+ this.start.setVisibility(start);
+ this.stop.setVisibility(stop);
+ this.resume.setVisibility(resume);
+ this.pause.setVisibility(pause);
+ }
+
+ /**
+ * Sets the formatted time
+ *
+ * @param millis the elapsed time
+ */
+ private void setFormattedTime(long millis) {
+ final String formattedTime = formatTime(millis);
+ chronometer.setText(formattedTime);
+ }
+
+ /**
+ * Formats the time to look like "HH:MM:SS"
+ *
+ * @param millis The number of elapsed milliseconds
+ * @return A formatted time value
+ */
+ public static String formatTime(final long millis) {
+ //TODO does not support hour>=100 (4.1 days)
+ return String.format("%02d:%02d:%02d",
+ millis / (1000 * 60 * 60),
+ (millis / (1000 * 60)) % 60,
+ (millis / 1000) % 60
+ );
+ }
+
+}
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/ui/CarouselActivity.java b/app/src/main/java/com/donnfelker/android/bootstrap/ui/CarouselActivity.java
deleted file mode 100644
index a5b6745..0000000
--- a/app/src/main/java/com/donnfelker/android/bootstrap/ui/CarouselActivity.java
+++ /dev/null
@@ -1,43 +0,0 @@
-
-
-package com.donnfelker.android.bootstrap.ui;
-
-import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
-import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
-import android.content.Intent;
-import android.os.Bundle;
-import android.support.v4.view.ViewPager;
-
-import com.actionbarsherlock.view.MenuItem;
-import com.actionbarsherlock.view.Window;
-import com.donnfelker.android.bootstrap.R;
-import com.donnfelker.android.bootstrap.R.id;
-import com.github.rtyley.android.sherlock.roboguice.activity.RoboSherlockFragmentActivity;
-import com.viewpagerindicator.TitlePageIndicator;
-
-import roboguice.inject.InjectView;
-
-/**
- * Activity to view the carousel and view pager indicator with fragments.
- */
-public class CarouselActivity extends RoboSherlockFragmentActivity {
-
- @InjectView(id.tpi_header) private TitlePageIndicator indicator;
- @InjectView(id.vp_pages) private ViewPager pager;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
-
- requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
-
- super.onCreate(savedInstanceState);
- setContentView(R.layout.carousel_view);
-
- pager.setAdapter(new BootstrapPagerAdapter(getResources(), getSupportFragmentManager()));
-
- indicator.setViewPager(pager);
- pager.setCurrentItem(1);
- }
-
-
-}
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/ui/CarouselFragment.java b/app/src/main/java/com/donnfelker/android/bootstrap/ui/CarouselFragment.java
new file mode 100644
index 0000000..f093e07
--- /dev/null
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/ui/CarouselFragment.java
@@ -0,0 +1,43 @@
+package com.donnfelker.android.bootstrap.ui;
+
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.view.ViewPager;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.donnfelker.android.bootstrap.R;
+import com.viewpagerindicator.TitlePageIndicator;
+
+import butterknife.InjectView;
+import butterknife.Views;
+
+/**
+ * Fragment which houses the View pager.
+ */
+public class CarouselFragment extends Fragment {
+
+ @InjectView(R.id.tpi_header)
+ protected TitlePageIndicator indicator;
+
+ @InjectView(R.id.vp_pages)
+ protected ViewPager pager;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.fragment_carousel, container, false);
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ Views.inject(this, getView());
+
+ pager.setAdapter(new BootstrapPagerAdapter(getResources(), getChildFragmentManager()));
+ indicator.setViewPager(pager);
+ pager.setCurrentItem(1);
+
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/ui/CheckInsListAdapter.java b/app/src/main/java/com/donnfelker/android/bootstrap/ui/CheckInsListAdapter.java
index 6761c54..2c5d9b6 100644
--- a/app/src/main/java/com/donnfelker/android/bootstrap/ui/CheckInsListAdapter.java
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/ui/CheckInsListAdapter.java
@@ -1,15 +1,12 @@
package com.donnfelker.android.bootstrap.ui;
-import android.util.Log;
import android.view.LayoutInflater;
import com.donnfelker.android.bootstrap.R;
import com.donnfelker.android.bootstrap.core.CheckIn;
-import com.donnfelker.android.bootstrap.core.News;
import java.util.List;
-import roboguice.util.Strings;
public class CheckInsListAdapter extends AlternatingColorListAdapter {
/**
@@ -17,8 +14,8 @@ public class CheckInsListAdapter extends AlternatingColorListAdapter {
* @param items
* @param selectable
*/
- public CheckInsListAdapter(LayoutInflater inflater, List items,
- boolean selectable) {
+ public CheckInsListAdapter(final LayoutInflater inflater, final List items,
+ final boolean selectable) {
super(R.layout.checkin_list_item, inflater, items, selectable);
}
@@ -26,19 +23,19 @@ public CheckInsListAdapter(LayoutInflater inflater, List items,
* @param inflater
* @param items
*/
- public CheckInsListAdapter(LayoutInflater inflater, List items) {
+ public CheckInsListAdapter(final LayoutInflater inflater, final List items) {
super(R.layout.checkin_list_item, inflater, items);
}
@Override
protected int[] getChildViewIds() {
- return new int[] { R.id.tv_name, R.id.tv_date };
+ return new int[]{R.id.tv_name, R.id.tv_date};
}
@Override
- protected void update(int position, CheckIn item) {
+ protected void update(final int position, final CheckIn item) {
super.update(position, item);
- setText(R.id.tv_name, item.getName());
+ setText(0, item.getName());
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/ui/CheckInsListFragment.java b/app/src/main/java/com/donnfelker/android/bootstrap/ui/CheckInsListFragment.java
index 0769ff8..1ac95f1 100644
--- a/app/src/main/java/com/donnfelker/android/bootstrap/ui/CheckInsListFragment.java
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/ui/CheckInsListFragment.java
@@ -10,19 +10,30 @@
import android.widget.ListView;
import com.donnfelker.android.bootstrap.BootstrapServiceProvider;
+import com.donnfelker.android.bootstrap.Injector;
import com.donnfelker.android.bootstrap.R;
+import com.donnfelker.android.bootstrap.authenticator.LogoutService;
import com.donnfelker.android.bootstrap.core.CheckIn;
import com.github.kevinsawicki.wishlist.SingleTypeAdapter;
-import com.google.inject.Inject;
+import java.util.Collections;
import java.util.List;
+import javax.inject.Inject;
+
public class CheckInsListFragment extends ItemListFragment {
@Inject protected BootstrapServiceProvider serviceProvider;
+ @Inject protected LogoutService logoutService;
+
+ @Override
+ public void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Injector.inject(this);
+ }
@Override
- protected void configureList(Activity activity, ListView listView) {
+ protected void configureList(final Activity activity, final ListView listView) {
super.configureList(activity, listView);
listView.setFastScrollEnabled(true);
@@ -33,6 +44,11 @@ protected void configureList(Activity activity, ListView listView) {
.inflate(R.layout.checkins_list_item_labels, null));
}
+ @Override
+ protected LogoutService getLogoutService() {
+ return logoutService;
+ }
+
@Override
public void onDestroyView() {
setListAdapter(null);
@@ -41,16 +57,20 @@ public void onDestroyView() {
}
@Override
- public Loader> onCreateLoader(int id, Bundle args) {
+ public Loader> onCreateLoader(final int id, final Bundle args) {
final List initialItems = items;
return new ThrowableLoader>(getActivity(), items) {
@Override
public List loadData() throws Exception {
try {
- return serviceProvider.getService().getCheckIns();
- } catch (OperationCanceledException e) {
- Activity activity = getActivity();
+ if (getActivity() != null) {
+ return serviceProvider.getService(getActivity()).getCheckIns();
+ } else {
+ return Collections.emptyList();
+ }
+ } catch (final OperationCanceledException e) {
+ final Activity activity = getActivity();
if (activity != null)
activity.finish();
return initialItems;
@@ -60,24 +80,26 @@ public List loadData() throws Exception {
}
@Override
- protected SingleTypeAdapter createAdapter(List items) {
+ protected SingleTypeAdapter createAdapter(final List items) {
return new CheckInsListAdapter(getActivity().getLayoutInflater(), items);
}
- public void onListItemClick(ListView l, View v, int position, long id) {
- CheckIn checkIn = ((CheckIn) l.getItemAtPosition(position));
+ public void onListItemClick(final ListView l, final View v, final int position, final long id) {
+ final CheckIn checkIn = ((CheckIn) l.getItemAtPosition(position));
- String uri = String.format("geo:%s,%s?q=%s",
+ final String uri = String.format("geo:%s,%s?q=%s",
checkIn.getLocation().getLatitude(),
checkIn.getLocation().getLongitude(),
checkIn.getName());
// Show a chooser that allows the user to decide how to display this data, in this case, map data.
- startActivity(Intent.createChooser(new Intent(Intent.ACTION_VIEW, Uri.parse(uri)), getString(R.string.choose)));
+ startActivity(Intent.createChooser(
+ new Intent(Intent.ACTION_VIEW, Uri.parse(uri)), getString(R.string.choose))
+ );
}
@Override
- protected int getErrorMessage(Exception exception) {
+ protected int getErrorMessage(final Exception exception) {
return R.string.error_loading_checkins;
}
}
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/ui/HeaderFooterListAdapter.java b/app/src/main/java/com/donnfelker/android/bootstrap/ui/HeaderFooterListAdapter.java
index d2be941..e7b331d 100644
--- a/app/src/main/java/com/donnfelker/android/bootstrap/ui/HeaderFooterListAdapter.java
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/ui/HeaderFooterListAdapter.java
@@ -31,13 +31,13 @@ public class HeaderFooterListAdapter extends
* @param view
* @param adapter
*/
- public HeaderFooterListAdapter(ListView view, E adapter) {
+ public HeaderFooterListAdapter(final ListView view, final E adapter) {
this(new ArrayList(), new ArrayList(),
view, adapter);
}
- private HeaderFooterListAdapter(ArrayList headerViewInfos,
- ArrayList footerViewInfos, ListView view, E adapter) {
+ private HeaderFooterListAdapter(final ArrayList headerViewInfos,
+ final ArrayList footerViewInfos, final ListView view, final E adapter) {
super(headerViewInfos, footerViewInfos, adapter);
headers = headerViewInfos;
@@ -49,11 +49,11 @@ private HeaderFooterListAdapter(ArrayList headerViewInfos,
/**
* Add non-selectable header view with no data
*
- * @see #addHeader(View, Object, boolean)
* @param view
* @return this adapter
+ * @see #addHeader(View, Object, boolean)
*/
- public HeaderFooterListAdapter addHeader(View view) {
+ public HeaderFooterListAdapter addHeader(final View view) {
return addHeader(view, null, false);
}
@@ -65,9 +65,9 @@ public HeaderFooterListAdapter addHeader(View view) {
* @param isSelectable
* @return this adapter
*/
- public HeaderFooterListAdapter addHeader(View view, Object data,
- boolean isSelectable) {
- FixedViewInfo info = list.new FixedViewInfo();
+ public HeaderFooterListAdapter addHeader(final View view, final Object data,
+ final boolean isSelectable) {
+ final FixedViewInfo info = list.new FixedViewInfo();
info.view = view;
info.data = data;
info.isSelectable = isSelectable;
@@ -80,11 +80,11 @@ public HeaderFooterListAdapter addHeader(View view, Object data,
/**
* Add non-selectable footer view with no data
*
- * @see #addFooter(View, Object, boolean)
* @param view
* @return this adapter
+ * @see #addFooter(View, Object, boolean)
*/
- public HeaderFooterListAdapter addFooter(View view) {
+ public HeaderFooterListAdapter addFooter(final View view) {
return addFooter(view, null, false);
}
@@ -96,9 +96,9 @@ public HeaderFooterListAdapter addFooter(View view) {
* @param isSelectable
* @return this adapter
*/
- public HeaderFooterListAdapter addFooter(View view, Object data,
- boolean isSelectable) {
- FixedViewInfo info = list.new FixedViewInfo();
+ public HeaderFooterListAdapter addFooter(final View view, final Object data,
+ final boolean isSelectable) {
+ final FixedViewInfo info = list.new FixedViewInfo();
info.view = view;
info.data = data;
info.isSelectable = isSelectable;
@@ -109,10 +109,11 @@ public HeaderFooterListAdapter addFooter(View view, Object data,
}
@Override
- public boolean removeHeader(View v) {
- boolean removed = super.removeHeader(v);
- if (removed)
+ public boolean removeHeader(final View v) {
+ final boolean removed = super.removeHeader(v);
+ if (removed) {
wrapped.notifyDataSetChanged();
+ }
return removed;
}
@@ -124,13 +125,15 @@ public boolean removeHeader(View v) {
public boolean clearHeaders() {
boolean removed = false;
if (!headers.isEmpty()) {
- FixedViewInfo[] infos = headers.toArray(new FixedViewInfo[headers
+ final FixedViewInfo[] infos = headers.toArray(new FixedViewInfo[headers
.size()]);
- for (FixedViewInfo info : infos)
+ for (final FixedViewInfo info : infos) {
removed = super.removeHeader(info.view) || removed;
+ }
}
- if (removed)
+ if (removed) {
wrapped.notifyDataSetChanged();
+ }
return removed;
}
@@ -142,21 +145,24 @@ public boolean clearHeaders() {
public boolean clearFooters() {
boolean removed = false;
if (!footers.isEmpty()) {
- FixedViewInfo[] infos = footers.toArray(new FixedViewInfo[footers
+ final FixedViewInfo[] infos = footers.toArray(new FixedViewInfo[footers
.size()]);
- for (FixedViewInfo info : infos)
+ for (final FixedViewInfo info : infos) {
removed = super.removeFooter(info.view) || removed;
+ }
}
- if (removed)
+ if (removed) {
wrapped.notifyDataSetChanged();
+ }
return removed;
}
@Override
- public boolean removeFooter(View v) {
- boolean removed = super.removeFooter(v);
- if (removed)
+ public boolean removeFooter(final View v) {
+ final boolean removed = super.removeFooter(v);
+ if (removed) {
wrapped.notifyDataSetChanged();
+ }
return removed;
}
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/ui/ItemListFragment.java b/app/src/main/java/com/donnfelker/android/bootstrap/ui/ItemListFragment.java
index 06370b6..e950599 100644
--- a/app/src/main/java/com/donnfelker/android/bootstrap/ui/ItemListFragment.java
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/ui/ItemListFragment.java
@@ -1,12 +1,16 @@
package com.donnfelker.android.bootstrap.ui;
-import android.accounts.AccountManager;
import android.app.Activity;
import android.os.Bundle;
+import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.Loader;
+import android.support.v7.app.ActionBarActivity;
import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AnimationUtils;
@@ -17,25 +21,17 @@
import android.widget.ProgressBar;
import android.widget.TextView;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuInflater;
-import com.actionbarsherlock.view.MenuItem;
import com.donnfelker.android.bootstrap.R;
+import com.donnfelker.android.bootstrap.R.id;
+import com.donnfelker.android.bootstrap.R.layout;
import com.donnfelker.android.bootstrap.authenticator.LogoutService;
import com.github.kevinsawicki.wishlist.SingleTypeAdapter;
import com.github.kevinsawicki.wishlist.Toaster;
import com.github.kevinsawicki.wishlist.ViewUtils;
-import com.donnfelker.android.bootstrap.R.id;
-import com.donnfelker.android.bootstrap.R.layout;
-import com.donnfelker.android.bootstrap.R.menu;
-import com.github.rtyley.android.sherlock.roboguice.fragment.RoboSherlockFragment;
-import com.google.inject.Inject;
import java.util.Collections;
import java.util.List;
-import java.util.concurrent.Callable;
-import roboguice.util.RoboAsyncTask;
/**
* Base fragment for displaying a list of items that loads with a progress bar
@@ -43,20 +39,17 @@
*
* @param
*/
-public abstract class ItemListFragment extends RoboSherlockFragment
+public abstract class ItemListFragment extends Fragment
implements LoaderCallbacks> {
- @Inject protected LogoutService logoutService;
-
private static final String FORCE_REFRESH = "forceRefresh";
/**
- * @param args
- * bundle passed to the loader by the LoaderManager
+ * @param args bundle passed to the loader by the LoaderManager
* @return true if the bundle indicates a requested forced refresh of the
- * items
+ * items
*/
- protected static boolean isForceRefresh(Bundle args) {
+ protected static boolean isForceRefresh(final Bundle args) {
return args != null && args.getBoolean(FORCE_REFRESH, false);
}
@@ -86,18 +79,19 @@ protected static boolean isForceRefresh(Bundle args) {
protected boolean listShown;
@Override
- public void onActivityCreated(Bundle savedInstanceState) {
+ public void onActivityCreated(final Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
- if (!items.isEmpty())
+ if (!items.isEmpty()) {
setListShown(true, false);
+ }
getLoaderManager().initLoader(0, null, this);
}
@Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
+ public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
+ final Bundle savedInstanceState) {
return inflater.inflate(layout.item_list, null);
}
@@ -115,7 +109,7 @@ public void onDestroyView() {
}
@Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
+ public void onViewCreated(final View view, final Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
listView = (ListView) view.findViewById(android.R.id.list);
@@ -123,7 +117,7 @@ public void onViewCreated(View view, Bundle savedInstanceState) {
@Override
public void onItemClick(AdapterView> parent, View view,
- int position, long id) {
+ int position, long id) {
onListItemClick((ListView) parent, view, position, id);
}
});
@@ -140,40 +134,43 @@ public void onItemClick(AdapterView> parent, View view,
* @param activity
* @param listView
*/
- protected void configureList(Activity activity, ListView listView) {
+ protected void configureList(final Activity activity, final ListView listView) {
listView.setAdapter(createAdapter());
}
@Override
- public void onCreate(Bundle savedInstanceState) {
+ public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
- public void onCreateOptionsMenu(Menu optionsMenu, MenuInflater inflater) {
+ public void onCreateOptionsMenu(final Menu optionsMenu, final MenuInflater inflater) {
inflater.inflate(R.menu.bootstrap, optionsMenu);
}
@Override
- public boolean onOptionsItemSelected(MenuItem item) {
- if (!isUsable())
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ if (!isUsable()) {
return false;
+ }
switch (item.getItemId()) {
- case id.refresh:
- forceRefresh();
- return true;
- case R.id.logout:
- logout();
- return true;
- default:
- return super.onOptionsItemSelected(item);
+ case id.refresh:
+ forceRefresh();
+ return true;
+ case R.id.logout:
+ logout();
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
}
}
+ protected abstract LogoutService getLogoutService();
+
private void logout() {
- logoutService.logout(new Runnable() {
+ getLogoutService().logout(new Runnable() {
@Override
public void run() {
// Calling a refresh will force the service to look for a logged in user
@@ -187,7 +184,7 @@ public void run() {
* Force a refresh of the items displayed ignoring any cached items
*/
protected void forceRefresh() {
- Bundle bundle = new Bundle();
+ final Bundle bundle = new Bundle();
bundle.putBoolean(FORCE_REFRESH, true);
refresh(bundle);
}
@@ -200,27 +197,32 @@ public void refresh() {
}
private void refresh(final Bundle args) {
- if (!isUsable())
+ if (!isUsable()) {
return;
+ }
- getSherlockActivity().setSupportProgressBarIndeterminateVisibility(true);
+ getActionBarActivity().setSupportProgressBarIndeterminateVisibility(true);
getLoaderManager().restartLoader(0, args, this);
}
+ private ActionBarActivity getActionBarActivity() {
+ return ((ActionBarActivity) getActivity());
+ }
+
/**
* Get error message to display for exception
*
* @param exception
* @return string resource id
*/
- protected abstract int getErrorMessage(Exception exception);
+ protected abstract int getErrorMessage(final Exception exception);
- public void onLoadFinished(Loader> loader, List items) {
+ public void onLoadFinished(final Loader> loader, final List items) {
- getSherlockActivity().setSupportProgressBarIndeterminateVisibility(false);
+ getActionBarActivity().setSupportProgressBarIndeterminateVisibility(false);
- Exception exception = getException(loader);
+ final Exception exception = getException(loader);
if (exception != null) {
showError(getErrorMessage(exception));
showList();
@@ -238,7 +240,7 @@ public void onLoadFinished(Loader> loader, List items) {
* @return adapter
*/
protected HeaderFooterListAdapter> createAdapter() {
- SingleTypeAdapter wrapped = createAdapter(items);
+ final SingleTypeAdapter wrapped = createAdapter(items);
return new HeaderFooterListAdapter>(getListView(),
wrapped);
}
@@ -259,7 +261,7 @@ protected void showList() {
}
@Override
- public void onLoaderReset(Loader> loader) {
+ public void onLoaderReset(final Loader> loader) {
// Intentionally left blank
}
@@ -280,10 +282,11 @@ protected void showError(final int message) {
* @return exception or null if none provided
*/
protected Exception getException(final Loader> loader) {
- if (loader instanceof ThrowableLoader)
+ if (loader instanceof ThrowableLoader) {
return ((ThrowableLoader>) loader).clearException();
- else
+ } else {
return null;
+ }
}
/**
@@ -311,11 +314,11 @@ public ListView getListView() {
*/
@SuppressWarnings("unchecked")
protected HeaderFooterListAdapter> getListAdapter() {
- if (listView != null)
+ if (listView != null) {
return (HeaderFooterListAdapter>) listView
.getAdapter();
- else
- return null;
+ }
+ return null;
}
/**
@@ -325,18 +328,21 @@ protected HeaderFooterListAdapter> getListAdapter() {
* @return this fragment
*/
protected ItemListFragment setListAdapter(final ListAdapter adapter) {
- if (listView != null)
+ if (listView != null) {
listView.setAdapter(adapter);
+ }
return this;
}
private ItemListFragment fadeIn(final View view, final boolean animate) {
- if (view != null)
- if (animate)
+ if (view != null) {
+ if (animate) {
view.startAnimation(AnimationUtils.loadAnimation(getActivity(),
android.R.anim.fade_in));
- else
+ } else {
view.clearAnimation();
+ }
+ }
return this;
}
@@ -367,35 +373,38 @@ public ItemListFragment setListShown(final boolean shown) {
* @param animate
* @return this fragment
*/
- public ItemListFragment setListShown(final boolean shown,
- final boolean animate) {
- if (!isUsable())
+ public ItemListFragment setListShown(final boolean shown, final boolean animate) {
+ if (!isUsable()) {
return this;
+ }
if (shown == listShown) {
- if (shown)
+ if (shown) {
// List has already been shown so hide/show the empty view with
// no fade effect
- if (items.isEmpty())
+ if (items.isEmpty()) {
hide(listView).show(emptyView);
- else
+ } else {
hide(emptyView).show(listView);
+ }
+ }
return this;
}
listShown = shown;
- if (shown)
- if (!items.isEmpty())
+ if (shown) {
+ if (!items.isEmpty()) {
hide(progressBar).hide(emptyView).fadeIn(listView, animate)
.show(listView);
- else
+ } else {
hide(progressBar).hide(listView).fadeIn(emptyView, animate)
.show(emptyView);
- else
+ }
+ } else {
hide(listView).hide(emptyView).fadeIn(progressBar, animate)
.show(progressBar);
-
+ }
return this;
}
@@ -406,8 +415,9 @@ public ItemListFragment setListShown(final boolean shown,
* @return this fragment
*/
protected ItemListFragment setEmptyText(final String message) {
- if (emptyView != null)
+ if (emptyView != null) {
emptyView.setText(message);
+ }
return this;
}
@@ -418,8 +428,9 @@ protected ItemListFragment setEmptyText(final String message) {
* @return this fragment
*/
protected ItemListFragment setEmptyText(final int resId) {
- if (emptyView != null)
+ if (emptyView != null) {
emptyView.setText(resId);
+ }
return this;
}
@@ -431,7 +442,8 @@ protected ItemListFragment setEmptyText(final int resId) {
* @param position
* @param id
*/
- public void onListItemClick(ListView l, View v, int position, long id) {
+ public void onListItemClick(final ListView l, final View v,
+ final int position, final long id) {
}
/**
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/ui/MainActivity.java b/app/src/main/java/com/donnfelker/android/bootstrap/ui/MainActivity.java
new file mode 100644
index 0000000..ef0d818
--- /dev/null
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/ui/MainActivity.java
@@ -0,0 +1,214 @@
+
+
+package com.donnfelker.android.bootstrap.ui;
+
+import android.accounts.OperationCanceledException;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.support.v4.app.ActionBarDrawerToggle;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.widget.DrawerLayout;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.Window;
+
+import com.donnfelker.android.bootstrap.BootstrapServiceProvider;
+import com.donnfelker.android.bootstrap.R;
+import com.donnfelker.android.bootstrap.core.BootstrapService;
+import com.donnfelker.android.bootstrap.events.NavItemSelectedEvent;
+import com.donnfelker.android.bootstrap.util.Ln;
+import com.donnfelker.android.bootstrap.util.SafeAsyncTask;
+import com.donnfelker.android.bootstrap.util.UIUtils;
+import com.squareup.otto.Subscribe;
+
+import javax.inject.Inject;
+
+import butterknife.Views;
+
+
+/**
+ * Initial activity for the application.
+ *
+ * If you need to remove the authentication from the application please see
+ * {@link com.donnfelker.android.bootstrap.authenticator.ApiKeyProvider#getAuthKey(android.app.Activity)}
+ */
+public class MainActivity extends BootstrapFragmentActivity {
+
+ @Inject protected BootstrapServiceProvider serviceProvider;
+
+ private boolean userHasAuthenticated = false;
+
+ private DrawerLayout drawerLayout;
+ private ActionBarDrawerToggle drawerToggle;
+ private CharSequence drawerTitle;
+ private CharSequence title;
+ private NavigationDrawerFragment navigationDrawerFragment;
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+
+ requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
+
+ super.onCreate(savedInstanceState);
+
+ if(isTablet()) {
+ setContentView(R.layout.main_activity_tablet);
+ } else {
+ setContentView(R.layout.main_activity);
+ }
+
+ // View injection with Butterknife
+ Views.inject(this);
+
+ // Set up navigation drawer
+ title = drawerTitle = getTitle();
+
+ if(!isTablet()) {
+ drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
+ drawerToggle = new ActionBarDrawerToggle(
+ this, /* Host activity */
+ drawerLayout, /* DrawerLayout object */
+ R.drawable.ic_drawer, /* nav drawer icon to replace 'Up' caret */
+ R.string.navigation_drawer_open, /* "open drawer" description */
+ R.string.navigation_drawer_close) { /* "close drawer" description */
+
+ /** Called when a drawer has settled in a completely closed state. */
+ public void onDrawerClosed(View view) {
+ getSupportActionBar().setTitle(title);
+ supportInvalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
+ }
+
+ /** Called when a drawer has settled in a completely open state. */
+ public void onDrawerOpened(View drawerView) {
+ getSupportActionBar().setTitle(drawerTitle);
+ supportInvalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
+ }
+ };
+
+ // Set the drawer toggle as the DrawerListener
+ drawerLayout.setDrawerListener(drawerToggle);
+
+ navigationDrawerFragment = (NavigationDrawerFragment)
+ getSupportFragmentManager().findFragmentById(R.id.navigation_drawer);
+
+ // Set up the drawer.
+ navigationDrawerFragment.setUp(
+ R.id.navigation_drawer,
+ (DrawerLayout) findViewById(R.id.drawer_layout));
+ }
+
+
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ getSupportActionBar().setHomeButtonEnabled(true);
+
+
+ checkAuth();
+
+ }
+
+ private boolean isTablet() {
+ return UIUtils.isTablet(this);
+ }
+
+ @Override
+ protected void onPostCreate(final Bundle savedInstanceState) {
+ super.onPostCreate(savedInstanceState);
+
+ if(!isTablet()) {
+ // Sync the toggle state after onRestoreInstanceState has occurred.
+ drawerToggle.syncState();
+ }
+ }
+
+
+ @Override
+ public void onConfigurationChanged(final Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ if(!isTablet()) {
+ drawerToggle.onConfigurationChanged(newConfig);
+ }
+ }
+
+
+ private void initScreen() {
+ if (userHasAuthenticated) {
+
+ Ln.d("Foo");
+ final FragmentManager fragmentManager = getSupportFragmentManager();
+ fragmentManager.beginTransaction()
+ .replace(R.id.container, new CarouselFragment())
+ .commit();
+ }
+
+ }
+
+ private void checkAuth() {
+ new SafeAsyncTask() {
+
+ @Override
+ public Boolean call() throws Exception {
+ final BootstrapService svc = serviceProvider.getService(MainActivity.this);
+ return svc != null;
+ }
+
+ @Override
+ protected void onException(final Exception e) throws RuntimeException {
+ super.onException(e);
+ if (e instanceof OperationCanceledException) {
+ // User cancelled the authentication process (back button, etc).
+ // Since auth could not take place, lets finish this activity.
+ finish();
+ }
+ }
+
+ @Override
+ protected void onSuccess(final Boolean hasAuthenticated) throws Exception {
+ super.onSuccess(hasAuthenticated);
+ userHasAuthenticated = true;
+ initScreen();
+ }
+ }.execute();
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem item) {
+
+ if (!isTablet() && drawerToggle.onOptionsItemSelected(item)) {
+ return true;
+ }
+
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ //menuDrawer.toggleMenu();
+ return true;
+ case R.id.timer:
+ navigateToTimer();
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ private void navigateToTimer() {
+ final Intent i = new Intent(this, BootstrapTimerActivity.class);
+ startActivity(i);
+ }
+
+ @Subscribe
+ public void onNavigationItemSelected(NavItemSelectedEvent event) {
+
+ Ln.d("Selected: %1$s", event.getItemPosition());
+
+ switch(event.getItemPosition()) {
+ case 0:
+ // Home
+ // do nothing as we're already on the home screen.
+ break;
+ case 1:
+ // Timer
+ navigateToTimer();
+ break;
+ }
+ }
+}
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/ui/NavigationDrawerFragment.java b/app/src/main/java/com/donnfelker/android/bootstrap/ui/NavigationDrawerFragment.java
new file mode 100644
index 0000000..178fdaf
--- /dev/null
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/ui/NavigationDrawerFragment.java
@@ -0,0 +1,267 @@
+package com.donnfelker.android.bootstrap.ui;
+
+import android.app.Activity;
+import android.content.SharedPreferences;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.support.v4.app.ActionBarDrawerToggle;
+import android.support.v4.app.Fragment;
+import android.support.v4.widget.DrawerLayout;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.ActionBarActivity;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+
+import com.donnfelker.android.bootstrap.Injector;
+import com.donnfelker.android.bootstrap.R;
+import com.donnfelker.android.bootstrap.events.NavItemSelectedEvent;
+import com.donnfelker.android.bootstrap.util.UIUtils;
+import com.squareup.otto.Bus;
+
+import javax.inject.Inject;
+
+/**
+ * Fragment used for managing interactions for and presentation of a navigation drawer.
+ * See the
+ * design guidelines for a complete explanation of the behaviors implemented here.
+ */
+public class NavigationDrawerFragment extends Fragment {
+
+ /**
+ * Remember the position of the selected item.
+ */
+ private static final String STATE_SELECTED_POSITION = "selected_navigation_drawer_position";
+
+ /**
+ * Per the design guidelines, you should show the drawer on launch until the user manually
+ * expands it. This shared preference tracks this.
+ */
+ private static final String PREF_USER_LEARNED_DRAWER = "navigation_drawer_learned";
+
+ /**
+ * Helper component that ties the action bar to the navigation drawer.
+ */
+ private ActionBarDrawerToggle drawerToggle;
+
+ private DrawerLayout drawerLayout;
+ private ListView drawerListView;
+ private View fragmentContainerView;
+
+ private int currentSelectedPosition = 0;
+ private boolean fromSavedInstanceState;
+ private boolean userLearnedDrawer;
+
+ @Inject protected SharedPreferences prefs;
+ @Inject protected Bus bus;
+
+
+ public NavigationDrawerFragment() {
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Injector.inject(this);
+
+ // Read in the flag indicating whether or not the user has demonstrated awareness of the
+ // drawer. See PREF_USER_LEARNED_DRAWER for details.
+ userLearnedDrawer = prefs.getBoolean(PREF_USER_LEARNED_DRAWER, false);
+
+ if (savedInstanceState != null) {
+ currentSelectedPosition = savedInstanceState.getInt(STATE_SELECTED_POSITION);
+ fromSavedInstanceState = true;
+ }
+
+ // Select either the default item (0) or the last selected item.
+ selectItem(currentSelectedPosition);
+
+
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ // Indicate that this fragment would like to influence the set of actions in the action bar.
+ setHasOptionsMenu(true);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ drawerListView = (ListView) inflater.inflate(R.layout.fragment_navigation_drawer, container, false);
+ drawerListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView> parent, View view, int position, long id) {
+ selectItem(position);
+ }
+ });
+ drawerListView.setAdapter(new ArrayAdapter(
+ getActionBar().getThemedContext(),
+ android.R.layout.simple_list_item_1,
+ android.R.id.text1,
+ new String[] {
+ getString(R.string.title_home),
+ getString(R.string.title_timer)
+ }));
+ drawerListView.setItemChecked(currentSelectedPosition, true);
+ return drawerListView;
+ }
+
+ public boolean isDrawerOpen() {
+ return drawerLayout != null && drawerLayout.isDrawerOpen(fragmentContainerView);
+ }
+
+ /**
+ * Users of this fragment must call this method to set up the navigation drawer interactions.
+ *
+ * @param fragmentId The android:id of this fragment in its activity's layout.
+ * @param drawerLayout The DrawerLayout containing this fragment's UI.
+ */
+ public void setUp(int fragmentId, DrawerLayout drawerLayout) {
+ fragmentContainerView = getActivity().findViewById(fragmentId);
+ this.drawerLayout = drawerLayout;
+
+ // set a custom shadow that overlays the main content when the drawer opens
+ //drawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
+ // set up the drawer's list view with items and click listener
+
+ ActionBar actionBar = getActionBar();
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ actionBar.setHomeButtonEnabled(true);
+
+ // ActionBarDrawerToggle ties together the the proper interactions
+ // between the navigation drawer and the action bar app icon.
+ drawerToggle = new ActionBarDrawerToggle(
+ getActivity(), /* host Activity */
+ NavigationDrawerFragment.this.drawerLayout, /* DrawerLayout object */
+ R.drawable.ic_drawer, /* nav drawer image to replace 'Up' caret */
+ R.string.navigation_drawer_open, /* "open drawer" description for accessibility */
+ R.string.navigation_drawer_close /* "close drawer" description for accessibility */
+ ) {
+ @Override
+ public void onDrawerClosed(View drawerView) {
+ super.onDrawerClosed(drawerView);
+ if (!isAdded()) {
+ return;
+ }
+
+ getActivity().supportInvalidateOptionsMenu(); // calls onPrepareOptionsMenu()
+ }
+
+ @Override
+ public void onDrawerOpened(View drawerView) {
+ super.onDrawerOpened(drawerView);
+ if (!isAdded()) {
+ return;
+ }
+
+ if (!userLearnedDrawer) {
+ // The user manually opened the drawer; store this flag to prevent auto-showing
+ // the navigation drawer automatically in the future.
+ userLearnedDrawer = true;
+ prefs.edit().putBoolean(PREF_USER_LEARNED_DRAWER, true).apply();
+ }
+
+ getActivity().supportInvalidateOptionsMenu(); // calls onPrepareOptionsMenu()
+ }
+ };
+
+ // If the user hasn't 'learned' about the drawer, open it to introduce them to the drawer,
+ // per the navigation drawer design guidelines.
+ if (!userLearnedDrawer && !fromSavedInstanceState) {
+ this.drawerLayout.openDrawer(fragmentContainerView);
+ }
+
+ // Defer code dependent on restoration of previous instance state.
+ this.drawerLayout.post(new Runnable() {
+ @Override
+ public void run() {
+ drawerToggle.syncState();
+ }
+ });
+
+ this.drawerLayout.setDrawerListener(drawerToggle);
+ }
+
+ private void selectItem(int position) {
+ currentSelectedPosition = position;
+ if (drawerListView != null) {
+ drawerListView.setItemChecked(position, true);
+ }
+ if (drawerLayout != null) {
+ drawerLayout.closeDrawer(fragmentContainerView);
+ }
+
+ // Fire the event off to the bus which.
+ bus.post(new NavItemSelectedEvent(position));
+
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putInt(STATE_SELECTED_POSITION, currentSelectedPosition);
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+
+ if(!isTablet()) {
+ // Forward the new configuration the drawer toggle component.
+ drawerToggle.onConfigurationChanged(newConfig);
+ }
+ }
+
+ private boolean isTablet() {
+ if(getActivity() != null) {
+ return UIUtils.isTablet(getActivity());
+ }
+ return false;
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ // If the drawer is open, show the global app actions in the action bar. See also
+ // showGlobalContextActionBar, which controls the top-left area of the action bar.
+ if (drawerLayout != null && isDrawerOpen()) {
+ inflater.inflate(R.menu.global, menu);
+ showGlobalContextActionBar();
+ }
+ super.onCreateOptionsMenu(menu, inflater);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (drawerToggle.onOptionsItemSelected(item)) {
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+
+ /**
+ * Per the navigation drawer design guidelines, updates the action bar to show the global app
+ * 'context', rather than just what's in the current screen.
+ */
+ private void showGlobalContextActionBar() {
+ ActionBar actionBar = getActionBar();
+ actionBar.setDisplayShowTitleEnabled(true);
+ actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
+ actionBar.setTitle(R.string.app_name);
+ }
+
+ private ActionBar getActionBar() {
+ return ((ActionBarActivity) getActivity()).getSupportActionBar();
+ }
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/ui/NewsActivity.java b/app/src/main/java/com/donnfelker/android/bootstrap/ui/NewsActivity.java
index bac4b8c..30ccec4 100644
--- a/app/src/main/java/com/donnfelker/android/bootstrap/ui/NewsActivity.java
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/ui/NewsActivity.java
@@ -1,36 +1,32 @@
package com.donnfelker.android.bootstrap.ui;
-import android.content.Intent;
import android.os.Bundle;
import android.widget.TextView;
-import com.actionbarsherlock.R;
-import com.actionbarsherlock.view.MenuItem;
-import com.donnfelker.android.bootstrap.BootstrapServiceProvider;
+import com.donnfelker.android.bootstrap.R;
import com.donnfelker.android.bootstrap.core.News;
-import com.github.rtyley.android.sherlock.roboguice.activity.RoboSherlockActivity;
-import com.google.inject.Inject;
-import roboguice.inject.InjectExtra;
-import roboguice.inject.InjectView;
+import butterknife.InjectView;
-import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
-import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
import static com.donnfelker.android.bootstrap.core.Constants.Extra.NEWS_ITEM;
public class NewsActivity extends BootstrapActivity {
- @InjectExtra(NEWS_ITEM) protected News newsItem;
+ private News newsItem;
@InjectView(R.id.tv_title) protected TextView title;
@InjectView(R.id.tv_content) protected TextView content;
@Override
- protected void onCreate(Bundle savedInstanceState) {
+ protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.news);
+ if (getIntent() != null && getIntent().getExtras() != null) {
+ newsItem = (News) getIntent().getExtras().getSerializable(NEWS_ITEM);
+ }
+
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeButtonEnabled(true);
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/ui/NewsListAdapter.java b/app/src/main/java/com/donnfelker/android/bootstrap/ui/NewsListAdapter.java
index 20f9567..144176c 100644
--- a/app/src/main/java/com/donnfelker/android/bootstrap/ui/NewsListAdapter.java
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/ui/NewsListAdapter.java
@@ -4,7 +4,6 @@
import com.donnfelker.android.bootstrap.R;
import com.donnfelker.android.bootstrap.core.News;
-import com.donnfelker.android.bootstrap.ui.AlternatingColorListAdapter;
import java.util.List;
@@ -14,8 +13,8 @@ public class NewsListAdapter extends AlternatingColorListAdapter {
* @param items
* @param selectable
*/
- public NewsListAdapter(LayoutInflater inflater, List items,
- boolean selectable) {
+ public NewsListAdapter(final LayoutInflater inflater, final List items,
+ final boolean selectable) {
super(R.layout.news_list_item, inflater, items, selectable);
}
@@ -23,22 +22,22 @@ public NewsListAdapter(LayoutInflater inflater, List items,
* @param inflater
* @param items
*/
- public NewsListAdapter(LayoutInflater inflater, List items) {
+ public NewsListAdapter(final LayoutInflater inflater, final List items) {
super(R.layout.news_list_item, inflater, items);
}
@Override
protected int[] getChildViewIds() {
- return new int[] { R.id.tv_title, R.id.tv_summary,
- R.id.tv_date };
+ return new int[]{R.id.tv_title, R.id.tv_summary,
+ R.id.tv_date};
}
@Override
- protected void update(int position, News item) {
+ protected void update(final int position, final News item) {
super.update(position, item);
- setText(R.id.tv_title, item.getTitle());
- setText(R.id.tv_summary, item.getContent());
+ setText(0, item.getTitle());
+ setText(1, item.getContent());
//setNumber(R.id.tv_date, item.getCreatedAt());
}
}
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/ui/NewsListFragment.java b/app/src/main/java/com/donnfelker/android/bootstrap/ui/NewsListFragment.java
index 430fb73..18e11a9 100644
--- a/app/src/main/java/com/donnfelker/android/bootstrap/ui/NewsListFragment.java
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/ui/NewsListFragment.java
@@ -1,6 +1,5 @@
package com.donnfelker.android.bootstrap.ui;
-import static com.donnfelker.android.bootstrap.core.Constants.Extra.NEWS_ITEM;
import android.accounts.OperationCanceledException;
import android.app.Activity;
import android.content.Intent;
@@ -10,16 +9,29 @@
import android.widget.ListView;
import com.donnfelker.android.bootstrap.BootstrapServiceProvider;
+import com.donnfelker.android.bootstrap.Injector;
import com.donnfelker.android.bootstrap.R;
+import com.donnfelker.android.bootstrap.authenticator.LogoutService;
import com.donnfelker.android.bootstrap.core.News;
import com.github.kevinsawicki.wishlist.SingleTypeAdapter;
-import com.google.inject.Inject;
+import java.util.Collections;
import java.util.List;
+import javax.inject.Inject;
+
+import static com.donnfelker.android.bootstrap.core.Constants.Extra.NEWS_ITEM;
+
public class NewsListFragment extends ItemListFragment {
@Inject protected BootstrapServiceProvider serviceProvider;
+ @Inject protected LogoutService logoutService;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Injector.inject(this);
+ }
@Override
public void onActivityCreated(Bundle savedInstanceState) {
@@ -40,6 +52,11 @@ protected void configureList(Activity activity, ListView listView) {
.inflate(R.layout.news_list_item_labels, null));
}
+ @Override
+ protected LogoutService getLogoutService() {
+ return logoutService;
+ }
+
@Override
public void onDestroyView() {
setListAdapter(null);
@@ -55,7 +72,12 @@ public Loader> onCreateLoader(int id, Bundle args) {
@Override
public List loadData() throws Exception {
try {
- return serviceProvider.getService().getNews();
+ if (getActivity() != null) {
+ return serviceProvider.getService(getActivity()).getNews();
+ } else {
+ return Collections.emptyList();
+ }
+
} catch (OperationCanceledException e) {
Activity activity = getActivity();
if (activity != null)
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/ui/TextWatcherAdapter.java b/app/src/main/java/com/donnfelker/android/bootstrap/ui/TextWatcherAdapter.java
index 695624e..7e4a283 100644
--- a/app/src/main/java/com/donnfelker/android/bootstrap/ui/TextWatcherAdapter.java
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/ui/TextWatcherAdapter.java
@@ -10,12 +10,14 @@
*/
public class TextWatcherAdapter implements TextWatcher {
- public void afterTextChanged(Editable s) {
+ public void afterTextChanged(final Editable s) {
}
- public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ public void beforeTextChanged(final CharSequence s, final int start,
+ final int count, final int after) {
}
- public void onTextChanged(CharSequence s, int start, int before, int count) {
+ public void onTextChanged(final CharSequence s, final int start,
+ final int before, final int count) {
}
}
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/ui/ThrowableLoader.java b/app/src/main/java/com/donnfelker/android/bootstrap/ui/ThrowableLoader.java
index f4a6d61..a224e9f 100644
--- a/app/src/main/java/com/donnfelker/android/bootstrap/ui/ThrowableLoader.java
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/ui/ThrowableLoader.java
@@ -2,9 +2,9 @@
package com.donnfelker.android.bootstrap.ui;
import android.content.Context;
-import android.util.Log;
-import roboguice.util.Ln;
+import com.donnfelker.android.bootstrap.util.Ln;
+
/**
* Loader that support throwing an exception when loading in the background
@@ -13,7 +13,6 @@
*/
public abstract class ThrowableLoader extends AsyncLoader {
-
private final D data;
private Exception exception;
@@ -24,7 +23,7 @@ public abstract class ThrowableLoader extends AsyncLoader {
* @param context
* @param data
*/
- public ThrowableLoader(Context context, D data) {
+ public ThrowableLoader(final Context context, final D data) {
super(context);
this.data = data;
@@ -35,7 +34,7 @@ public D loadInBackground() {
exception = null;
try {
return loadData();
- } catch (Exception e) {
+ } catch (final Exception e) {
Ln.d(e, "Exception loading data");
exception = e;
return data;
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/ui/UserActivity.java b/app/src/main/java/com/donnfelker/android/bootstrap/ui/UserActivity.java
index 86e7f37..ee18e4d 100644
--- a/app/src/main/java/com/donnfelker/android/bootstrap/ui/UserActivity.java
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/ui/UserActivity.java
@@ -1,37 +1,41 @@
package com.donnfelker.android.bootstrap.ui;
-import static com.donnfelker.android.bootstrap.core.Constants.Extra.USER;
import android.os.Bundle;
import android.widget.ImageView;
import android.widget.TextView;
import com.donnfelker.android.bootstrap.R;
-import com.donnfelker.android.bootstrap.core.AvatarLoader;
import com.donnfelker.android.bootstrap.core.User;
-import com.google.inject.Inject;
+import com.squareup.picasso.Picasso;
+
+import butterknife.InjectView;
-import roboguice.inject.InjectExtra;
-import roboguice.inject.InjectView;
+import static com.donnfelker.android.bootstrap.core.Constants.Extra.USER;
public class UserActivity extends BootstrapActivity {
@InjectView(R.id.iv_avatar) protected ImageView avatar;
@InjectView(R.id.tv_name) protected TextView name;
- @InjectExtra(USER) protected User user;
-
- @Inject protected AvatarLoader avatarLoader;
+ private User user;
@Override
- protected void onCreate(Bundle savedInstanceState) {
+ protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.user_view);
+ if (getIntent() != null && getIntent().getExtras() != null) {
+ user = (User) getIntent().getExtras().getSerializable(USER);
+ }
+
getSupportActionBar().setHomeButtonEnabled(true);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- avatarLoader.bind(avatar, user);
+ Picasso.with(this).load(user.getAvatarUrl())
+ .placeholder(R.drawable.gravatar_icon)
+ .into(avatar);
+
name.setText(String.format("%s %s", user.getFirstName(), user.getLastName()));
}
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/ui/UserListAdapter.java b/app/src/main/java/com/donnfelker/android/bootstrap/ui/UserListAdapter.java
index 5830f3b..b546d3e 100644
--- a/app/src/main/java/com/donnfelker/android/bootstrap/ui/UserListAdapter.java
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/ui/UserListAdapter.java
@@ -3,10 +3,11 @@
import android.text.TextUtils;
import android.view.LayoutInflater;
+import com.donnfelker.android.bootstrap.BootstrapApplication;
import com.donnfelker.android.bootstrap.R;
-import com.donnfelker.android.bootstrap.core.AvatarLoader;
import com.donnfelker.android.bootstrap.core.User;
import com.github.kevinsawicki.wishlist.SingleTypeAdapter;
+import com.squareup.picasso.Picasso;
import java.text.SimpleDateFormat;
import java.util.List;
@@ -17,24 +18,22 @@
public class UserListAdapter extends SingleTypeAdapter {
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("MMMM dd");
- private final AvatarLoader avatars;
/**
* @param inflater
* @param items
*/
- public UserListAdapter(LayoutInflater inflater, List items, AvatarLoader avatars) {
+ public UserListAdapter(final LayoutInflater inflater, final List items) {
super(inflater, R.layout.user_list_item);
- this.avatars = avatars;
setItems(items);
}
/**
* @param inflater
*/
- public UserListAdapter(LayoutInflater inflater, AvatarLoader avatars) {
- this(inflater, null, avatars);
+ public UserListAdapter(final LayoutInflater inflater) {
+ this(inflater, null);
}
@@ -47,15 +46,18 @@ public long getItemId(final int position) {
@Override
protected int[] getChildViewIds() {
- return new int[] { R.id.iv_avatar, R.id.tv_name };
+ return new int[]{R.id.iv_avatar, R.id.tv_name};
}
@Override
- protected void update(int position, User user) {
+ protected void update(final int position, final User user) {
- avatars.bind(imageView(R.id.iv_avatar), user);
+ Picasso.with(BootstrapApplication.getInstance())
+ .load(user.getAvatarUrl())
+ .placeholder(R.drawable.gravatar_icon)
+ .into(imageView(0));
- setText(R.id.tv_name, String.format("%1$s %2$s", user.getFirstName(), user.getLastName()));
+ setText(1, String.format("%1$s %2$s", user.getFirstName(), user.getLastName()));
}
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/ui/UserListFragment.java b/app/src/main/java/com/donnfelker/android/bootstrap/ui/UserListFragment.java
index ab53827..09272ea 100644
--- a/app/src/main/java/com/donnfelker/android/bootstrap/ui/UserListFragment.java
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/ui/UserListFragment.java
@@ -1,7 +1,5 @@
package com.donnfelker.android.bootstrap.ui;
-import static com.donnfelker.android.bootstrap.core.Constants.Extra.NEWS_ITEM;
-import static com.donnfelker.android.bootstrap.core.Constants.Extra.USER;
import android.accounts.OperationCanceledException;
import android.app.Activity;
import android.content.Intent;
@@ -11,58 +9,78 @@
import android.widget.ListView;
import com.donnfelker.android.bootstrap.BootstrapServiceProvider;
+import com.donnfelker.android.bootstrap.Injector;
import com.donnfelker.android.bootstrap.R;
-import com.donnfelker.android.bootstrap.core.AvatarLoader;
-import com.donnfelker.android.bootstrap.core.News;
+import com.donnfelker.android.bootstrap.authenticator.LogoutService;
import com.donnfelker.android.bootstrap.core.User;
import com.github.kevinsawicki.wishlist.SingleTypeAdapter;
-import com.google.inject.Inject;
import java.util.Collections;
import java.util.List;
-public class UserListFragment extends ItemListFragment {
+import javax.inject.Inject;
+
+import static com.donnfelker.android.bootstrap.core.Constants.Extra.USER;
+
+public class UserListFragment extends ItemListFragment {
+
+ @Inject protected BootstrapServiceProvider serviceProvider;
+ @Inject protected LogoutService logoutService;
- @Inject private BootstrapServiceProvider serviceProvider;
- @Inject private AvatarLoader avatars;
@Override
- public void onActivityCreated(Bundle savedInstanceState) {
+ public void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Injector.inject(this);
+ }
+
+ @Override
+ public void onActivityCreated(final Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setEmptyText(R.string.no_users);
}
@Override
- protected void configureList(Activity activity, ListView listView) {
+ protected void configureList(final Activity activity, final ListView listView) {
super.configureList(activity, listView);
listView.setFastScrollEnabled(true);
listView.setDividerHeight(0);
getListAdapter().addHeader(activity.getLayoutInflater()
- .inflate(R.layout.user_list_item_labels, null));
+ .inflate(R.layout.user_list_item_labels, null));
}
-
+ @Override
+ protected LogoutService getLogoutService() {
+ return logoutService;
+ }
@Override
- public Loader> onCreateLoader(int id, Bundle args) {
+ public Loader> onCreateLoader(final int id, final Bundle args) {
final List initialItems = items;
return new ThrowableLoader>(getActivity(), items) {
@Override
public List loadData() throws Exception {
try {
- List latest = serviceProvider.getService().getUsers();
- if (latest != null)
+ List latest = null;
+
+ if (getActivity() != null) {
+ latest = serviceProvider.getService(getActivity()).getUsers();
+ }
+
+ if (latest != null) {
return latest;
- else
+ } else {
return Collections.emptyList();
- } catch (OperationCanceledException e) {
- Activity activity = getActivity();
- if (activity != null)
+ }
+ } catch (final OperationCanceledException e) {
+ final Activity activity = getActivity();
+ if (activity != null) {
activity.finish();
+ }
return initialItems;
}
}
@@ -70,25 +88,25 @@ public List loadData() throws Exception {
}
- public void onListItemClick(ListView l, View v, int position, long id) {
- User user = ((User) l.getItemAtPosition(position));
+ public void onListItemClick(final ListView l, final View v, final int position, final long id) {
+ final User user = ((User) l.getItemAtPosition(position));
startActivity(new Intent(getActivity(), UserActivity.class).putExtra(USER, user));
}
@Override
- public void onLoadFinished(Loader> loader, List items) {
+ public void onLoadFinished(final Loader> loader, final List items) {
super.onLoadFinished(loader, items);
}
@Override
- protected int getErrorMessage(Exception exception) {
+ protected int getErrorMessage(final Exception exception) {
return R.string.error_loading_users;
}
@Override
- protected SingleTypeAdapter createAdapter(List items) {
- return new UserListAdapter(getActivity().getLayoutInflater(), items, avatars);
+ protected SingleTypeAdapter createAdapter(final List items) {
+ return new UserListAdapter(getActivity().getLayoutInflater(), items);
}
}
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/ui/view/CapitalizedTextView.java b/app/src/main/java/com/donnfelker/android/bootstrap/ui/view/CapitalizedTextView.java
new file mode 100644
index 0000000..9bfac6d
--- /dev/null
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/ui/view/CapitalizedTextView.java
@@ -0,0 +1,81 @@
+package com.donnfelker.android.bootstrap.ui.view;
+
+import android.content.Context;
+import android.graphics.Typeface;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.widget.Button;
+
+import com.donnfelker.android.bootstrap.util.Strings;
+import android.util.Log;
+import java.util.Hashtable;
+import java.util.Locale;
+
+/**
+ * A button who's text is always uppercase which uses the roboto font.
+ * Inspired by com.actionbarsherlock.internal.widget.CapitalizingTextView
+ */
+public class CapitalizedTextView extends Button {
+
+ private static final boolean IS_GINGERBREAD
+ = Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD;
+
+ private static final String TAG = "Typefaces";
+ private static final Hashtable cache = new Hashtable();
+
+ public CapitalizedTextView(Context context) {
+ super(context);
+
+ setTF(context);
+ }
+
+ public CapitalizedTextView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ setTF(context);
+ }
+
+ public CapitalizedTextView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ setTF(context);
+
+ }
+
+ @Override
+ public void setText(CharSequence text, BufferType type) {
+ if (IS_GINGERBREAD) {
+ try {
+ super.setText(text.toString().toUpperCase(Locale.ROOT), type);
+ } catch (NoSuchFieldError e) {
+ //Some manufacturer broke Locale.ROOT. See #572.
+ super.setText(text.toString().toUpperCase(), type);
+ }
+ } else {
+ super.setText(text.toString().toUpperCase(), type);
+ }
+ }
+
+ public static Typeface getTypeFace(Context c, String assetPath) {
+ synchronized (cache) {
+ if (!cache.containsKey(assetPath)) {
+ try {
+ Typeface t = Typeface.createFromAsset(c.getAssets(),
+ assetPath);
+ cache.put(assetPath, t);
+ } catch (Exception e) {
+ Log.e(TAG, "Could not get typeface '" + assetPath
+ + "' because " + e.getMessage());
+ return null;
+ }
+ }
+ return cache.get(assetPath);
+ }
+ }
+
+ private void setTF(Context context) {
+ Typeface tf = getTypeFace(context, "fonts/Roboto-Regular.ttf");
+ setTypeface(tf);
+ }
+}
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/util/Ln.java b/app/src/main/java/com/donnfelker/android/bootstrap/util/Ln.java
new file mode 100644
index 0000000..2f52135
--- /dev/null
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/util/Ln.java
@@ -0,0 +1,300 @@
+package com.donnfelker.android.bootstrap.util;
+
+
+import android.app.Application;
+import android.content.pm.ApplicationInfo;
+import android.util.Log;
+
+import javax.inject.Inject;
+
+
+/**
+ * Originally from RoboGuice:
+ * https://github.com/roboguice/roboguice/blob/master/roboguice/src/main/java/roboguice/util/Ln.java
+ *
+ * A more natural android logging facility.
+ *
+ * WARNING: CHECK OUT COMMON PITFALLS BELOW
+ *
+ * Unlike {@link android.util.Log}, Log provides sensible defaults.
+ * Debug and Verbose logging is enabled for applications that
+ * have "android:debuggable=true" in their AndroidManifest.xml.
+ * For apps built using SDK Tools r8 or later, this means any debug
+ * build. Release builds built with r8 or later will have verbose
+ * and debug log messages turned off.
+ *
+ * The default tag is automatically set to your app's packagename,
+ * and the current context (eg. activity, service, application, etc)
+ * is appended as well. You can add an additional parameter to the
+ * tag using {@link #Log(String)}.
+ *
+ * Log-levels can be programatically overridden for specific instances
+ * using {@link #Log(String, boolean, boolean)}.
+ *
+ * Log messages may optionally use {@link String#format(String, Object...)}
+ * formatting, which will not be evaluated unless the log statement is output.
+ * Additional parameters to the logging statement are treated as varrgs parameters
+ * to {@link String#format(String, Object...)}
+ *
+ * Also, the current file and line is automatically appended to the tag
+ * (this is only done if debug is enabled for performance reasons).
+ *
+ * COMMON PITFALLS:
+ * * Make sure you put the exception FIRST in the call. A common
+ * mistake is to place it last as is the android.util.Log convention,
+ * but then it will get treated as varargs parameter.
+ * * vararg parameters are not appended to the log message! You must
+ * insert them into the log message using %s or another similar
+ * format parameter
+ *
+ * Usage Examples:
+ *
+ * Ln.v("hello there");
+ * Ln.d("%s %s", "hello", "there");
+ * Ln.e( exception, "Error during some operation");
+ * Ln.w( exception, "Error during %s operation", "some other");
+ */
+@SuppressWarnings({"ImplicitArrayToString"})
+public class Ln {
+ /**
+ * config is initially set to BaseConfig() with sensible defaults, then replaced
+ * by BaseConfig(ContextSingleton) during guice static injection pass.
+ */
+ @Inject
+ protected static BaseConfig config = new BaseConfig();
+
+ /**
+ * print is initially set to Print(), then replaced by guice during
+ * static injection pass. This allows overriding where the log message is delivered to.
+ */
+ @Inject
+ protected static Print print = new Print();
+
+
+ private Ln() {
+ }
+
+
+ public static int v(Throwable t) {
+ return config.minimumLogLevel <= Log.VERBOSE ? print.println(Log.VERBOSE,
+ Log.getStackTraceString(t)) : 0;
+ }
+
+ public static int v(Object s1, Object... args) {
+ if (config.minimumLogLevel > Log.VERBOSE)
+ return 0;
+
+ final String s = Strings.toString(s1);
+ final String message = args.length > 0 ? String.format(s, args) : s;
+ return print.println(Log.VERBOSE, message);
+ }
+
+ public static int v(Throwable throwable, Object s1, Object... args) {
+ if (config.minimumLogLevel > Log.VERBOSE)
+ return 0;
+
+ final String s = Strings.toString(s1);
+ final String message = (args.length > 0 ? String.format(s, args) : s) + '\n' +
+ Log.getStackTraceString(throwable);
+ return print.println(Log.VERBOSE, message);
+ }
+
+ public static int d(Throwable t) {
+ return config.minimumLogLevel <= Log.DEBUG ? print.println(Log.DEBUG,
+ Log.getStackTraceString(t)) : 0;
+ }
+
+ public static int d(Object s1, Object... args) {
+ if (config.minimumLogLevel > Log.DEBUG)
+ return 0;
+
+ final String s = Strings.toString(s1);
+ final String message = args.length > 0 ? String.format(s, args) : s;
+ return print.println(Log.DEBUG, message);
+ }
+
+ public static int d(Throwable throwable, Object s1, Object... args) {
+ if (config.minimumLogLevel > Log.DEBUG)
+ return 0;
+
+ final String s = Strings.toString(s1);
+ final String message = (args.length > 0 ? String.format(s, args) : s) + '\n' +
+ Log.getStackTraceString(throwable);
+ return print.println(Log.DEBUG, message);
+ }
+
+ public static int i(Throwable t) {
+ return config.minimumLogLevel <= Log.INFO ? print.println(Log.INFO,
+ Log.getStackTraceString(t)) : 0;
+ }
+
+ public static int i(Object s1, Object... args) {
+ if (config.minimumLogLevel > Log.INFO)
+ return 0;
+
+ final String s = Strings.toString(s1);
+ final String message = args.length > 0 ? String.format(s, args) : s;
+ return print.println(Log.INFO, message);
+ }
+
+ public static int i(Throwable throwable, Object s1, Object... args) {
+ if (config.minimumLogLevel > Log.INFO)
+ return 0;
+
+ final String s = Strings.toString(s1);
+ final String message = (args.length > 0 ? String.format(s, args) : s) + '\n' +
+ Log.getStackTraceString(throwable);
+ return print.println(Log.INFO, message);
+ }
+
+ public static int w(Throwable t) {
+ return config.minimumLogLevel <= Log.WARN ? print.println(Log.WARN,
+ Log.getStackTraceString(t)) : 0;
+ }
+
+ public static int w(Object s1, Object... args) {
+ if (config.minimumLogLevel > Log.WARN)
+ return 0;
+
+ final String s = Strings.toString(s1);
+ final String message = args.length > 0 ? String.format(s, args) : s;
+ return print.println(Log.WARN, message);
+ }
+
+ public static int w(Throwable throwable, Object s1, Object... args) {
+ if (config.minimumLogLevel > Log.WARN)
+ return 0;
+
+ final String s = Strings.toString(s1);
+ final String message = (args.length > 0 ? String.format(s, args) : s) + '\n' +
+ Log.getStackTraceString(throwable);
+ return print.println(Log.WARN, message);
+ }
+
+ public static int e(Throwable t) {
+ return config.minimumLogLevel <= Log.ERROR ? print.println(Log.ERROR,
+ Log.getStackTraceString(t)) : 0;
+ }
+
+ public static int e(Object s1, Object... args) {
+ if (config.minimumLogLevel > Log.ERROR)
+ return 0;
+
+ final String s = Strings.toString(s1);
+ final String message = args.length > 0 ? String.format(s, args) : s;
+ return print.println(Log.ERROR, message);
+ }
+
+ public static int e(Throwable throwable, Object s1, Object... args) {
+ if (config.minimumLogLevel > Log.ERROR)
+ return 0;
+
+ final String s = Strings.toString(s1);
+ final String message = (args.length > 0 ? String.format(s, args) : s) + '\n' +
+ Log.getStackTraceString(throwable);
+ return print.println(Log.ERROR, message);
+ }
+
+ public static boolean isDebugEnabled() {
+ return config.minimumLogLevel <= Log.DEBUG;
+ }
+
+ public static boolean isVerboseEnabled() {
+ return config.minimumLogLevel <= Log.VERBOSE;
+ }
+
+ public static Config getConfig() {
+ return config;
+ }
+
+
+ public static interface Config {
+ public int getLoggingLevel();
+
+ public void setLoggingLevel(int level);
+ }
+
+ public static class BaseConfig implements Config {
+ protected int minimumLogLevel = Log.VERBOSE;
+ protected String packageName = "";
+ protected String scope = "";
+
+ protected BaseConfig() {
+ }
+
+ @Inject
+ public BaseConfig(Application context) {
+ try {
+ packageName = context.getPackageName();
+ final int flags = context.getPackageManager()
+ .getApplicationInfo(packageName, 0).flags;
+ minimumLogLevel = (flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0
+ ? Log.VERBOSE : Log.INFO;
+ scope = packageName.toUpperCase();
+
+ Ln.d("Configuring Logging, minimum log level is %s",
+ logLevelToString(minimumLogLevel));
+
+ } catch (Exception e) {
+ try {
+ Log.e(packageName, "Error configuring logger", e);
+ } catch (RuntimeException f) {
+ // HACK ignore Stub! errors in mock objects during testing
+ }
+ }
+ }
+
+ public int getLoggingLevel() {
+ return minimumLogLevel;
+ }
+
+ public void setLoggingLevel(int level) {
+ minimumLogLevel = level;
+ }
+ }
+
+ public static String logLevelToString(int loglevel) {
+ switch (loglevel) {
+ case Log.VERBOSE:
+ return "VERBOSE";
+ case Log.DEBUG:
+ return "DEBUG";
+ case Log.INFO:
+ return "INFO";
+ case Log.WARN:
+ return "WARN";
+ case Log.ERROR:
+ return "ERROR";
+ case Log.ASSERT:
+ return "ASSERT";
+ }
+
+ return "UNKNOWN";
+ }
+
+
+ /**
+ * Default implementation logs to android.util.Log
+ */
+ public static class Print {
+ public int println(int priority, String msg) {
+ return Log.println(priority, getScope(5), processMessage(msg));
+ }
+
+ protected String processMessage(String msg) {
+ if (config.minimumLogLevel <= Log.DEBUG)
+ msg = String.format("%s %s", Thread.currentThread().getName(), msg);
+ return msg;
+ }
+
+ protected static String getScope(int skipDepth) {
+ if (config.minimumLogLevel <= Log.DEBUG) {
+ final StackTraceElement trace = Thread.currentThread().getStackTrace()[skipDepth];
+ return config.scope + "/" + trace.getFileName() + ":" + trace.getLineNumber();
+ }
+
+ return config.scope;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/util/SafeAsyncTask.java b/app/src/main/java/com/donnfelker/android/bootstrap/util/SafeAsyncTask.java
new file mode 100644
index 0000000..1ec0ff6
--- /dev/null
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/util/SafeAsyncTask.java
@@ -0,0 +1,304 @@
+package com.donnfelker.android.bootstrap.util;
+
+import android.os.Handler;
+import android.os.Looper;
+
+import java.io.InterruptedIOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.FutureTask;
+
+/**
+ * Originally from RoboGuice:
+ * https://github.com/roboguice/roboguice/blob/master/roboguice/src/main/java/roboguice/util/SafeAsyncTask.java
+ *
+ * A class similar but unrelated to android's {@link android.os.AsyncTask}.
+ *
+ * Unlike AsyncTask, this class properly propagates exceptions.
+ *
+ * If you're familiar with AsyncTask and are looking for {@link android.os.AsyncTask#doInBackground(Object[])},
+ * we've named it {@link #call()} here to conform with java 1.5's {@link java.util.concurrent.Callable} interface.
+ *
+ * Current limitations: does not yet handle progress, although it shouldn't be
+ * hard to add.
+ *
+ * If using your own executor, you must call future() to get a runnable you can execute.
+ *
+ * @param
+ */
+public abstract class SafeAsyncTask implements Callable {
+ public static final int DEFAULT_POOL_SIZE = 25;
+ protected static final Executor DEFAULT_EXECUTOR = Executors.newFixedThreadPool(DEFAULT_POOL_SIZE);
+
+ protected Handler handler;
+ protected Executor executor;
+ protected StackTraceElement[] launchLocation;
+ protected FutureTask future;
+
+
+ /**
+ * Sets executor to Executors.newFixedThreadPool(DEFAULT_POOL_SIZE) and
+ * Handler to new Handler()
+ */
+ public SafeAsyncTask() {
+ this.executor = DEFAULT_EXECUTOR;
+ }
+
+ /**
+ * Sets executor to Executors.newFixedThreadPool(DEFAULT_POOL_SIZE)
+ */
+ public SafeAsyncTask(Handler handler) {
+ this.handler = handler;
+ this.executor = DEFAULT_EXECUTOR;
+ }
+
+ /**
+ * Sets Handler to new Handler()
+ */
+ public SafeAsyncTask(Executor executor) {
+ this.executor = executor;
+ }
+
+ public SafeAsyncTask(Handler handler, Executor executor) {
+ this.handler = handler;
+ this.executor = executor;
+ }
+
+
+ public FutureTask future() {
+ future = new FutureTask(newTask());
+ return future;
+ }
+
+ public SafeAsyncTask executor(Executor executor) {
+ this.executor = executor;
+ return this;
+ }
+
+ public Executor executor() {
+ return executor;
+ }
+
+ public SafeAsyncTask handler(Handler handler) {
+ this.handler = handler;
+ return this;
+ }
+
+ public Handler handler() {
+ return handler;
+ }
+
+ public void execute() {
+ execute(Thread.currentThread().getStackTrace());
+ }
+
+ protected void execute(StackTraceElement[] launchLocation) {
+ this.launchLocation = launchLocation;
+ executor.execute(future());
+ }
+
+ public boolean cancel(boolean mayInterruptIfRunning) {
+ if (future == null)
+ throw new UnsupportedOperationException("You cannot cancel this task before calling future()");
+
+ return future.cancel(mayInterruptIfRunning);
+ }
+
+
+ /**
+ * @throws Exception, captured on passed to onException() if present.
+ */
+ protected void onPreExecute() throws Exception {
+ }
+
+ /**
+ * @param t the result of {@link #call()}
+ * @throws Exception, captured on passed to onException() if present.
+ */
+ @SuppressWarnings({"UnusedDeclaration"})
+ protected void onSuccess(ResultT t) throws Exception {
+ }
+
+ /**
+ * Called when the thread has been interrupted, likely because
+ * the task was canceled.
+ *
+ * By default, calls {@link #onException(Exception)}, but this method
+ * may be overridden to handle interruptions differently than other
+ * exceptions.
+ *
+ * @param e an InterruptedException or InterruptedIOException
+ */
+ protected void onInterrupted(Exception e) {
+ onException(e);
+ }
+
+ /**
+ * Logs the exception as an Error by default, but this method may
+ * be overridden by subclasses.
+ *
+ * @param e the exception thrown from {@link #onPreExecute()}, {@link #call()}, or {@link #onSuccess(Object)}
+ * @throws RuntimeException, ignored
+ */
+ protected void onException(Exception e) throws RuntimeException {
+ onThrowable(e);
+ }
+
+ protected void onThrowable(Throwable t) throws RuntimeException {
+ Ln.e(t, "Throwable caught during background processing");
+ }
+
+ /**
+ * @throws RuntimeException, ignored
+ */
+ protected void onFinally() throws RuntimeException {
+ }
+
+
+ protected Task newTask() {
+ return new Task(this);
+ }
+
+
+ public static class Task implements Callable {
+ protected final SafeAsyncTask parent;
+ protected final Handler handler;
+
+ public Task(SafeAsyncTask parent) {
+ this.parent = parent;
+ this.handler = parent.handler != null ? parent.handler : new Handler(Looper.getMainLooper());
+ }
+
+ public Void call() throws Exception {
+ try {
+ doPreExecute();
+ doSuccess(doCall());
+
+ } catch (final Exception e) {
+ try {
+ doException(e);
+ } catch (Exception f) {
+ // logged but ignored
+ Ln.e(f);
+ }
+
+ } catch (final Throwable t) {
+ try {
+ doThrowable(t);
+ } catch (Exception f) {
+ // logged but ignored
+ Ln.e(f);
+ }
+ } finally {
+ doFinally();
+ }
+
+ return null;
+ }
+
+ protected void doPreExecute() throws Exception {
+ postToUiThreadAndWait(new Callable() {
+ public Object call() throws Exception {
+ parent.onPreExecute();
+ return null;
+ }
+ });
+ }
+
+ protected ResultT doCall() throws Exception {
+ return parent.call();
+ }
+
+ protected void doSuccess(final ResultT r) throws Exception {
+ postToUiThreadAndWait(new Callable() {
+ public Object call() throws Exception {
+ parent.onSuccess(r);
+ return null;
+ }
+ });
+ }
+
+ protected void doException(final Exception e) throws Exception {
+ if (parent.launchLocation != null) {
+ final ArrayList stack = new ArrayList(Arrays.asList(e.getStackTrace()));
+ stack.addAll(Arrays.asList(parent.launchLocation));
+ e.setStackTrace(stack.toArray(new StackTraceElement[stack.size()]));
+ }
+ postToUiThreadAndWait(new Callable() {
+ public Object call() throws Exception {
+ if (e instanceof InterruptedException || e instanceof InterruptedIOException)
+ parent.onInterrupted(e);
+ else
+ parent.onException(e);
+ return null;
+ }
+ });
+ }
+
+ protected void doThrowable(final Throwable e) throws Exception {
+ if (parent.launchLocation != null) {
+ final ArrayList stack = new ArrayList(Arrays.asList(e.getStackTrace()));
+ stack.addAll(Arrays.asList(parent.launchLocation));
+ e.setStackTrace(stack.toArray(new StackTraceElement[stack.size()]));
+ }
+ postToUiThreadAndWait(new Callable() {
+ public Object call() throws Exception {
+ parent.onThrowable(e);
+ return null;
+ }
+ });
+ }
+
+ protected void doFinally() throws Exception {
+ postToUiThreadAndWait(new Callable() {
+ public Object call() throws Exception {
+ parent.onFinally();
+ return null;
+ }
+ });
+ }
+
+
+ /**
+ * Posts the specified runnable to the UI thread using a handler,
+ * and waits for operation to finish. If there's an exception,
+ * it captures it and rethrows it.
+ *
+ * @param c the callable to post
+ * @throws Exception on error
+ */
+ protected void postToUiThreadAndWait(final Callable c) throws Exception {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final Exception[] exceptions = new Exception[1];
+
+ // Execute onSuccess in the UI thread, but wait
+ // for it to complete.
+ // If it throws an exception, capture that exception
+ // and rethrow it later.
+ handler.post(new Runnable() {
+ public void run() {
+ try {
+ c.call();
+ } catch (Exception e) {
+ exceptions[0] = e;
+ } finally {
+ latch.countDown();
+ }
+ }
+ });
+
+ // Wait for onSuccess to finish
+ latch.await();
+
+ if (exceptions[0] != null)
+ throw exceptions[0];
+
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/util/Strings.java b/app/src/main/java/com/donnfelker/android/bootstrap/util/Strings.java
new file mode 100644
index 0000000..6c9ba97
--- /dev/null
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/util/Strings.java
@@ -0,0 +1,197 @@
+package com.donnfelker.android.bootstrap.util;
+
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.security.InvalidParameterException;
+import java.security.MessageDigest;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+public class Strings {
+ private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
+
+ /**
+ * Originally from RoboGuice:
+ * https://github.com/roboguice/roboguice/blob/master/roboguice/src/main/java/roboguice/util/Strings.java
+ * Like join, but allows for a distinct final delimiter. For english sentences such
+ * as "Alice, Bob and Charlie" use ", " and " and " as the delimiters.
+ *
+ * @param delimiter usually ", "
+ * @param lastDelimiter usually " and "
+ * @param objs the objects
+ * @param the type
+ * @return a string
+ */
+ public static String joinAnd(final String delimiter, final String lastDelimiter,
+ final Collection objs) {
+ if (objs == null || objs.isEmpty())
+ return "";
+
+ final Iterator iter = objs.iterator();
+ final StringBuilder buffer = new StringBuilder(Strings.toString(iter.next()));
+ int i = 1;
+ while (iter.hasNext()) {
+ final T obj = iter.next();
+ if (notEmpty(obj))
+ buffer.append(++i == objs.size() ? lastDelimiter : delimiter).
+ append(Strings.toString(obj));
+ }
+ return buffer.toString();
+ }
+
+ public static String joinAnd(final String delimiter, final String lastDelimiter,
+ final T... objs) {
+ return joinAnd(delimiter, lastDelimiter, Arrays.asList(objs));
+ }
+
+ public static String join(final String delimiter, final Collection objs) {
+ if (objs == null || objs.isEmpty())
+ return "";
+
+ final Iterator iter = objs.iterator();
+ final StringBuilder buffer = new StringBuilder(Strings.toString(iter.next()));
+
+ while (iter.hasNext()) {
+ final T obj = iter.next();
+ if (notEmpty(obj)) buffer.append(delimiter).append(Strings.toString(obj));
+ }
+ return buffer.toString();
+ }
+
+ public static String join(final String delimiter, final T... objects) {
+ return join(delimiter, Arrays.asList(objects));
+ }
+
+ public static String toString(InputStream input) {
+ StringWriter sw = new StringWriter();
+ copy(new InputStreamReader(input), sw);
+ return sw.toString();
+ }
+
+ public static String toString(Reader input) {
+ StringWriter sw = new StringWriter();
+ copy(input, sw);
+ return sw.toString();
+ }
+
+ public static int copy(Reader input, Writer output) {
+ long count = copyLarge(input, output);
+ return count > Integer.MAX_VALUE ? -1 : (int) count;
+ }
+
+ public static long copyLarge(Reader input, Writer output) throws RuntimeException {
+ try {
+ char[] buffer = new char[DEFAULT_BUFFER_SIZE];
+ long count = 0;
+ int n;
+ while (-1 != (n = input.read(buffer))) {
+ output.write(buffer, 0, n);
+ count += n;
+ }
+ return count;
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static String toString(final Object o) {
+ return toString(o, "");
+ }
+
+ public static String toString(final Object o, final String def) {
+ return o == null ? def :
+ o instanceof InputStream ? toString((InputStream) o) :
+ o instanceof Reader ? toString((Reader) o) :
+ o instanceof Object[] ? Strings.join(", ", (Object[]) o) :
+ o instanceof Collection ? Strings.join(", ", (Collection>) o) : o.toString();
+ }
+
+ public static boolean isEmpty(final Object o) {
+ return toString(o).trim().length() == 0;
+ }
+
+ public static boolean notEmpty(final Object o) {
+ return toString(o).trim().length() != 0;
+ }
+
+ public static String md5(String s) {
+ // http://stackoverflow.com/questions/1057041/difference-between-java-and-php5-md5-hash
+ // http://code.google.com/p/roboguice/issues/detail?id=89
+ try {
+
+ final byte[] hash = MessageDigest.getInstance("MD5").digest(s.getBytes("UTF-8"));
+ final StringBuilder hashString = new StringBuilder();
+
+ for (byte aHash : hash) {
+ String hex = Integer.toHexString(aHash);
+
+ if (hex.length() == 1) {
+ hashString.append('0');
+ hashString.append(hex.charAt(hex.length() - 1));
+ } else {
+ hashString.append(hex.substring(hex.length() - 2));
+ }
+ }
+
+ return hashString.toString();
+
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static String capitalize(String s) {
+ final String c = Strings.toString(s);
+ return c.length() >= 2 ? c.substring(0, 1).toUpperCase() + c.substring(1) :
+ c.length() >= 1 ? c.toUpperCase() : c;
+ }
+
+ public static boolean equals(Object a, Object b) {
+ return Strings.toString(a).equals(Strings.toString(b));
+ }
+
+ public static boolean equalsIgnoreCase(Object a, Object b) {
+ return Strings.toString(a).toLowerCase().equals(Strings.toString(b).toLowerCase());
+ }
+
+ public static String[] chunk(String str, int chunkSize) {
+ if (isEmpty(str) || chunkSize == 0)
+ return new String[0];
+
+ final int len = str.length();
+ final int arrayLen = ((len - 1) / chunkSize) + 1;
+ final String[] array = new String[arrayLen];
+ for (int i = 0; i < arrayLen; ++i)
+ array[i] = str.substring(i * chunkSize, (i * chunkSize) + chunkSize < len
+ ? (i * chunkSize) + chunkSize : len);
+
+ return array;
+ }
+
+ public static String namedFormat(String str, Map substitutions) {
+ for (String key : substitutions.keySet())
+ str = str.replace('$' + key, substitutions.get(key));
+
+ return str;
+ }
+
+ public static String namedFormat(String str, Object... nameValuePairs) {
+ if (nameValuePairs.length % 2 != 0)
+ throw new InvalidParameterException("You must include one value for each parameter");
+
+ final HashMap map = new HashMap(nameValuePairs.length / 2);
+ for (int i = 0; i < nameValuePairs.length; i += 2)
+ map.put(Strings.toString(nameValuePairs[i]), Strings.toString(nameValuePairs[i + 1]));
+
+ return namedFormat(str, map);
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/donnfelker/android/bootstrap/util/UIUtils.java b/app/src/main/java/com/donnfelker/android/bootstrap/util/UIUtils.java
new file mode 100644
index 0000000..f80afe2
--- /dev/null
+++ b/app/src/main/java/com/donnfelker/android/bootstrap/util/UIUtils.java
@@ -0,0 +1,19 @@
+package com.donnfelker.android.bootstrap.util;
+
+import android.content.Context;
+import android.content.res.Configuration;
+
+public class UIUtils {
+
+ /**
+ * Helps determine if the app is running in a Tablet context.
+ *
+ * @param context
+ * @return
+ */
+ public static boolean isTablet(Context context) {
+ return (context.getResources().getConfiguration().screenLayout
+ & Configuration.SCREENLAYOUT_SIZE_MASK)
+ >= Configuration.SCREENLAYOUT_SIZE_LARGE;
+ }
+}
diff --git a/app/src/main/res/color/nav_text_selector.xml b/app/src/main/res/color/nav_text_selector.xml
new file mode 100644
index 0000000..2fcef81
--- /dev/null
+++ b/app/src/main/res/color/nav_text_selector.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/res/color/table_text_light_selector.xml b/app/src/main/res/color/table_text_light_selector.xml
similarity index 100%
rename from app/res/color/table_text_light_selector.xml
rename to app/src/main/res/color/table_text_light_selector.xml
diff --git a/app/res/color/table_text_selector.xml b/app/src/main/res/color/table_text_selector.xml
similarity index 100%
rename from app/res/color/table_text_selector.xml
rename to app/src/main/res/color/table_text_selector.xml
diff --git a/app/res/color/text_light_selector.xml b/app/src/main/res/color/text_light_selector.xml
similarity index 100%
rename from app/res/color/text_light_selector.xml
rename to app/src/main/res/color/text_light_selector.xml
diff --git a/app/res/color/text_selector.xml b/app/src/main/res/color/text_selector.xml
similarity index 100%
rename from app/res/color/text_selector.xml
rename to app/src/main/res/color/text_selector.xml
diff --git a/app/res/color/text_title_selector.xml b/app/src/main/res/color/text_title_selector.xml
similarity index 100%
rename from app/res/color/text_title_selector.xml
rename to app/src/main/res/color/text_title_selector.xml
diff --git a/app/src/main/res/drawable-hdpi-v11/ic_stat_ab_notification.png b/app/src/main/res/drawable-hdpi-v11/ic_stat_ab_notification.png
new file mode 100755
index 0000000..3b28155
Binary files /dev/null and b/app/src/main/res/drawable-hdpi-v11/ic_stat_ab_notification.png differ
diff --git a/app/src/main/res/drawable-hdpi-v9/ic_stat_ab_notification.png b/app/src/main/res/drawable-hdpi-v9/ic_stat_ab_notification.png
new file mode 100755
index 0000000..72d24f6
Binary files /dev/null and b/app/src/main/res/drawable-hdpi-v9/ic_stat_ab_notification.png differ
diff --git a/app/res/drawable-hdpi/ic_action_refresh.png b/app/src/main/res/drawable-hdpi/ic_action_refresh.png
similarity index 100%
rename from app/res/drawable-hdpi/ic_action_refresh.png
rename to app/src/main/res/drawable-hdpi/ic_action_refresh.png
diff --git a/app/src/main/res/drawable-hdpi/ic_action_timer.png b/app/src/main/res/drawable-hdpi/ic_action_timer.png
new file mode 100755
index 0000000..dda32bd
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_timer.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_drawer.png b/app/src/main/res/drawable-hdpi/ic_drawer.png
new file mode 100644
index 0000000..ec8c51f
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_drawer.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_home.png b/app/src/main/res/drawable-hdpi/ic_home.png
new file mode 100755
index 0000000..eb11224
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_home.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_stat_ab_notification.png b/app/src/main/res/drawable-hdpi/ic_stat_ab_notification.png
new file mode 100755
index 0000000..d3d4d39
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_stat_ab_notification.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_timer.png b/app/src/main/res/drawable-hdpi/ic_timer.png
new file mode 100755
index 0000000..0febe1d
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_timer.png differ
diff --git a/app/res/drawable-hdpi/icon.png b/app/src/main/res/drawable-hdpi/icon.png
similarity index 100%
rename from app/res/drawable-hdpi/icon.png
rename to app/src/main/res/drawable-hdpi/icon.png
diff --git a/app/src/main/res/drawable-ldpi-v11/ic_stat_ab_notification.png b/app/src/main/res/drawable-ldpi-v11/ic_stat_ab_notification.png
new file mode 100755
index 0000000..b8685af
Binary files /dev/null and b/app/src/main/res/drawable-ldpi-v11/ic_stat_ab_notification.png differ
diff --git a/app/src/main/res/drawable-ldpi-v9/ic_stat_ab_notification.png b/app/src/main/res/drawable-ldpi-v9/ic_stat_ab_notification.png
new file mode 100755
index 0000000..944778f
Binary files /dev/null and b/app/src/main/res/drawable-ldpi-v9/ic_stat_ab_notification.png differ
diff --git a/app/res/drawable-ldpi/ic_action_refresh.png b/app/src/main/res/drawable-ldpi/ic_action_refresh.png
similarity index 100%
rename from app/res/drawable-ldpi/ic_action_refresh.png
rename to app/src/main/res/drawable-ldpi/ic_action_refresh.png
diff --git a/app/res/drawable-ldpi/icon.png b/app/src/main/res/drawable-ldpi/icon.png
similarity index 100%
rename from app/res/drawable-ldpi/icon.png
rename to app/src/main/res/drawable-ldpi/icon.png
diff --git a/app/src/main/res/drawable-mdpi-v11/ic_stat_ab_notification.png b/app/src/main/res/drawable-mdpi-v11/ic_stat_ab_notification.png
new file mode 100755
index 0000000..d4087e5
Binary files /dev/null and b/app/src/main/res/drawable-mdpi-v11/ic_stat_ab_notification.png differ
diff --git a/app/src/main/res/drawable-mdpi-v9/ic_stat_ab_notification.png b/app/src/main/res/drawable-mdpi-v9/ic_stat_ab_notification.png
new file mode 100755
index 0000000..6011074
Binary files /dev/null and b/app/src/main/res/drawable-mdpi-v9/ic_stat_ab_notification.png differ
diff --git a/app/res/drawable-mdpi/ic_action_refresh.png b/app/src/main/res/drawable-mdpi/ic_action_refresh.png
similarity index 100%
rename from app/res/drawable-mdpi/ic_action_refresh.png
rename to app/src/main/res/drawable-mdpi/ic_action_refresh.png
diff --git a/app/src/main/res/drawable-mdpi/ic_action_timer.png b/app/src/main/res/drawable-mdpi/ic_action_timer.png
new file mode 100755
index 0000000..475b0c6
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_action_timer.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_drawer.png b/app/src/main/res/drawable-mdpi/ic_drawer.png
new file mode 100644
index 0000000..7420a68
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_drawer.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_home.png b/app/src/main/res/drawable-mdpi/ic_home.png
new file mode 100755
index 0000000..ad925f8
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_home.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_stat_ab_notification.png b/app/src/main/res/drawable-mdpi/ic_stat_ab_notification.png
new file mode 100755
index 0000000..d763f38
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_stat_ab_notification.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_timer.png b/app/src/main/res/drawable-mdpi/ic_timer.png
new file mode 100755
index 0000000..74037a2
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_timer.png differ
diff --git a/app/res/drawable-mdpi/icon.png b/app/src/main/res/drawable-mdpi/icon.png
similarity index 100%
rename from app/res/drawable-mdpi/icon.png
rename to app/src/main/res/drawable-mdpi/icon.png
diff --git a/app/res/drawable-nodpi/gravatar_icon.png b/app/src/main/res/drawable-nodpi/gravatar_icon.png
similarity index 100%
rename from app/res/drawable-nodpi/gravatar_icon.png
rename to app/src/main/res/drawable-nodpi/gravatar_icon.png
diff --git a/app/res/drawable-nodpi/spinner_inner.png b/app/src/main/res/drawable-nodpi/spinner_inner.png
similarity index 100%
rename from app/res/drawable-nodpi/spinner_inner.png
rename to app/src/main/res/drawable-nodpi/spinner_inner.png
diff --git a/app/res/drawable-nodpi/spinner_outer.png b/app/src/main/res/drawable-nodpi/spinner_outer.png
similarity index 100%
rename from app/res/drawable-nodpi/spinner_outer.png
rename to app/src/main/res/drawable-nodpi/spinner_outer.png
diff --git a/app/src/main/res/drawable-xhdpi-v11/ic_stat_ab_notification.png b/app/src/main/res/drawable-xhdpi-v11/ic_stat_ab_notification.png
new file mode 100755
index 0000000..b321d3a
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi-v11/ic_stat_ab_notification.png differ
diff --git a/app/src/main/res/drawable-xhdpi-v9/ic_stat_ab_notification.png b/app/src/main/res/drawable-xhdpi-v9/ic_stat_ab_notification.png
new file mode 100755
index 0000000..1f33587
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi-v9/ic_stat_ab_notification.png differ
diff --git a/app/res/drawable-xhdpi/ic_action_refresh.png b/app/src/main/res/drawable-xhdpi/ic_action_refresh.png
similarity index 100%
rename from app/res/drawable-xhdpi/ic_action_refresh.png
rename to app/src/main/res/drawable-xhdpi/ic_action_refresh.png
diff --git a/app/src/main/res/drawable-xhdpi/ic_action_timer.png b/app/src/main/res/drawable-xhdpi/ic_action_timer.png
new file mode 100755
index 0000000..1c2ca4f
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_timer.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_drawer.png b/app/src/main/res/drawable-xhdpi/ic_drawer.png
new file mode 100644
index 0000000..7ffbe5c
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_drawer.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_home.png b/app/src/main/res/drawable-xhdpi/ic_home.png
new file mode 100755
index 0000000..a7031e4
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_home.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_stat_ab_notification.png b/app/src/main/res/drawable-xhdpi/ic_stat_ab_notification.png
new file mode 100755
index 0000000..8d8c0e9
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_stat_ab_notification.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_timer.png b/app/src/main/res/drawable-xhdpi/ic_timer.png
new file mode 100755
index 0000000..0e15946
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_timer.png differ
diff --git a/app/res/drawable-xhdpi/icon.png b/app/src/main/res/drawable-xhdpi/icon.png
similarity index 100%
rename from app/res/drawable-xhdpi/icon.png
rename to app/src/main/res/drawable-xhdpi/icon.png
diff --git a/app/src/main/res/drawable-xxhdpi/ic_home.png b/app/src/main/res/drawable-xxhdpi/ic_home.png
new file mode 100755
index 0000000..547a4e1
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_home.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_timer.png b/app/src/main/res/drawable-xxhdpi/ic_timer.png
new file mode 100755
index 0000000..43ce820
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_timer.png differ
diff --git a/app/res/drawable/actionbar_background.xml b/app/src/main/res/drawable/actionbar_background.xml
similarity index 100%
rename from app/res/drawable/actionbar_background.xml
rename to app/src/main/res/drawable/actionbar_background.xml
diff --git a/app/res/drawable/bootstrap_divider.xml b/app/src/main/res/drawable/bootstrap_divider.xml
similarity index 100%
rename from app/res/drawable/bootstrap_divider.xml
rename to app/src/main/res/drawable/bootstrap_divider.xml
diff --git a/app/res/drawable/button_background_disabled.xml b/app/src/main/res/drawable/button_background_disabled.xml
similarity index 100%
rename from app/res/drawable/button_background_disabled.xml
rename to app/src/main/res/drawable/button_background_disabled.xml
diff --git a/app/res/drawable/button_background_enabled.xml b/app/src/main/res/drawable/button_background_enabled.xml
similarity index 100%
rename from app/res/drawable/button_background_enabled.xml
rename to app/src/main/res/drawable/button_background_enabled.xml
diff --git a/app/src/main/res/drawable/button_background_pressed.xml b/app/src/main/res/drawable/button_background_pressed.xml
new file mode 100644
index 0000000..6dcb77f
--- /dev/null
+++ b/app/src/main/res/drawable/button_background_pressed.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/res/drawable/button_background_states.xml b/app/src/main/res/drawable/button_background_states.xml
similarity index 73%
rename from app/res/drawable/button_background_states.xml
rename to app/src/main/res/drawable/button_background_states.xml
index 6aebde7..eafea6f 100644
--- a/app/res/drawable/button_background_states.xml
+++ b/app/src/main/res/drawable/button_background_states.xml
@@ -2,7 +2,8 @@
-
+
+
\ No newline at end of file
diff --git a/app/res/drawable/edit_text_background.xml b/app/src/main/res/drawable/edit_text_background.xml
similarity index 100%
rename from app/res/drawable/edit_text_background.xml
rename to app/src/main/res/drawable/edit_text_background.xml
diff --git a/app/res/drawable/edit_text_cursor.xml b/app/src/main/res/drawable/edit_text_cursor.xml
similarity index 100%
rename from app/res/drawable/edit_text_cursor.xml
rename to app/src/main/res/drawable/edit_text_cursor.xml
diff --git a/app/res/drawable/list_item_background.xml b/app/src/main/res/drawable/list_item_background.xml
similarity index 100%
rename from app/res/drawable/list_item_background.xml
rename to app/src/main/res/drawable/list_item_background.xml
diff --git a/app/res/drawable/main_background.xml b/app/src/main/res/drawable/main_background.xml
similarity index 100%
rename from app/res/drawable/main_background.xml
rename to app/src/main/res/drawable/main_background.xml
diff --git a/app/res/drawable/map_header_background.xml b/app/src/main/res/drawable/map_header_background.xml
similarity index 100%
rename from app/res/drawable/map_header_background.xml
rename to app/src/main/res/drawable/map_header_background.xml
diff --git a/app/src/main/res/drawable/nav_menu_button_background_disabled.xml b/app/src/main/res/drawable/nav_menu_button_background_disabled.xml
new file mode 100755
index 0000000..1045bb3
--- /dev/null
+++ b/app/src/main/res/drawable/nav_menu_button_background_disabled.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/nav_menu_button_background_enabled.xml b/app/src/main/res/drawable/nav_menu_button_background_enabled.xml
new file mode 100755
index 0000000..97384c3
--- /dev/null
+++ b/app/src/main/res/drawable/nav_menu_button_background_enabled.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/nav_menu_button_background_pressed.xml b/app/src/main/res/drawable/nav_menu_button_background_pressed.xml
new file mode 100755
index 0000000..b6813e6
--- /dev/null
+++ b/app/src/main/res/drawable/nav_menu_button_background_pressed.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/nav_menu_button_background_selector.xml b/app/src/main/res/drawable/nav_menu_button_background_selector.xml
new file mode 100755
index 0000000..f5a007b
--- /dev/null
+++ b/app/src/main/res/drawable/nav_menu_button_background_selector.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/res/drawable/spinner.xml b/app/src/main/res/drawable/spinner.xml
similarity index 100%
rename from app/res/drawable/spinner.xml
rename to app/src/main/res/drawable/spinner.xml
diff --git a/app/res/drawable/stripe.png b/app/src/main/res/drawable/stripe.png
similarity index 100%
rename from app/res/drawable/stripe.png
rename to app/src/main/res/drawable/stripe.png
diff --git a/app/res/drawable/stripe_repeat.xml b/app/src/main/res/drawable/stripe_repeat.xml
similarity index 100%
rename from app/res/drawable/stripe_repeat.xml
rename to app/src/main/res/drawable/stripe_repeat.xml
diff --git a/app/res/drawable/table_background_alternate_selector.xml b/app/src/main/res/drawable/table_background_alternate_selector.xml
similarity index 100%
rename from app/res/drawable/table_background_alternate_selector.xml
rename to app/src/main/res/drawable/table_background_alternate_selector.xml
diff --git a/app/res/drawable/table_background_selector.xml b/app/src/main/res/drawable/table_background_selector.xml
similarity index 100%
rename from app/res/drawable/table_background_selector.xml
rename to app/src/main/res/drawable/table_background_selector.xml
diff --git a/app/src/main/res/layout/bootstrap_timer.xml b/app/src/main/res/layout/bootstrap_timer.xml
new file mode 100644
index 0000000..5367a92
--- /dev/null
+++ b/app/src/main/res/layout/bootstrap_timer.xml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/res/layout/checkin_list_item.xml b/app/src/main/res/layout/checkin_list_item.xml
similarity index 100%
rename from app/res/layout/checkin_list_item.xml
rename to app/src/main/res/layout/checkin_list_item.xml
diff --git a/app/res/layout/checkins_list_item_labels.xml b/app/src/main/res/layout/checkins_list_item_labels.xml
similarity index 100%
rename from app/res/layout/checkins_list_item_labels.xml
rename to app/src/main/res/layout/checkins_list_item_labels.xml
diff --git a/app/src/main/res/layout/fragment_carousel.xml b/app/src/main/res/layout/fragment_carousel.xml
new file mode 100644
index 0000000..eb418d9
--- /dev/null
+++ b/app/src/main/res/layout/fragment_carousel.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_navigation_drawer.xml b/app/src/main/res/layout/fragment_navigation_drawer.xml
new file mode 100644
index 0000000..e934bed
--- /dev/null
+++ b/app/src/main/res/layout/fragment_navigation_drawer.xml
@@ -0,0 +1,10 @@
+
+
diff --git a/app/res/layout/item_list.xml b/app/src/main/res/layout/item_list.xml
similarity index 100%
rename from app/res/layout/item_list.xml
rename to app/src/main/res/layout/item_list.xml
diff --git a/app/res/layout/login_activity.xml b/app/src/main/res/layout/login_activity.xml
similarity index 100%
rename from app/res/layout/login_activity.xml
rename to app/src/main/res/layout/login_activity.xml
diff --git a/app/src/main/res/layout/main_activity.xml b/app/src/main/res/layout/main_activity.xml
new file mode 100644
index 0000000..d4d25a2
--- /dev/null
+++ b/app/src/main/res/layout/main_activity.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/main_activity_tablet.xml b/app/src/main/res/layout/main_activity_tablet.xml
new file mode 100644
index 0000000..cea9803
--- /dev/null
+++ b/app/src/main/res/layout/main_activity_tablet.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/res/layout/news.xml b/app/src/main/res/layout/news.xml
similarity index 100%
rename from app/res/layout/news.xml
rename to app/src/main/res/layout/news.xml
diff --git a/app/res/layout/news_list_item.xml b/app/src/main/res/layout/news_list_item.xml
similarity index 100%
rename from app/res/layout/news_list_item.xml
rename to app/src/main/res/layout/news_list_item.xml
diff --git a/app/res/layout/news_list_item_labels.xml b/app/src/main/res/layout/news_list_item_labels.xml
similarity index 100%
rename from app/res/layout/news_list_item_labels.xml
rename to app/src/main/res/layout/news_list_item_labels.xml
diff --git a/app/res/layout/user_list_item.xml b/app/src/main/res/layout/user_list_item.xml
similarity index 100%
rename from app/res/layout/user_list_item.xml
rename to app/src/main/res/layout/user_list_item.xml
diff --git a/app/res/layout/user_list_item_labels.xml b/app/src/main/res/layout/user_list_item_labels.xml
similarity index 100%
rename from app/res/layout/user_list_item_labels.xml
rename to app/src/main/res/layout/user_list_item_labels.xml
diff --git a/app/res/layout/user_view.xml b/app/src/main/res/layout/user_view.xml
similarity index 100%
rename from app/res/layout/user_view.xml
rename to app/src/main/res/layout/user_view.xml
diff --git a/app/res/menu/bootstrap.xml b/app/src/main/res/menu/bootstrap.xml
similarity index 52%
rename from app/res/menu/bootstrap.xml
rename to app/src/main/res/menu/bootstrap.xml
index 17f5d88..8edc541 100644
--- a/app/res/menu/bootstrap.xml
+++ b/app/src/main/res/menu/bootstrap.xml
@@ -1,18 +1,26 @@
-
+
-
+ -
+
+
-
diff --git a/app/src/main/res/menu/global.xml b/app/src/main/res/menu/global.xml
new file mode 100644
index 0000000..7c97a20
--- /dev/null
+++ b/app/src/main/res/menu/global.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/res/values/colors.xml b/app/src/main/res/values/colors.xml
similarity index 79%
rename from app/res/values/colors.xml
rename to app/src/main/res/values/colors.xml
index ea34552..d315190 100644
--- a/app/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -27,6 +27,8 @@
#2C2C2C
#1fb6ed
#1c9ac8
+ #f76500
+ #fb812c
#EFF6FF
#FBFDFF
#7b7b7b
@@ -34,5 +36,12 @@
#000000
#1fb6ed
+ #ffffff
+ #1fb6ed
+ #646464
+ #464646
+
+ #1f1f1f
+ #c2c2c2
\ No newline at end of file
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..fa96a04
--- /dev/null
+++ b/app/src/main/res/values/dimens.xml
@@ -0,0 +1,6 @@
+
+
+
+ 240dp
+
\ No newline at end of file
diff --git a/app/res/values/strings.xml b/app/src/main/res/values/strings.xml
similarity index 77%
rename from app/res/values/strings.xml
rename to app/src/main/res/values/strings.xml
index b7de1bf..0ad73be 100644
--- a/app/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -32,6 +32,17 @@
Email Address or Password is incorrect
Please enter a valid password
Please enter a valid email address & password
+ Timer is running
+ Timer is paused
+ Stop
+ Start
+ Pause
+ Resume
+ Timer
+ Home
+ Drawer Open
+ Drawer Close
+ Home
\ No newline at end of file
diff --git a/app/res/values/styles.xml b/app/src/main/res/values/styles.xml
similarity index 77%
rename from app/res/values/styles.xml
rename to app/src/main/res/values/styles.xml
index 157d00f..e8cc640 100644
--- a/app/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -74,4 +74,24 @@
- @drawable/list_item_background
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/res/values/theme.xml b/app/src/main/res/values/theme.xml
similarity index 67%
rename from app/res/values/theme.xml
rename to app/src/main/res/values/theme.xml
index e330dd9..74807f8 100644
--- a/app/res/values/theme.xml
+++ b/app/src/main/res/values/theme.xml
@@ -1,14 +1,14 @@
+
-
-
diff --git a/app/res/xml/authenticator.xml b/app/src/main/res/xml/authenticator.xml
similarity index 100%
rename from app/res/xml/authenticator.xml
rename to app/src/main/res/xml/authenticator.xml
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..495c503
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
diff --git a/default.properties b/default.properties
index a923a3f..37b6a1a 100644
--- a/default.properties
+++ b/default.properties
@@ -1,4 +1,4 @@
# A dummy file to force the Android Emulator Plugin to install SDK 15
# which we need for compiling against ABS 4.
-target=android-15
+target=android-16
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..8c0fb64
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..a006dad
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Sat Jun 07 14:10:10 MST 2014
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=http\://services.gradle.org/distributions/gradle-1.12-all.zip
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..91a7e26
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..aec9973
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/integration-tests/.classpath b/integration-tests/.classpath
deleted file mode 100644
index a7628f0..0000000
--- a/integration-tests/.classpath
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/integration-tests/.project b/integration-tests/.project
deleted file mode 100644
index 6dbf54a..0000000
--- a/integration-tests/.project
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
- com.donnfelker.android.bootstrap.tests
-
-
- com.donnfelker.android.bootstrap
-
-
-
- com.android.ide.eclipse.adt.ResourceManagerBuilder
-
-
-
-
- com.android.ide.eclipse.adt.PreCompilerBuilder
-
-
-
-
- org.eclipse.jdt.core.javabuilder
-
-
-
-
- com.android.ide.eclipse.adt.ApkBuilder
-
-
-
-
-
- com.android.ide.eclipse.adt.AndroidNature
- org.eclipse.jdt.core.javanature
-
-
diff --git a/integration-tests/.settings/org.eclipse.jdt.core.prefs b/integration-tests/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index 5c91302..0000000
--- a/integration-tests/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,291 +0,0 @@
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.debug.lineNumber=generate
-org.eclipse.jdt.core.compiler.debug.localVariable=generate
-org.eclipse.jdt.core.compiler.debug.sourceFile=generate
-org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
-org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
-org.eclipse.jdt.core.compiler.source=1.6
-org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_assignment=0
-org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
-org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80
-org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0
-org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
-org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0
-org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80
-org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16
-org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
-org.eclipse.jdt.core.formatter.blank_lines_after_package=1
-org.eclipse.jdt.core.formatter.blank_lines_before_field=0
-org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
-org.eclipse.jdt.core.formatter.blank_lines_before_imports=1
-org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1
-org.eclipse.jdt.core.formatter.blank_lines_before_method=1
-org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
-org.eclipse.jdt.core.formatter.blank_lines_before_package=0
-org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
-org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1
-org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
-org.eclipse.jdt.core.formatter.comment.format_block_comments=true
-org.eclipse.jdt.core.formatter.comment.format_header=false
-org.eclipse.jdt.core.formatter.comment.format_html=true
-org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
-org.eclipse.jdt.core.formatter.comment.format_line_comments=true
-org.eclipse.jdt.core.formatter.comment.format_source_code=true
-org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true
-org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
-org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
-org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert
-org.eclipse.jdt.core.formatter.comment.line_length=80
-org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true
-org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true
-org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false
-org.eclipse.jdt.core.formatter.compact_else_if=true
-org.eclipse.jdt.core.formatter.continuation_indentation=2
-org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
-org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off
-org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on
-org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
-org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
-org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_empty_lines=false
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false
-org.eclipse.jdt.core.formatter.indentation.size=4
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert
-org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
-org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.join_lines_in_comments=true
-org.eclipse.jdt.core.formatter.join_wrapped_lines=true
-org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
-org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false
-org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.lineSplit=80
-org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
-org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1
-org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true
-org.eclipse.jdt.core.formatter.tabulation.char=space
-org.eclipse.jdt.core.formatter.tabulation.size=4
-org.eclipse.jdt.core.formatter.use_on_off_tags=false
-org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
-org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
-org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true
-org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true
diff --git a/integration-tests/.settings/org.eclipse.jdt.ui.prefs b/integration-tests/.settings/org.eclipse.jdt.ui.prefs
deleted file mode 100644
index a6bfc93..0000000
--- a/integration-tests/.settings/org.eclipse.jdt.ui.prefs
+++ /dev/null
@@ -1,56 +0,0 @@
-#Fri Jan 27 15:21:39 PST 2012
-eclipse.preferences.version=1
-editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
-formatter_settings_version=12
-sp_cleanup.add_default_serial_version_id=true
-sp_cleanup.add_generated_serial_version_id=false
-sp_cleanup.add_missing_annotations=false
-sp_cleanup.add_missing_deprecated_annotations=true
-sp_cleanup.add_missing_methods=false
-sp_cleanup.add_missing_nls_tags=false
-sp_cleanup.add_missing_override_annotations=true
-sp_cleanup.add_missing_override_annotations_interface_methods=false
-sp_cleanup.add_serial_version_id=false
-sp_cleanup.always_use_blocks=true
-sp_cleanup.always_use_parentheses_in_expressions=false
-sp_cleanup.always_use_this_for_non_static_field_access=false
-sp_cleanup.always_use_this_for_non_static_method_access=false
-sp_cleanup.convert_to_enhanced_for_loop=false
-sp_cleanup.correct_indentation=false
-sp_cleanup.format_source_code=false
-sp_cleanup.format_source_code_changes_only=false
-sp_cleanup.make_local_variable_final=false
-sp_cleanup.make_parameters_final=false
-sp_cleanup.make_private_fields_final=true
-sp_cleanup.make_type_abstract_if_missing_method=false
-sp_cleanup.make_variable_declarations_final=false
-sp_cleanup.never_use_blocks=false
-sp_cleanup.never_use_parentheses_in_expressions=true
-sp_cleanup.on_save_use_additional_actions=true
-sp_cleanup.organize_imports=false
-sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
-sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
-sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
-sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
-sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
-sp_cleanup.remove_private_constructors=true
-sp_cleanup.remove_trailing_whitespaces=true
-sp_cleanup.remove_trailing_whitespaces_all=true
-sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
-sp_cleanup.remove_unnecessary_casts=false
-sp_cleanup.remove_unnecessary_nls_tags=false
-sp_cleanup.remove_unused_imports=false
-sp_cleanup.remove_unused_local_variables=false
-sp_cleanup.remove_unused_private_fields=true
-sp_cleanup.remove_unused_private_members=false
-sp_cleanup.remove_unused_private_methods=true
-sp_cleanup.remove_unused_private_types=true
-sp_cleanup.sort_members=false
-sp_cleanup.sort_members_all=false
-sp_cleanup.use_blocks=false
-sp_cleanup.use_blocks_only_for_return_and_throw=false
-sp_cleanup.use_parentheses_in_expressions=false
-sp_cleanup.use_this_for_non_static_field_access=false
-sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
-sp_cleanup.use_this_for_non_static_method_access=false
-sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
diff --git a/integration-tests/build.gradle b/integration-tests/build.gradle
new file mode 100644
index 0000000..e69de29
diff --git a/integration-tests/default.properties b/integration-tests/default.properties
index 31ff39a..3411c80 100644
--- a/integration-tests/default.properties
+++ b/integration-tests/default.properties
@@ -1,4 +1,2 @@
-
-
# Project target.
-target=android-15
+target=android-18
diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml
deleted file mode 100644
index 518cb4e..0000000
--- a/integration-tests/pom.xml
+++ /dev/null
@@ -1,86 +0,0 @@
-
-
-
-
- 4.0.0
-
- android-bootstrap-integration-tests
- Android-Bootstrap integration tests
- apk
-
-
- 1.4
- com.donnfelker.android.bootstrap
- android-bootstrap-parent
-
-
-
-
- com.google.android
- android
- provided
- ${android.version}
-
-
- com.google.android
- android-test
- provided
- ${android.version}
-
-
- com.donnfelker.android.bootstrap
- android-bootstrap
- ${project.version}
- provided
- apk
-
-
- com.donnfelker.android.bootstrap
- android-bootstrap
- ${project.version}
- provided
- jar
-
-
-
-
-
-
- ${project.basedir}/res
- true
- ${project.build.directory}/filtered-res
-
- **/*.xml
-
-
-
- ${project.basedir}/res
- false
- ${project.build.directory}/filtered-res
-
- **/*.xml
-
-
-
-
-
- maven-resources-plugin
-
-
- initialize
-
- resources
-
-
-
-
-
- com.jayway.maven.plugins.android.generation2
- android-maven-plugin
-
- ${project.build.directory}/filtered-res
-
-
-
-
-
diff --git a/integration-tests/AndroidManifest.xml b/integration-tests/src/main/AndroidManifest.xml
similarity index 99%
rename from integration-tests/AndroidManifest.xml
rename to integration-tests/src/main/AndroidManifest.xml
index 4d7733b..3f9cdfc 100644
--- a/integration-tests/AndroidManifest.xml
+++ b/integration-tests/src/main/AndroidManifest.xml
@@ -2,7 +2,7 @@
-
+
diff --git a/integration-tests/src/main/java/com/donnfelker/android/bootstrap/test/TestUserAccountUtil.java b/integration-tests/src/main/java/com/donnfelker/android/bootstrap/test/TestUserAccountUtil.java
index f3c6c12..9c034b4 100644
--- a/integration-tests/src/main/java/com/donnfelker/android/bootstrap/test/TestUserAccountUtil.java
+++ b/integration-tests/src/main/java/com/donnfelker/android/bootstrap/test/TestUserAccountUtil.java
@@ -10,8 +10,8 @@
import static com.donnfelker.android.bootstrap.core.Constants.Auth.*;
import com.donnfelker.android.bootstrap.tests.R;
+import com.donnfelker.android.bootstrap.util.Ln;
-import roboguice.util.Ln;
/**
* Utilities for verifying an account
diff --git a/integration-tests/res/drawable-hdpi/ic_launcher.png b/integration-tests/src/main/res/drawable-hdpi/ic_launcher.png
similarity index 100%
rename from integration-tests/res/drawable-hdpi/ic_launcher.png
rename to integration-tests/src/main/res/drawable-hdpi/ic_launcher.png
diff --git a/integration-tests/res/drawable-ldpi/ic_launcher.png b/integration-tests/src/main/res/drawable-ldpi/ic_launcher.png
similarity index 100%
rename from integration-tests/res/drawable-ldpi/ic_launcher.png
rename to integration-tests/src/main/res/drawable-ldpi/ic_launcher.png
diff --git a/integration-tests/res/drawable-mdpi/ic_launcher.png b/integration-tests/src/main/res/drawable-mdpi/ic_launcher.png
similarity index 100%
rename from integration-tests/res/drawable-mdpi/ic_launcher.png
rename to integration-tests/src/main/res/drawable-mdpi/ic_launcher.png
diff --git a/integration-tests/res/layout/main.xml b/integration-tests/src/main/res/layout/main.xml
similarity index 100%
rename from integration-tests/res/layout/main.xml
rename to integration-tests/src/main/res/layout/main.xml
diff --git a/integration-tests/res/values/strings.xml b/integration-tests/src/main/res/values/strings.xml
similarity index 100%
rename from integration-tests/res/values/strings.xml
rename to integration-tests/src/main/res/values/strings.xml
diff --git a/integration-tests/res/values/test_account_credentials.xml b/integration-tests/src/main/res/values/test_account_credentials.xml
similarity index 100%
rename from integration-tests/res/values/test_account_credentials.xml
rename to integration-tests/src/main/res/values/test_account_credentials.xml
diff --git a/pom.xml b/pom.xml
deleted file mode 100644
index 6bdaf11..0000000
--- a/pom.xml
+++ /dev/null
@@ -1,72 +0,0 @@
-
-
-
-
- 4.0.0
-
- 1.4
- com.donnfelker.android.bootstrap
- android-bootstrap-parent
- Android Bootstrap parent
- pom
-
-
- https://github.com/donnfelker/android-bootstrap/issues
- GitHub Issues
-
-
-
-
- Apache License Version 2.0
- http://www.apache.org/licenses/LICENSE-2.0.html
- repo
-
-
-
-
- https://github.com/donnfelker/android-bootstrap
- scm:git:git://github.com/donnfelker/android-bootstrap.git
- scm:git:git@github.com:donnfelker/android-bootstrap.git
-
-
-
-
- donn@donnfelker.com
- Donn Felker
- https://github.com/donnfelker
- donnfelker
-
-
-
-
- app
- integration-tests
-
-
-
- UTF-8
- 4.1.1.4
-
-
-
-
-
- com.jayway.maven.plugins.android.generation2
- android-maven-plugin
- 3.3.0
- true
-
-
- 16
-
-
-
- false
- ../proguard.cfg
-
-
-
-
-
-
-
diff --git a/proguard.cfg b/proguard.cfg
index cd5c825..7f5c343 100644
--- a/proguard.cfg
+++ b/proguard.cfg
@@ -36,7 +36,6 @@
-keepclassmembers class * { @com.google.inject.Provides *; @android.test.suitebuilder.annotation.* *; void test*(...); }
--keep public class roboguice.**
-keep class com.google.inject.Binder
-keep class com.google.inject.Key
-keep class com.google.inject.Provider
@@ -56,4 +55,26 @@
*** startFinalizer(java.lang.Class,java.lang.Object);
}
--keepclassmembers class * extends com.actionbarsherlock.ActionBarSherlock { public (...); }
+-keepnames class * implements java.io.Serializable
+
+-keepclassmembers class * implements java.io.Serializable {
+ static final long serialVersionUID;
+ private static final java.io.ObjectStreamField[] serialPersistentFields;
+ !static !transient ;
+ !private ;
+ !private ;
+ private void writeObject(java.io.ObjectOutputStream);
+ private void readObject(java.io.ObjectInputStream);
+ java.lang.Object writeReplace();
+ java.lang.Object readResolve();
+}
+
+-keepclassmembers class ** {
+ @com.squareup.otto.Subscribe public *;
+ @com.squareup.otto.Produce public *;
+}
+
+-dontwarn butterknife.Views$InjectViewProcessor
+-keepclassmembers class **$$ViewInjector {*;}
+
+-dontwarn com.squareup.okhttp.**
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..b3f4d22
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,2 @@
+include ':app'
+// include ':integration-tests'