org.akvo.flow.ui.fragment.SurveyedLocaleListFragment.java Source code

Java tutorial

Introduction

Here is the source code for org.akvo.flow.ui.fragment.SurveyedLocaleListFragment.java

Source

/*
 *  Copyright (C) 2013-2014 Stichting Akvo (Akvo Foundation)
 *
 *  This file is part of Akvo FLOW.
 *
 *  Akvo FLOW is free software: you can redistribute it and modify it under the terms of
 *  the GNU Affero General Public License (AGPL) as published by the Free Software Foundation,
 *  either version 3 of the License or any later version.
 *
 *  Akvo FLOW 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 Affero General Public License included below for more details.
 *
 *  The full license text can also be seen at <http://www.gnu.org/licenses/agpl.html>.
 */

package org.akvo.flow.ui.fragment;

import java.text.DecimalFormat;
import java.util.Date;

import org.akvo.flow.util.PlatformUtil;
import org.ocpsoft.prettytime.PrettyTime;

import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.Cursor;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.ListFragment;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.Loader;
import android.support.v4.widget.CursorAdapter;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import org.akvo.flow.R;
import org.akvo.flow.activity.RecordListActivity;
import org.akvo.flow.async.loader.SurveyedLocaleLoader;
import org.akvo.flow.dao.SurveyDbAdapter;
import org.akvo.flow.dao.SurveyDbAdapter.RecordColumns;
import org.akvo.flow.dao.SurveyDbAdapter.SurveyInstanceColumns;
import org.akvo.flow.dao.SurveyDbAdapter.SurveyInstanceStatus;
import org.akvo.flow.domain.SurveyedLocale;
import org.akvo.flow.ui.fragment.OrderByDialogFragment.OrderByDialogListener;
import org.akvo.flow.util.ConstantUtil;

public class SurveyedLocaleListFragment extends ListFragment
        implements LocationListener, OnItemClickListener, LoaderCallbacks<Cursor>, OrderByDialogListener {
    private static final String TAG = SurveyedLocaleListFragment.class.getSimpleName();

    private LocationManager mLocationManager;
    private double mLatitude = 0.0d;
    private double mLongitude = 0.0d;

    private int mOrderBy;
    private long mSurveyGroupId;
    private SurveyDbAdapter mDatabase;

    private SurveyedLocaleListAdapter mAdapter;
    private RecordListListener mListener;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mSurveyGroupId = getArguments().getLong(RecordListActivity.EXTRA_SURVEY_GROUP_ID);
        mOrderBy = ConstantUtil.ORDER_BY_DATE;// Default case
        setHasOptionsMenu(true);
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);

        // This makes sure that the container activity has implemented
        // the callback interface. If not, it throws an exception
        try {
            mListener = (RecordListListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString() + " must implement SurveyedLocalesFragmentListener");
        }
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        mLocationManager = (LocationManager) getActivity().getSystemService(Context.LOCATION_SERVICE);
        mDatabase = new SurveyDbAdapter(getActivity());
        if (mAdapter == null) {
            mAdapter = new SurveyedLocaleListAdapter(getActivity());
            setListAdapter(mAdapter);
        }
        setEmptyText(getString(R.string.no_records_text));
        getListView().setOnItemClickListener(this);
    }

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

        // try to find out where we are
        Criteria criteria = new Criteria();
        criteria.setAccuracy(Criteria.ACCURACY_FINE);
        String provider = mLocationManager.getBestProvider(criteria, true);
        if (provider != null) {
            Location loc = mLocationManager.getLastKnownLocation(provider);
            if (loc != null) {
                mLatitude = loc.getLatitude();
                mLongitude = loc.getLongitude();
            }
        }
        mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 0, this);

        // Listen for data sync updates, so we can update the UI accordingly
        getActivity().registerReceiver(dataSyncReceiver, new IntentFilter(getString(R.string.action_data_sync)));

        refresh();
    }

    @Override
    public void onPause() {
        super.onPause();
        getActivity().unregisterReceiver(dataSyncReceiver);
        mLocationManager.removeUpdates(this);
        mDatabase.close();
    }

    /**
     * Ideally, we should build a ContentProvider, so this notifications are handled
     * automatically, and the loaders restarted without this explicit dependency.
     */
    public void refresh() {
        if (isResumed()) {
            if (mOrderBy == ConstantUtil.ORDER_BY_DISTANCE && mLatitude == 0.0d && mLongitude == 0.0d) {
                // Warn user that the location is unknown
                Toast.makeText(getActivity(), "Unknown Location", Toast.LENGTH_SHORT).show();
                return;
            }
            getLoaderManager().restartLoader(0, null, this);
        }
    }

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        Cursor cursor = (Cursor) mAdapter.getItem(position);
        final String localeId = cursor.getString(cursor.getColumnIndexOrThrow(RecordColumns.RECORD_ID));

        mListener.onRecordSelected(localeId);// Notify the host activity
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle item selection
        switch (item.getItemId()) {
        case R.id.order_by:
            DialogFragment dialogFragment = new OrderByDialogFragment();
            dialogFragment.setTargetFragment(this, 0);
            dialogFragment.show(getFragmentManager(), "order_by");
            return true;
        }

        return false;
    }

    @Override
    public void onOrderByClick(int order) {
        if (mOrderBy != order) {
            mOrderBy = order;
            refresh();
        }
    }

    // ==================================== //
    // ========= Loader Callbacks ========= //
    // ==================================== //

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        return new SurveyedLocaleLoader(getActivity(), mDatabase, mSurveyGroupId, mLatitude, mLongitude, mOrderBy);
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
        if (cursor == null) {
            Log.e(TAG, "onFinished() - Loader returned no data");
            return;
        }

        mAdapter.swapCursor(cursor);
    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        mAdapter.swapCursor(null);
    }

    // ==================================== //
    // ======== Location Callbacks ======== //
    // ==================================== //

    @Override
    public void onLocationChanged(Location location) {
        // a single location is all we need
        mLocationManager.removeUpdates(this);
        mLatitude = location.getLatitude();
        mLongitude = location.getLongitude();
        refresh();
    }

    @Override
    public void onProviderDisabled(String provider) {
    }

    @Override
    public void onProviderEnabled(String provider) {
    }

    @Override
    public void onStatusChanged(String provider, int status, Bundle extras) {
    }

    /**
     * BroadcastReceiver to notify of data synchronisation. This should be
     * fired from DataSyncService.
     */
    private BroadcastReceiver dataSyncReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            Log.i(TAG, "Survey Instance status has changed. Refreshing UI...");
            refresh();
        }
    };

    /**
     * List Adapter to bind the Surveyed Locales into the list items
     */
    class SurveyedLocaleListAdapter extends CursorAdapter {

        public SurveyedLocaleListAdapter(Context context) {
            super(context, null, false);
        }

        private String getDistanceText(SurveyedLocale surveyedLocale) {
            StringBuilder builder = new StringBuilder("Distance: ");

            if (surveyedLocale.getLatitude() != null && surveyedLocale.getLongitude() != null
                    && (mLatitude != 0.0d || mLongitude != 0.0d)) {
                float[] results = new float[1];
                Location.distanceBetween(mLatitude, mLongitude, surveyedLocale.getLatitude(),
                        surveyedLocale.getLongitude(), results);
                final double distance = results[0];

                // default: no decimal point, km as unit
                DecimalFormat df = new DecimalFormat("#.#");
                String unit = "km";
                Double factor = 0.001; // convert from meters to km

                // for distances smaller than 1 km, use meters as unit
                if (distance < 1000.0) {
                    factor = 1.0;
                    unit = "m";
                    df = new DecimalFormat("#"); // only whole meters
                }
                double dist = distance * factor;
                builder.append(df.format(dist)).append(" ").append(unit);
            } else {
                builder.append(getString(R.string.unknown));
            }

            return builder.toString();
        }

        private void displayDateText(TextView tv, Long time) {
            if (time != null && time > 0) {
                tv.setVisibility(View.VISIBLE);
                tv.setText(getString(R.string.last_modified) + " " + new PrettyTime().format(new Date(time)));
            } else {
                tv.setVisibility(View.GONE);
            }
        }

        @Override
        public void bindView(View view, Context context, Cursor c) {
            TextView nameView = (TextView) view.findViewById(R.id.locale_name);
            TextView idView = (TextView) view.findViewById(R.id.locale_id);
            TextView dateView = (TextView) view.findViewById(R.id.last_modified);
            TextView distanceView = (TextView) view.findViewById(R.id.locale_distance);
            ImageView statusImage = (ImageView) view.findViewById(R.id.status_img);
            final SurveyedLocale surveyedLocale = SurveyDbAdapter.getSurveyedLocale(c);

            // This cursor contains extra info about the Record status
            int status = c.getInt(c.getColumnIndexOrThrow(SurveyInstanceColumns.STATUS));

            nameView.setText(surveyedLocale.getDisplayName(context));
            idView.setText(surveyedLocale.getId());
            distanceView.setText(getDistanceText(surveyedLocale));
            displayDateText(dateView, surveyedLocale.getLastModified());

            int statusRes = 0;
            switch (status) {
            case SurveyInstanceStatus.SAVED:
                statusRes = R.drawable.record_saved_icn;
                break;
            case SurveyInstanceStatus.SUBMITTED:
            case SurveyInstanceStatus.EXPORTED:
                statusRes = R.drawable.record_exported_icn;
                break;
            case SurveyInstanceStatus.SYNCED:
            case SurveyInstanceStatus.DOWNLOADED:
                statusRes = R.drawable.record_synced_icn;
                break;
            }

            statusImage.setImageResource(statusRes);

            // Alternate background
            int attr = c.getPosition() % 2 == 0 ? R.attr.listitem_bg1 : R.attr.listitem_bg2;
            final int res = PlatformUtil.getResource(context, attr);
            view.setBackgroundResource(res);
        }

        @Override
        public View newView(Context context, Cursor c, ViewGroup parent) {
            LayoutInflater inflater = LayoutInflater.from(getActivity());
            return inflater.inflate(R.layout.surveyed_locale_item, null);
        }

    }

}