package kc.spark.pixels.android.ui; import static org.solemnsilence.util.Py.truthy; import kc.get.pixel.list.android.R; import kc.spark.pixels.android.app.DeviceState; import kc.spark.pixels.android.cloud.ApiFacade; import kc.spark.pixels.android.cloud.requestservice.ClearableIntentService; import kc.spark.pixels.android.cloud.requestservice.SimpleSparkApiService; import kc.spark.pixels.android.storage.Prefs; import kc.spark.pixels.android.ui.assets.Typefaces; import kc.spark.pixels.android.ui.assets.Typefaces.Style; import kc.spark.pixels.android.ui.corelist.CoreListActivity; import kc.spark.pixels.android.ui.util.Ui; import org.apache.commons.lang3.StringUtils; import org.solemnsilence.util.TLog; import org.solemnsilence.util.Toaster; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.app.ActionBar; import android.app.Activity; import android.app.AlertDialog; import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.res.Resources; import android.net.Uri; import android.os.Bundle; import android.support.v4.content.LocalBroadcastManager; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.TextView; public abstract class BaseActivity extends Activity { private static final TLog log = new TLog(BaseActivity.class); protected Prefs prefs; protected ApiFacade api; protected LocalBroadcastManager broadcastMgr; private ErrorsDelegate errorsDelegate; private LogOutReceiver logOutReceiver; private DevicesUpdatedReceiver devicesUpdatedReceiver; private boolean isLoggingOut = false; /** * In most of the activities in this project, the Up button should be * visible if there are any Cores assigned to this account, but should NOT * be visible otherwise. The exception is in {@link CoreListActivity}, which * is the top of the app's hierarchy, and as such should never show the Up * button. * * This method exists solely to easily provide the above behavior without a * lot of duplicated code. * * @return */ protected boolean shouldShowUpButtonWhenDevicesListNotEmpty() { return true; } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); prefs = Prefs.getInstance(); api = ApiFacade.getInstance(this); broadcastMgr = LocalBroadcastManager.getInstance(this); errorsDelegate = new ErrorsDelegate(this); logOutReceiver = new LogOutReceiver(); devicesUpdatedReceiver = new DevicesUpdatedReceiver(); if (getResources().getBoolean(R.bool.lock_to_portrait)) { setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); } } @Override protected void onPostCreate(Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); if (getActionBar() != null) { // This is really cheating, but the alternatives are all worse. int actionBarTitle = Resources.getSystem().getIdentifier("action_bar_title", "id", "android"); TextView titleView = (TextView) getWindow().findViewById(actionBarTitle); if (titleView != null) { titleView.setTypeface(Typefaces.getTypeface(this, Style.LIGHT)); titleView.setTextSize(21); titleView.setSingleLine(true); } // not generally a "best practice" to embed this kind of default // behavior in a common base class like this, but for this app's // needs, it works and appears to have no nasty side effects setCustomActionBarTitle(getString(R.string.app_name_lower)); } } @Override protected void onStart() { super.onStart(); // action bar not guaranteed to exist. ActionBar actionBar = getActionBar(); if (actionBar != null) { if (DeviceState.getKnownDevices().isEmpty()) { // no known devices; disallow Up nav, but at least request the // devices getActionBar().setDisplayHomeAsUpEnabled(false); getActionBar().setHomeButtonEnabled(false); api.requestAllDevices(); } CharSequence title = getActionBar().getTitle(); if (truthy(title)) { setCustomActionBarTitle(title); } } errorsDelegate.startListeningForErrors(); broadcastMgr.registerReceiver(logOutReceiver, logOutReceiver.getFilter()); broadcastMgr.registerReceiver(devicesUpdatedReceiver, devicesUpdatedReceiver.getFilter()); } @Override protected void onStop() { errorsDelegate.stopListeningForErrors(); broadcastMgr.unregisterReceiver(logOutReceiver); broadcastMgr.unregisterReceiver(devicesUpdatedReceiver); super.onStop(); } public void setCustomActionBarTitle(CharSequence title) { // TODO: remove this. // This doesn't do much anymore, but it's called all over the place, so // I'm leaving it here, at least for now. if (!truthy(title)) { log.w("Refusing to set action bar title to:'" + title + "'"); return; } getActionBar().setTitle(title); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.all_screens, menu); MenuItem logOutItem = menu.findItem(R.id.action_log_out); logOutItem.setTitle(logOutItem.getTitle() + " " + getEllipsizedUsername()); return true; } private String getEllipsizedUsername() { // this used to ellipsize using the proper unicode ellipsis character, // but that caused "funny character" issues, so now we do it the lame // but reliable way. return StringUtils.left(Prefs.getInstance().getUsername(), 12) + "..."; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.action_log_out: showLogOutConfirmation(); return true; case R.id.action_support: openUri(R.string.uri_support); return true; case R.id.action_spark_homepage: openUri(R.string.uri_spark_homepage); return true; case R.id.action_build_your_own_core_app: openUri(R.string.uri_build_your_own_app); return true; case R.id.action_documentation: openUri(R.string.uri_docs); return true; case R.id.action_contribute: openUri(R.string.uri_contribute); return true; case R.id.action_report_a_bug: openUri(R.string.uri_report_a_bug); return true; } return super.onOptionsItemSelected(item); } /** * Shows & hides the progress spinner and hides the login form. */ protected void showProgress(int viewId, final boolean show) { // Fade-in the progress spinner. int shortAnimTime = getResources().getInteger(android.R.integer.config_shortAnimTime); final View progressView = Ui.findView(this, viewId); progressView.setVisibility(View.VISIBLE); progressView.animate() .setDuration(shortAnimTime) .alpha(show ? 1 : 0) .setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { progressView.setVisibility(show ? View.VISIBLE : View.GONE); } }); } protected void openUri(int uriStringResourceId) { openUri(Uri.parse( getString(uriStringResourceId))); } protected void openUri(Uri uri) { startActivity(new Intent(Intent.ACTION_VIEW, uri)); } public ErrorsDelegate getErrorsDelegate() { return errorsDelegate; } private void showLogOutConfirmation() { new AlertDialog.Builder(this) .setMessage(R.string.log_out_) .setPositiveButton(R.string.action_log_out, new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); logOut(); } }) .setNegativeButton(R.string.cancel, new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }) .create() .show(); } private void logOut() { if (!isLoggingOut) { isLoggingOut = true; startService(new Intent(this, SimpleSparkApiService.class) .setAction(ClearableIntentService.ACTION_CLEAR_INTENT_QUEUE)); Toaster.s(this, getString(R.string.logged_out)); prefs.clear(); startActivity(new Intent(BaseActivity.this, LoginActivity.class)); finish(); } } private void onDevicesUpdated() { ActionBar actionBar = getActionBar(); if (actionBar != null && shouldShowUpButtonWhenDevicesListNotEmpty()) { boolean noDevices = DeviceState.getKnownDevices().isEmpty(); getActionBar().setDisplayHomeAsUpEnabled(!noDevices); getActionBar().setHomeButtonEnabled(!noDevices); } } private class LogOutReceiver extends BroadcastReceiver { IntentFilter getFilter() { return new IntentFilter(ApiFacade.BROADCAST_SHOULD_LOG_OUT); } @Override public void onReceive(Context context, Intent intent) { logOut(); } } class DevicesUpdatedReceiver extends BroadcastReceiver { IntentFilter getFilter() { return new IntentFilter(ApiFacade.BROADCAST_DEVICES_UPDATED); } @Override public void onReceive(Context context, Intent intent) { onDevicesUpdated(); } } }