uk.org.rivernile.edinburghbustracker.android.fragments.general.AlertManagerFragment.java Source code

Java tutorial

Introduction

Here is the source code for uk.org.rivernile.edinburghbustracker.android.fragments.general.AlertManagerFragment.java

Source

/*
 * Copyright (C) 2011 - 2013 Niall 'Rivernile' Scott
 *
 * This software is provided 'as-is', without any express or implied
 * warranty.  In no event will the authors or contributors be held liable for
 * any damages arising from the use of this software.
 *
 * The aforementioned copyright holder(s) hereby grant you a
 * non-transferrable right to use this software for any purpose (including
 * commercial applications), and to modify it and redistribute it, subject to
 * the following conditions:
 *
 *  1. This notice may not be removed or altered from any file it appears in.
 *
 *  2. Any modifications made to this software, except those defined in
 *     clause 3 of this agreement, must be released under this license, and
 *     the source code of any modifications must be made available on a
 *     publically accessible (and locateable) website, or sent to the
 *     original author of this software.
 *
 *  3. Software modifications that do not alter the functionality of the
 *     software but are simply adaptations to a specific environment are
 *     exempt from clause 2.
 */

package uk.org.rivernile.edinburghbustracker.android.fragments.general;

import android.app.Activity;
import android.content.Context;
import android.database.Cursor;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.support.v4.widget.CursorAdapter;
import android.text.Html;
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.Button;
import android.widget.TextView;
import uk.org.rivernile.android.utils.SimpleCursorLoader;
import uk.org.rivernile.edinburghbustracker.android.BusStopDatabase;
import uk.org.rivernile.edinburghbustracker.android.R;
import uk.org.rivernile.edinburghbustracker.android.SettingsDatabase;
import uk.org.rivernile.edinburghbustracker.android.fragments.dialogs.DeleteAllAlertsDialogFragment;
import uk.org.rivernile.edinburghbustracker.android.fragments.dialogs.DeleteProximityAlertDialogFragment;
import uk.org.rivernile.edinburghbustracker.android.fragments.dialogs.DeleteTimeAlertDialogFragment;

/**
 * This Fragment allows the users to view what proximity and time alerts they
 * have set and allow them to delete single alerts or all alerts.
 * 
 * Instances of this Fragment are retained between rotation changes.
 * 
 * @author Niall Scott
 */
public class AlertManagerFragment extends ListFragment
        implements LoaderManager.LoaderCallbacks<Cursor>, DeleteAllAlertsDialogFragment.Callbacks,
        DeleteProximityAlertDialogFragment.Callbacks, DeleteTimeAlertDialogFragment.Callbacks {

    private Callbacks callbacks;
    private AlertCursorAdapter ad;

    /**
     * {@inheritDoc}
     */
    @Override
    public void onAttach(final Activity activity) {
        super.onAttach(activity);

        try {
            callbacks = (Callbacks) activity;
        } catch (ClassCastException e) {
            throw new IllegalStateException(
                    activity.getClass().getName() + " does not implement " + Callbacks.class.getName());
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Create the adapter.
        ad = new AlertCursorAdapter(getActivity(), null);
        setListAdapter(ad);

        // Tell the underlying Activity that this Fragment hosts an options
        // menu.
        setHasOptionsMenu(true);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
            final Bundle savedInstanceState) {
        return inflater.inflate(R.layout.alertmanager, container, false);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onResume() {
        super.onResume();

        // Reload the data every time onResume is called.
        getLoaderManager().restartLoader(0, null, this);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
        // Inflate the menu.
        inflater.inflate(R.menu.alertmanager_option_menu, menu);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onPrepareOptionsMenu(final Menu menu) {
        super.onPrepareOptionsMenu(menu);

        final MenuItem deleteAllItem = menu.findItem(R.id.alertmanager_option_menu_delete_all);

        // Only enable the 'Delete all alerts' item when there's alerts to
        // delete.
        if (ad.getCount() > 0) {
            deleteAllItem.setEnabled(true);
        } else {
            deleteAllItem.setEnabled(false);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean onOptionsItemSelected(final MenuItem item) {
        switch (item.getItemId()) {
        case R.id.alertmanager_option_menu_delete_all:
            // Show the 'Delete all alerts' confirmation DialogFragment.
            callbacks.onShowConfirmDeleteAllAlerts();
            return true;
        default:
            return super.onOptionsItemSelected(item);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Loader<Cursor> onCreateLoader(final int id, final Bundle args) {
        // Return the only Loader for this Fragment.
        return new AlertCursorLoader(getActivity());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onLoadFinished(final Loader<Cursor> loader, final Cursor c) {
        if (isAdded()) {
            // Swap in the new Cursor. The superclass deals with closing the old
            // Cursor object.
            ad.swapCursor(c);
            // There may be change in status so refresh the options menu.
            getActivity().supportInvalidateOptionsMenu();
        } else {
            if (c != null) {
                c.close();
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onLoaderReset(final Loader<Cursor> loader) {
        // If the Loader has been reset, empty the ListAdapter.
        ad.swapCursor(null);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onConfirmAllAlertsDeletion() {
        // All alerts have been removed, refresh the data.
        getLoaderManager().restartLoader(0, null, this);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onCancelAllAlertsDeletion() {
        // Nothing to do here.
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onConfirmProximityAlertDeletion() {
        // The proximity alert has been removed, refresh the data.
        getLoaderManager().restartLoader(0, null, this);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onCancelProximityAlertDeletion() {
        // Nothing to do here.
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onConfirmTimeAlertDeletion() {
        // The time alert has been removed, refresh the data.
        getLoaderManager().restartLoader(0, null, this);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onCancelTimeAlertDeletion() {
        // Nothing to do here.
    }

    /**
     * This class allows the alert list to be loaded in the background.
     */
    private static class AlertCursorLoader extends SimpleCursorLoader {

        private final SettingsDatabase sd;

        /**
         * Create a new AlertCursorLoader.
         * 
         * @param context A Context object.
         */
        public AlertCursorLoader(final Context context) {
            super(context);

            sd = SettingsDatabase.getInstance(context.getApplicationContext());
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public Cursor loadInBackground() {
            final Cursor c = sd.getAllAlerts();

            // This ensures the Cursor window is set properly.
            if (c != null)
                c.getCount();

            return c;
        }
    }

    /**
     * This CursorAdapter shows a list of alerts that have been set by the user.
     */
    private class AlertCursorAdapter extends CursorAdapter {

        private BusStopDatabase bsd;

        /**
         * Create a new AlertCursorAdapter.
         * 
         * @param context A Context object.
         * @param c The Cursor to use.
         */
        public AlertCursorAdapter(final Context context, final Cursor c) {
            super(context, c, 0);

            // Get a reference to the BusStopDatabase.
            bsd = BusStopDatabase.getInstance(context.getApplicationContext());
        }

        /**
         * This getView() overrides the superclass implementation as we do not
         * want to use convertView here. There can only be 1 item of its type
         * shown in the list and therefore will never have a proper item to
         * convert from. This means that the View will need to be inflated from
         * XML each time it needs to be used.
         * 
         * @param position The position in the list, and therefore the Cursor,
         * to be used.
         * @param convertView A View to be converted from. This is not used in
         * this implementation.
         * @param parent The parent View.
         */
        @Override
        public View getView(final int position, final View convertView, final ViewGroup parent) {
            // Move the Cursor in to position. Throw an Exception if this is
            // not possible.
            if (!mCursor.moveToPosition(position)) {
                throw new IllegalStateException("Couldn't move cursor to " + "position " + position);
            }

            // Create a new View.
            final View v = newView(mContext, mCursor, parent);
            // Populate the View
            bindView(v, mContext, mCursor);

            return v;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public View newView(final Context context, final Cursor c, final ViewGroup container) {
            final LayoutInflater inflater = LayoutInflater.from(context);

            // The inflated layout depends on what time of alert it is showing.
            switch (c.getInt(1)) {
            case SettingsDatabase.ALERTS_TYPE_PROXIMITY:
                return inflater.inflate(R.layout.alertmanager_list_proximity, container, false);
            case SettingsDatabase.ALERTS_TYPE_TIME:
                return inflater.inflate(R.layout.alertmanager_list_time, container, false);
            default:
                return null;
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void bindView(final View view, final Context context, final Cursor c) {
            // Get the stopCode and locality information.
            final String stopCode = c.getString(3);
            final String locality = bsd.getLocalityForStopCode(stopCode);
            final String busStop;

            // Append the locality if it is available.
            if (locality != null) {
                busStop = context.getString(R.string.busstop_locality_coloured, bsd.getNameForBusStop(stopCode),
                        locality, stopCode);
            } else {
                busStop = context.getString(R.string.busstop_coloured, bsd.getNameForBusStop(stopCode), stopCode);
            }

            Button btn;
            TextView txt;

            // How the View is populated depends on the alert type.
            switch (c.getInt(1)) {
            case SettingsDatabase.ALERTS_TYPE_PROXIMITY:
                btn = (Button) view.findViewById(R.id.btnRemoveProxAlert);
                btn.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(final View v) {
                        callbacks.onShowConfirmDeleteProximityAlert();
                    }
                });

                // Set information text.
                txt = (TextView) view.findViewById(R.id.txtAlertManProx);
                txt.setText(
                        Html.fromHtml(context.getString(R.string.alertmanager_prox_text, c.getInt(4), busStop)));
                break;
            case SettingsDatabase.ALERTS_TYPE_TIME:
                btn = (Button) view.findViewById(R.id.btnRemoveTimeAlert);
                btn.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(final View v) {
                        callbacks.onShowConfirmDeleteTimeAlert();
                    }
                });

                txt = (TextView) view.findViewById(R.id.txtAlertManTime);
                final int timeTrigger = c.getInt(6);
                final String[] services = c.getString(5).split(",");
                final StringBuilder sb = new StringBuilder();

                for (String service : services) {
                    if (sb.length() > 0)
                        sb.append(", ");

                    sb.append(service);
                }

                // Get the correct text to display, depending on plurality.
                txt.setText(Html.fromHtml(context.getResources().getQuantityString(R.plurals.alertmanager_time_text,
                        timeTrigger == 0 ? 1 : timeTrigger, busStop, sb.toString(), timeTrigger)));
                break;
            default:
                break;
            }
        }
    }

    /**
     * Any Activities which host this Fragment must implement this interface to
     * handle navigation events.
     */
    public static interface Callbacks {

        /**
         * This is called when it should be confirmed with the user that they
         * want to delete all alerts.
         */
        public void onShowConfirmDeleteAllAlerts();

        /**
         * This is called when it should be confirmed with the user that they
         * want to delete the proximity alert.
         */
        public void onShowConfirmDeleteProximityAlert();

        /**
         * This is called when it should be confirmed with the user that they
         * want to delete the time alert.
         */
        public void onShowConfirmDeleteTimeAlert();
    }
}