package com.sunlightlabs.android.congress.notifications; import java.util.ArrayList; import java.util.List; import android.app.Activity; import android.content.Intent; import android.content.res.Resources; import android.database.sqlite.SQLiteException; import android.os.AsyncTask; import android.preference.PreferenceManager; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentActivity; import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; import com.sunlightlabs.android.congress.NotificationSettings; import com.sunlightlabs.android.congress.NotificationTabs; import com.sunlightlabs.android.congress.R; import com.sunlightlabs.android.congress.utils.Analytics; import com.sunlightlabs.android.congress.utils.Database; import com.sunlightlabs.android.congress.utils.Utils; import com.sunlightlabs.congress.models.CongressException; public class Footer { public static final int ERROR = -2; public static final int DISABLED = -1; public static final int OFF = 0; public static final int ON = 1; public static final int WORKING = 2; public TextView text; public ImageView image; public ProgressBar working; private int state; private Activity context; private Resources resources; private ViewGroup footerView; private Subscription subscription; private List<String> latestIds; // initialize the footer to a fragment, obtain a tracker from its activity's fragment pool // set it up with a subscription and a list of seen items public static void setup(Fragment fragment, Subscription subscription, List<?> objects) { new Footer(fragment).init(subscription, objects); } public Footer(Fragment fragment) { FragmentActivity activity = fragment.getActivity(); this.context = activity; this.resources = activity.getResources(); this.footerView = (ViewGroup) fragment.getView().findViewById(R.id.footer); this.text = (TextView) footerView.findViewById(R.id.text); this.image = (ImageView) footerView.findViewById(R.id.image); this.working = (ProgressBar) footerView.findViewById(R.id.working); } public void init(Subscription subscription, List<?> objects) { this.subscription = subscription; setupControls(); Subscriber subscriber; try { subscriber = subscription.getSubscriber(); List<String> ids = new ArrayList<String>(); if (objects != null) { int size = objects.size(); for (int i=0; i<size; i++) { // TODO: can get rid of this null check when we switch to a pagination approach that doesn't use a null entry Object obj = objects.get(i); if (obj != null) ids.add(subscriber.decodeId(obj)); } } this.latestIds = ids; } catch(CongressException e) { Log.e(Utils.TAG, "Could not instantiate a Subscriber of class " + subscription.notificationClass, e); } } public void hide() { this.footerView.setVisibility(View.GONE); } public void setupControls() { footerView.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { onTap(); } }); // our logic in the footer works out so we should never have concurrent database access attempts, but // if that were not the case, we would need to surround calls to the database with a synchronized(this) {} block if (Utils.getBooleanPreference(context, NotificationSettings.KEY_NOTIFY_ENABLED, NotificationSettings.DEFAULT_NOTIFY_ENABLED)) { if (state == WORKING) setWorking(); else { Database database = new Database(context); try { database.open(); boolean on = database.hasSubscription(subscription.id, subscription.notificationClass); database.close(); if (on) setOn(); else setOff(); } catch(SQLiteException e) { Log.e(Utils.TAG, "Error on initializing footer, giving up and letting the user know.", e); setError(); } } } else { if (firstTime()) setFirstTime(); else setDisabled(); } footerView.setVisibility(View.VISIBLE); } private void onTap() { if (state == OFF) { setWorking(); Analytics.subscribeNotification(context, subscription.notificationClass); new SubscribeTask(this).execute(); } else if (state == ON) { setWorking(); Analytics.unsubscribeNotification(context, subscription.notificationClass); new UnsubscribeTask(this).execute(); } else if (state == DISABLED) context.startActivity(new Intent(context, NotificationTabs.class)); } private void setOn() { state = ON; text.setText(R.string.footer_on); text.setTextColor(resources.getColor(R.color.text)); image.setVisibility(View.VISIBLE); image.setImageResource(R.drawable.circle_on); working.setVisibility(View.GONE); footerView.setBackgroundColor(resources.getColor(R.color.background_dark)); } private void setOff() { state = OFF; text.setText(R.string.footer_off); text.setTextColor(resources.getColor(R.color.text_grey)); image.setVisibility(View.VISIBLE); image.setImageResource(R.drawable.circle_off); working.setVisibility(View.GONE); footerView.setBackgroundColor(resources.getColor(R.color.background_dark)); } private void setWorking() { state = WORKING; text.setText(R.string.footer_working); image.setVisibility(View.GONE); working.setVisibility(View.VISIBLE); text.setTextColor(resources.getColor(R.color.text_grey)); footerView.setBackgroundColor(resources.getColor(R.color.background_dark)); } private void setDisabled() { state = DISABLED; text.setText(R.string.footer_disabled); text.setTextColor(resources.getColor(R.color.text_grey)); working.setVisibility(View.GONE); footerView.setBackgroundColor(resources.getColor(R.color.background_grey)); } private void setFirstTime() { state = DISABLED; // leave it at disabled for purposes of tapping text.setText(R.string.footer_first_time); text.setTextColor(resources.getColor(R.color.text)); working.setVisibility(View.GONE); footerView.setBackgroundColor(resources.getColor(R.color.background_grey)); } // used when there's a database error on initialization and there's not much else to do private void setError() { state = ERROR; text.setText(R.string.footer_error); text.setTextColor(resources.getColor(R.color.text_grey)); image.setVisibility(View.GONE); working.setVisibility(View.GONE); footerView.setBackgroundColor(resources.getColor(R.color.background_dark)); } // will turn false once the user has visited the notification settings (and seen the explanation dialog) for the first time private boolean firstTime() { return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(NotificationSettings.KEY_FIRST_TIME_SETTINGS, NotificationSettings.DEFAULT_FIRST_TIME_SETTINGS); } private class SubscribeTask extends AsyncTask<Void,Void,Integer> { private Footer footer; private Subscription subscription; private List<String> latestIds; public SubscribeTask(Footer footer) { this.footer = footer; this.subscription = footer.subscription; this.latestIds = footer.latestIds; } @Override public Integer doInBackground(Void... nothing) { Database database = new Database(footer.context); database.open(); try { database.addSubscription(subscription); int results = (int) database.addSeenIds(subscription, latestIds); database.close(); return results; } // most likely a locked database catch (SQLiteException e) { Log.e(Utils.TAG, "Database exception on subscribe tap.", e); database.close(); return -1; } } @Override public void onPostExecute(Integer rows) { if (rows == -1) { Log.i(Utils.TAG, "Footer: [" + subscription.notificationClass + "][" + subscription.id + "] " + "Error saving notifications, -1 returned from one or more insert calls"); Utils.alert(footer.context, R.string.footer_busy); setOff(); } else { Log.i(Utils.TAG, "Footer: [" + subscription.notificationClass + "][" + subscription.id + "] " + "Added notification in the db for subscription with " + rows + " new inserted IDs"); setOn(); } } } private class UnsubscribeTask extends AsyncTask<Void,Void,Integer> { private Footer footer; private Subscription subscription; public UnsubscribeTask(Footer footer) { this.footer = footer; this.subscription = footer.subscription; } @Override public Integer doInBackground(Void... nothing) { Database database = new Database(footer.context); database.open(); try { int results = (int) database.removeSubscription(subscription.id, subscription.notificationClass); database.close(); return results; } // most likely a locked database catch (SQLiteException e) { Log.e(Utils.TAG, "Database exception on unsubscribe tap.", e); database.close(); return -1; } } @Override public void onPostExecute(Integer rows) { if (rows == -1) { Log.i(Utils.TAG, "Footer: [" + subscription.notificationClass + "][" + subscription.id + "] " + "Error saving notifications, -1 returned from a delete call"); Utils.alert(footer.context, R.string.footer_busy); setOn(); } else { Log.i(Utils.TAG, "Footer: [" + subscription.notificationClass + "][" + subscription.id + "] " + "Removed notification from the db, " + rows + " rows deleted"); setOff(); } } } }