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

Java tutorial

Introduction

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

Source

/*
 * Copyright (c) 2017 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.traveltimes;

import android.arch.lifecycle.ViewModelProvider;
import android.arch.lifecycle.ViewModelProviders;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Typeface;
import android.os.Bundle;
import android.support.design.widget.Snackbar;
import android.support.v4.view.MenuItemCompat;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.SearchView;
import android.support.v7.widget.SearchView.OnQueryTextListener;
import android.text.TextUtils;
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.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.List;

import javax.inject.Inject;

import gov.wa.wsdot.android.wsdot.R;
import gov.wa.wsdot.android.wsdot.database.traveltimes.TravelTimeEntity;
import gov.wa.wsdot.android.wsdot.database.traveltimes.TravelTimeGroup;
import gov.wa.wsdot.android.wsdot.di.Injectable;
import gov.wa.wsdot.android.wsdot.ui.BaseActivity;
import gov.wa.wsdot.android.wsdot.ui.BaseFragment;
import gov.wa.wsdot.android.wsdot.util.ParserUtils;
import gov.wa.wsdot.android.wsdot.util.decoration.SimpleDividerItemDecoration;

public class TravelTimesFragment extends BaseFragment
        implements OnQueryTextListener, SwipeRefreshLayout.OnRefreshListener, Injectable {

    private static final String TAG = TravelTimesFragment.class.getSimpleName();
    private static TravelTimesAdapter mAdapter;

    private View mEmptyView;

    private static SwipeRefreshLayout swipeRefreshLayout;

    protected RecyclerView mRecyclerView;
    protected LinearLayoutManager mLayoutManager;

    private static TravelTimesViewModel viewModel;

    @Inject
    ViewModelProvider.Factory viewModelFactory;

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

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

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

        mRecyclerView = root.findViewById(R.id.my_recycler_view);
        mRecyclerView.setHasFixedSize(true);
        mLayoutManager = new LinearLayoutManager(getActivity());
        mLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        mRecyclerView.setLayoutManager(mLayoutManager);
        mAdapter = new TravelTimesAdapter(getActivity());
        mRecyclerView.setAdapter(mAdapter);

        mRecyclerView.addItemDecoration(new SimpleDividerItemDecoration(getActivity()));

        // 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 = root.findViewById(R.id.swipe_container);
        swipeRefreshLayout.setOnRefreshListener(this);
        swipeRefreshLayout.setColorSchemeResources(R.color.holo_blue_bright, R.color.holo_green_light,
                R.color.holo_orange_light, R.color.holo_red_light);

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

        viewModel = ViewModelProviders.of(this, viewModelFactory).get(TravelTimesViewModel.class);

        viewModel.getResourceStatus().observe(this, resourceStatus -> {
            if (resourceStatus != null) {
                switch (resourceStatus.status) {
                case LOADING:
                    swipeRefreshLayout.setRefreshing(true);
                    break;
                case SUCCESS:
                    swipeRefreshLayout.setRefreshing(false);
                    break;
                case ERROR:
                    swipeRefreshLayout.setRefreshing(false);
                    Toast.makeText(this.getContext(), "connection error", Toast.LENGTH_LONG).show();
                }
            }
        });

        viewModel.getQueryTravelTimes().observe(this, travelTimes -> {
            if (travelTimes != null) {
                if (travelTimes.size() == 0) {
                    TextView t = (TextView) mEmptyView;
                    if (!viewModel.getQueryTermValue().equals("")) {
                        t.setText(R.string.no_matching_travel_times);
                    } else {
                        t.setText("travel times unavailable.");
                    }
                    mEmptyView.setVisibility(View.VISIBLE);
                } else {
                    mEmptyView.setVisibility(View.GONE);
                }
                mAdapter.setData(new ArrayList<>(travelTimes));
            }
        });

        viewModel.setQueryTerm("");

        return root;
    }

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

        //Create the search view
        SearchView searchView = new SearchView(
                ((BaseActivity) 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) {
                viewModel.setQueryTerm("");
                return true;
            }

            public boolean onMenuItemActionExpand(MenuItem item) {
                return true;
            }
        });
    }

    public boolean onQueryTextChange(String newText) {
        String query = !TextUtils.isEmpty(newText) ? newText : "";
        viewModel.setQueryTerm(query);
        return true;
    }

    public boolean onQueryTextSubmit(String query) {
        viewModel.setQueryTerm(query);
        return false;
    }

    /**
     * Custom adapter for items in recycler view.
     *
     * Binds the custom ViewHolder class to it's data.
     *
     * @see android.support.v7.widget.RecyclerView.Adapter
     */
    private class TravelTimesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
        private Typeface tf = Typeface.createFromAsset(getActivity().getAssets(), "fonts/Roboto-Regular.ttf");
        private Typeface tfb = Typeface.createFromAsset(getActivity().getAssets(), "fonts/Roboto-Bold.ttf");
        private Context context;

        private ArrayList<TravelTimeGroup> mData = new ArrayList<>();

        private List<RecyclerView.ViewHolder> mItems = new ArrayList<>();

        public TravelTimesAdapter(Context context) {
            this.context = context;
        }

        public void setData(ArrayList<TravelTimeGroup> data) {
            mData = data;
            this.notifyDataSetChanged();
        }

        @Override
        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            View view = LayoutInflater.from(context).inflate(R.layout.list_item_travel_time_group, null);
            ViewHolder viewholder = new ViewHolder(view);
            view.setTag(viewholder);
            mItems.add(viewholder);
            return viewholder;
        }

        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {

            ViewHolder viewholder = (ViewHolder) viewHolder;

            TravelTimeGroup travelTimeGroup = mData.get(position);

            final String title = travelTimeGroup.trip.getTitle();
            viewholder.title.setText(title);
            viewholder.title.setTypeface(tfb);

            viewholder.travel_times_layout.removeAllViews();

            for (TravelTimeEntity time : travelTimeGroup.travelTimes) {

                View travelTimeView = makeTravelTimeView(time, getContext());

                if (travelTimeGroup.travelTimes.indexOf(time) == travelTimeGroup.travelTimes.size() - 1) {
                    travelTimeView.findViewById(R.id.line).setVisibility(View.GONE);
                }

                viewholder.travel_times_layout.addView(travelTimeView);
            }

            // 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(travelTimeGroup.trip.getIsStarred() != 0);

            viewholder.star_button.setOnCheckedChangeListener((buttonView, isChecked) -> {

                Snackbar added_snackbar = Snackbar.make(getView(), R.string.add_favorite, Snackbar.LENGTH_SHORT);

                Snackbar removed_snackbar = Snackbar.make(getView(), R.string.remove_favorite,
                        Snackbar.LENGTH_SHORT);

                if (isChecked) {
                    added_snackbar.show();
                } else {
                    removed_snackbar.show();
                }

                viewModel.setIsStarredFor(title, isChecked ? 1 : 0);
            });
        }

        @Override
        public int getItemCount() {
            return mData.size();
        }

        private class ViewHolder extends RecyclerView.ViewHolder {
            public LinearLayout travel_times_layout;
            public TextView title;
            public CheckBox star_button;

            public ViewHolder(View view) {
                super(view);
                travel_times_layout = view.findViewById(R.id.travel_times_linear_layout);
                title = view.findViewById(R.id.title);
                star_button = view.findViewById(R.id.star_button);
            }
        }
    }

    public static View makeTravelTimeView(TravelTimeEntity time, Context context) {

        Typeface tfb = Typeface.createFromAsset(context.getAssets(), "fonts/Roboto-Bold.ttf");
        LayoutInflater li = LayoutInflater.from(context);
        View cv = li.inflate(R.layout.trip_view, null);

        // set via label
        ((TextView) cv.findViewById(R.id.title)).setText("Via " + time.getVia());

        TextView currentTimeTextView = cv.findViewById(R.id.current_value);
        currentTimeTextView.setTypeface(tfb);

        // set updated
        ((TextView) cv.findViewById(R.id.updated))
                .setText(ParserUtils.relativeTime(time.getUpdated(), "yyyy-MM-dd HH:mm a", false));

        if (time.getStatus().toLowerCase().equals("closed")) {
            currentTimeTextView.setText("Closed");
            currentTimeTextView.setTextColor(Color.RED);
            cv.findViewById(R.id.subtitle).setVisibility(View.GONE);
        } else {

            // set distance and avg time text view
            String average_time;
            String distance = time.getDistance();
            int average = time.getAverage();

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

            ((TextView) cv.findViewById(R.id.subtitle)).setText(distance + " / " + average_time);

            // set current travel time. Set to closed if status is closed.
            int current = time.getCurrent();

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

            currentTimeTextView.setText(current + " min");

        }
        return cv;

    }

    public void onRefresh() {
        swipeRefreshLayout.setRefreshing(true);
        viewModel.forceRefreshTravelTimes();
    }
}