/*
* 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();
}
}