gov.wa.wsdot.android.wsdot.ui.TravelTimesFragment.java Source code

Java tutorial

Introduction

Here is the source code for gov.wa.wsdot.android.wsdot.ui.TravelTimesFragment.java

Source

/*
 * Copyright (c) 2014 Washington State Department of Transportation
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 *
 */

package gov.wa.wsdot.android.wsdot.ui;

import gov.wa.wsdot.android.wsdot.R;
import gov.wa.wsdot.android.wsdot.provider.WSDOTContract.TravelTimes;
import gov.wa.wsdot.android.wsdot.service.TravelTimesSyncService;
import gov.wa.wsdot.android.wsdot.util.AnalyticsUtils;
import gov.wa.wsdot.android.wsdot.util.ParserUtils;
import gov.wa.wsdot.android.wsdot.util.UIUtils;
import android.content.BroadcastReceiver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.Cursor;
import android.graphics.Color;
import android.graphics.Typeface;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.view.MenuItemCompat;
import android.support.v4.widget.CursorAdapter;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.SearchView;
import android.support.v7.widget.SearchView.OnQueryTextListener;
import android.text.TextUtils;
import android.util.Log;
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.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.TextView;
import android.widget.Toast;

public class TravelTimesFragment extends ListFragment
        implements LoaderCallbacks<Cursor>, OnQueryTextListener, SwipeRefreshLayout.OnRefreshListener {

    @SuppressWarnings("unused")
    private static final String TAG = TravelTimesFragment.class.getSimpleName();
    private static TravelTimesAdapter adapter;
    private TravelTimesSyncReceiver mTravelTimesSyncReceiver;
    private String mFilter;
    private View mEmptyView;
    private boolean mIsQuery = false;
    private static SwipeRefreshLayout swipeRefreshLayout;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setHasOptionsMenu(true);

        AnalyticsUtils.getInstance(getActivity()).trackPageView("/Traffic Map/Travel Times");

        Intent intent = new Intent(getActivity(), TravelTimesSyncService.class);
        getActivity().startService(intent);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

        ViewGroup root = (ViewGroup) inflater.inflate(R.layout.fragment_list_with_swipe_refresh, null);

        // For some reason, if we omit this, NoSaveStateFrameLayout thinks we are
        // FILL_PARENT / WRAP_CONTENT, making the progress bar stick to the top of the activity.
        root.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT));

        swipeRefreshLayout = (SwipeRefreshLayout) root.findViewById(R.id.swipe_container);
        swipeRefreshLayout.setOnRefreshListener(this);
        swipeRefreshLayout.setColorScheme(17170451, // android.R.color.holo_blue_bright 
                17170452, // android.R.color.holo_green_light 
                17170456, // android.R.color.holo_orange_light 
                17170454); // android.R.color.holo_red_light)

        mEmptyView = root.findViewById(R.id.empty_list_view);

        return root;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        adapter = new TravelTimesAdapter(getActivity(), null, false);
        setListAdapter(adapter);

        // Prepare the loader. Either re-connect with an existing one,
        // or start a new one.        
        getLoaderManager().initLoader(0, null, this);
    }

    @Override
    public void onPause() {
        super.onPause();

        getActivity().unregisterReceiver(mTravelTimesSyncReceiver);
    }

    @Override
    public void onResume() {
        super.onResume();

        IntentFilter filter = new IntentFilter("gov.wa.wsdot.android.wsdot.intent.action.TRAVEL_TIMES_RESPONSE");
        filter.addCategory(Intent.CATEGORY_DEFAULT);
        mTravelTimesSyncReceiver = new TravelTimesSyncReceiver();
        getActivity().registerReceiver(mTravelTimesSyncReceiver, filter);
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        super.onCreateOptionsMenu(menu, inflater);

        //Create the search view
        SearchView searchView = new SearchView(
                ((ActionBarActivity) getActivity()).getSupportActionBar().getThemedContext());
        searchView.setQueryHint("Search Travel Times");
        searchView.setOnQueryTextListener(this);

        MenuItem menuItem_Search = menu.add(R.string.search_title).setIcon(R.drawable.ic_menu_search);
        MenuItemCompat.setActionView(menuItem_Search, searchView);
        MenuItemCompat.setShowAsAction(menuItem_Search,
                MenuItemCompat.SHOW_AS_ACTION_IF_ROOM | MenuItemCompat.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW);

        MenuItemCompat.setOnActionExpandListener(menuItem_Search, new MenuItemCompat.OnActionExpandListener() {
            public boolean onMenuItemActionCollapse(MenuItem item) {
                mFilter = null;
                getLoaderManager().restartLoader(0, null, TravelTimesFragment.this);

                return true;
            }

            public boolean onMenuItemActionExpand(MenuItem item) {

                return true;
            }
        });
    }

    public boolean onQueryTextChange(String newText) {
        // Called when the action bar search text has changed.
        // Update the search filter and restart the loader.
        mFilter = !TextUtils.isEmpty(newText) ? newText : null;
        mIsQuery = true;
        getLoaderManager().restartLoader(0, null, this);

        return true;
    }

    public boolean onQueryTextSubmit(String query) {
        getLoaderManager().restartLoader(0, null, this);
        mIsQuery = true;

        return false;
    }

    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        String[] projection = { TravelTimes._ID, TravelTimes.TRAVEL_TIMES_ID, TravelTimes.TRAVEL_TIMES_TITLE,
                TravelTimes.TRAVEL_TIMES_UPDATED, TravelTimes.TRAVEL_TIMES_DISTANCE,
                TravelTimes.TRAVEL_TIMES_AVERAGE, TravelTimes.TRAVEL_TIMES_CURRENT,
                TravelTimes.TRAVEL_TIMES_IS_STARRED };

        Uri baseUri;

        if (mFilter != null) {
            baseUri = Uri.withAppendedPath(TravelTimes.CONTENT_FILTER_URI, Uri.encode(mFilter));
        } else {
            baseUri = TravelTimes.CONTENT_URI;
        }

        CursorLoader cursorLoader = new TravelTimesItemsLoader(getActivity(), baseUri, projection, null, null,
                null);

        return cursorLoader;
    }

    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
        if (cursor.moveToFirst()) {
            // Nothing.
        } else {
            if (mIsQuery) {
                mIsQuery = false;
                TextView t = (TextView) mEmptyView;
                t.setText(R.string.no_matching_travel_times);
                getListView().setEmptyView(mEmptyView);
            }
        }

        swipeRefreshLayout.setRefreshing(false);
        adapter.swapCursor(cursor);
    }

    public void onLoaderReset(Loader<Cursor> loader) {
        swipeRefreshLayout.setRefreshing(false);
        adapter.swapCursor(null);
    }

    public static class TravelTimesItemsLoader extends CursorLoader {
        public TravelTimesItemsLoader(Context context, Uri uri, String[] projection, String selection,
                String[] selectionArgs, String sortOrder) {
            super(context, uri, projection, selection, selectionArgs, sortOrder);
        }

        @Override
        protected void onStartLoading() {
            super.onStartLoading();

            swipeRefreshLayout.setRefreshing(true);
            forceLoad();
        }
    }

    private class TravelTimesAdapter extends CursorAdapter {
        private Typeface tf = Typeface.createFromAsset(getActivity().getAssets(), "fonts/Roboto-Regular.ttf");
        private Typeface tfb = Typeface.createFromAsset(getActivity().getAssets(), "fonts/Roboto-Bold.ttf");

        public TravelTimesAdapter(Context context, Cursor c, boolean autoRequery) {
            super(context, c, autoRequery);
        }

        @SuppressWarnings("unused")
        public boolean areAllItemsSelectable() {
            return false;
        }

        public boolean isEnabled(int position) {
            return false;
        }

        @Override
        public void bindView(View view, Context context, final Cursor cursor) {
            ViewHolder viewholder = (ViewHolder) view.getTag();

            String average_time;

            String title = cursor.getString(cursor.getColumnIndex(TravelTimes.TRAVEL_TIMES_TITLE));
            viewholder.title.setText(title);
            viewholder.title.setTypeface(tfb);

            String distance = cursor.getString(cursor.getColumnIndex(TravelTimes.TRAVEL_TIMES_DISTANCE));
            int average = cursor.getInt(cursor.getColumnIndex(TravelTimes.TRAVEL_TIMES_AVERAGE));

            if (average == 0) {
                average_time = "Not Available";
            } else {
                average_time = average + " min";
            }

            viewholder.distance_average_time.setText(distance + " / " + average_time);
            viewholder.distance_average_time.setTypeface(tf);

            int current = cursor.getInt(cursor.getColumnIndex(TravelTimes.TRAVEL_TIMES_CURRENT));

            if (current < average) {
                viewholder.current_time.setTextColor(0xFF008060);
            } else if ((current > average) && (average != 0)) {
                viewholder.current_time.setTextColor(Color.RED);
            } else {
                viewholder.current_time.setTextColor(Color.BLACK);
            }

            viewholder.current_time.setText(current + " min");
            viewholder.current_time.setTypeface(tfb);

            String created_at = cursor.getString(cursor.getColumnIndex(TravelTimes.TRAVEL_TIMES_UPDATED));
            viewholder.updated.setText(ParserUtils.relativeTime(created_at, "yyyy-MM-dd h:mm a", false));
            viewholder.updated.setTypeface(tf);

            viewholder.star_button.setTag(cursor.getInt(cursor.getColumnIndex("_id")));
            // Seems when Android recycles the views, the onCheckedChangeListener is still active
            // and the call to setChecked() causes that code within the listener to run repeatedly.
            // Assigning null to setOnCheckedChangeListener seems to fix it.
            viewholder.star_button.setOnCheckedChangeListener(null);
            viewholder.star_button
                    .setChecked(cursor.getInt(cursor.getColumnIndex(TravelTimes.TRAVEL_TIMES_IS_STARRED)) != 0);
            viewholder.star_button.setOnCheckedChangeListener(new OnCheckedChangeListener() {
                public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                    int rowId = (Integer) buttonView.getTag();
                    ContentValues values = new ContentValues();
                    values.put(TravelTimes.TRAVEL_TIMES_IS_STARRED, isChecked ? 1 : 0);

                    int toastMessage = isChecked ? R.string.add_favorite : R.string.remove_favorite;
                    Toast.makeText(getActivity(), toastMessage, Toast.LENGTH_SHORT).show();

                    getActivity().getContentResolver().update(TravelTimes.CONTENT_URI, values,
                            TravelTimes._ID + "=?", new String[] { Integer.toString(rowId) });
                }
            });
        }

        @Override
        public View newView(Context context, Cursor cursor, ViewGroup parent) {
            View view = LayoutInflater.from(context).inflate(R.layout.list_item_travel_times, null);
            ViewHolder viewholder = new ViewHolder(view);
            view.setTag(viewholder);

            return view;
        }

        private class ViewHolder {
            public TextView title;
            public TextView current_time;
            public TextView distance_average_time;
            public TextView updated;
            public CheckBox star_button;

            public ViewHolder(View view) {
                title = (TextView) view.findViewById(R.id.title);
                current_time = (TextView) view.findViewById(R.id.current_time);
                distance_average_time = (TextView) view.findViewById(R.id.distance_average_time);
                updated = (TextView) view.findViewById(R.id.updated);
                star_button = (CheckBox) view.findViewById(R.id.star_button);
            }
        }
    }

    public class TravelTimesSyncReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {
            String responseString = intent.getStringExtra("responseString");

            if (responseString != null) {
                if (responseString.equals("OK")) {
                    getLoaderManager().restartLoader(0, null, TravelTimesFragment.this);
                } else if (responseString.equals("NOP")) {
                    swipeRefreshLayout.setRefreshing(false);
                } else {
                    swipeRefreshLayout.setRefreshing(false);
                    Log.e("TravelTimesSyncReceiver", responseString);

                    if (!UIUtils.isNetworkAvailable(context)) {
                        responseString = getString(R.string.no_connection);
                    }

                    if (getListView().getCount() > 0) {
                        Toast.makeText(context, responseString, Toast.LENGTH_LONG).show();
                    } else {
                        TextView t = (TextView) mEmptyView;
                        t.setText(responseString);
                        getListView().setEmptyView(mEmptyView);
                    }
                }
            } else {
                swipeRefreshLayout.setRefreshing(false);
            }
        }
    }

    public void onRefresh() {
        swipeRefreshLayout.setRefreshing(true);
        Intent intent = new Intent(getActivity(), TravelTimesSyncService.class);
        intent.putExtra("forceUpdate", true);
        getActivity().startService(intent);
    }

}