ca.appvelopers.mcgillmobile.ui.MapActivity.java Source code

Java tutorial

Introduction

Here is the source code for ca.appvelopers.mcgillmobile.ui.MapActivity.java

Source

/*
 * Copyright 2014-2016 Julien Guerinet
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package ca.appvelopers.mcgillmobile.ui;

import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.FragmentManager;
import android.support.v4.content.ContextCompat;
import android.support.v4.util.Pair;
import android.support.v4.view.MenuItemCompat;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.SearchView;
import android.widget.TextView;
import android.widget.Toast;

import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;
import com.guerinet.formgenerator.FormGenerator;
import com.guerinet.formgenerator.TextViewFormItem;
import com.guerinet.utils.Utils;
import com.guerinet.utils.dialog.DialogUtils;

import junit.framework.Assert;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import javax.inject.Inject;

import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import ca.appvelopers.mcgillmobile.App;
import ca.appvelopers.mcgillmobile.R;
import ca.appvelopers.mcgillmobile.model.Place;
import ca.appvelopers.mcgillmobile.model.PlaceType;
import ca.appvelopers.mcgillmobile.ui.dialog.list.PlaceTypeListAdapter;
import ca.appvelopers.mcgillmobile.util.manager.HomepageManager;
import ca.appvelopers.mcgillmobile.util.manager.PlacesManager;
import timber.log.Timber;

/**
 * Displays a campus map
 * @author Ryan Singzon
 * @author Julien Guerinet
 * @author Quang Dao
 * @since 1.0.0
 */
public class MapActivity extends DrawerActivity implements OnMapReadyCallback, GoogleMap.OnMarkerClickListener {
    private static final int LOCATION_REQUEST = 101;
    /**
     * Info container used to show the current place's detail
     */
    @BindView(R.id.info_container)
    LinearLayout infoContainer;
    /**
     * {@link FormGenerator} container for the filter
     */
    @BindView(R.id.container)
    LinearLayout container;
    /**
     * Current place's title
     */
    @BindView(R.id.place_title)
    TextView title;
    /**
     * Current place's address
     */
    @BindView(R.id.place_address)
    TextView address;
    /**
     * Button to get directions to a place
     */
    @BindView(R.id.directions)
    Button directions;
    /**
     * Button to add or remove a place from the user's favorites
     */
    @BindView(R.id.map_favorite)
    Button favorite;
    /**
     * {@link PlacesManager} instance
     */
    @Inject
    PlacesManager placesManager;
    /**
     * Fragment containing the map
     */
    private GoogleMap map;
    /**
     * Total list of places with their associated markers
     */
    private List<Pair<Place, Marker>> places;
    /**
     * Currently shown map places with their associated markers
     */
    private List<Pair<Place, Marker>> shownPlaces;
    /**
     * Currently shown place with its associated marker
     */
    private Pair<Place, Marker> place;
    /**
     * Currently selected category
     */
    private PlaceType type;
    /**
     * Current search String
     */
    private String searchString;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_map);
        ButterKnife.bind(this);
        App.component(this).inject(this);
        analytics.sendScreen("Map");

        //Set up the initial information
        places = new ArrayList<>();
        shownPlaces = new ArrayList<>();
        searchString = "";
        type = new PlaceType(false);

        FormGenerator fg = FormGenerator.bind(this, container);

        // Icon coloring
        int red = ContextCompat.getColor(this, R.color.red);
        Utils.setTint(directions, 0, red);
        Utils.setTint(favorite, 0, red);

        //Set up the place filter
        fg.text(type.getString(this, languagePref.get())).leftIcon(R.drawable.ic_location)
                .rightIcon(R.drawable.ic_chevron_right, Color.GRAY).onClick(new TextViewFormItem.OnClickListener() {
                    @Override
                    public void onClick(final TextViewFormItem item) {
                        DialogUtils.list(MapActivity.this, R.string.map_filter,
                                new PlaceTypeListAdapter(MapActivity.this, type) {
                                    @Override
                                    public void onPlaceTypeSelected(PlaceType type) {
                                        MapActivity.this.type = type;

                                        //Update the text
                                        item.view().setText(
                                                type.getString(MapActivity.this, languagePreference.get()));

                                        //Update the filtered places
                                        filterByCategory();
                                    }
                                });
                    }
                }).build();

        FragmentManager manager = getSupportFragmentManager();
        //Get the MapFragment
        SupportMapFragment fragment = (SupportMapFragment) manager.findFragmentById(R.id.map);
        //If it's null, initialize it and put it in its view
        if (fragment == null) {
            fragment = SupportMapFragment.newInstance();
            manager.beginTransaction().replace(R.id.map, fragment).addToBackStack(null).commit();
        }

        fragment.getMapAsync(this);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.search, menu);

        // Get the SearchView
        MenuItem item = menu.findItem(R.id.action_search);
        Assert.assertNotNull(getSupportActionBar());
        final SearchView searchView = new SearchView(this);
        final int textViewID = searchView.getContext().getResources().getIdentifier("android:id/search_src_text",
                null, null);
        final AutoCompleteTextView searchTextView = (AutoCompleteTextView) searchView.findViewById(textViewID);
        try {
            // Set the cursor to the same color as the text
            Field cursorDrawable = TextView.class.getDeclaredField("mCursorDrawableRes");
            cursorDrawable.setAccessible(true);
            cursorDrawable.set(searchTextView, 0);
        } catch (Exception e) {
            Timber.e(e, "Cannot change color of cursor");
        }

        // Set up the query listener
        MenuItemCompat.setActionView(item, searchView);
        searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
            @Override
            public boolean onQueryTextSubmit(String query) {
                searchString = query;
                filterBySearchString();
                return false;
            }

            @Override
            public boolean onQueryTextChange(String newText) {
                searchString = newText;
                filterBySearchString();
                return false;
            }
        });

        //Reset the search view
        searchView.setOnCloseListener(new SearchView.OnCloseListener() {
            @Override
            public boolean onClose() {
                searchString = "";
                filterBySearchString();
                return false;
            }
        });

        return true;
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[],
            @NonNull int[] grantResults) {
        switch (requestCode) {
        case LOCATION_REQUEST:
            //Check if the permission has been granted
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                //Show the user on the map if that is the case
                if (map != null) {
                    //noinspection MissingPermission
                    map.setMyLocationEnabled(true);
                }
            }
            break;
        default:
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }

    @Override
    protected @HomepageManager.Homepage int getCurrentPage() {
        return HomepageManager.MAP;
    }

    /**
     * Opens Google Maps with directions to the chosen place
     */
    @OnClick(R.id.directions)
    void directions() {
        //Open Google Maps
        if (place != null) {
            Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://maps.google.com/maps?f=d &daddr="
                    + place.second.getPosition().latitude + "," + place.second.getPosition().longitude));
            startActivity(intent);
        }
    }

    /**
     * Adds or remove a place from the user's favorites
     */
    @OnClick(R.id.map_favorite)
    void favorites() {
        if (place != null) {
            String message;
            //Check if it was in the favorites
            if (placesManager.isFavorite(place.first)) {
                placesManager.removeFavorite(place.first);

                //Set the toast message
                message = getString(R.string.map_favorites_removed, place.first.getName());

                //Change the text to "Add Favorites"
                favorite.setText(R.string.map_favorites_add);

                //If we are in the favorites category, we need to hide this pin
                if (type.getId() == PlaceType.FAVORITES) {
                    place.second.setVisible(false);
                }
            } else {
                placesManager.addFavorite(place.first);

                //Set the toast message
                message = getString(R.string.map_favorites_added, place.first.getName());

                //Change the text to "Remove Favorites"
                favorite.setText(getString(R.string.map_favorites_remove));
            }

            //Alert the user
            Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
        }
    }

    /**
     * Shows or hides the given place
     *
     * @param place   The place
     * @param visible True if the place should be visible, false otherwise
     */
    private void showPlace(Pair<Place, Marker> place, boolean visible) {
        place.second.setVisible(visible);
        if (visible) {
            shownPlaces.add(place);
        }
    }

    /**
     * Filters the current places by the selected category
     */
    private void filterByCategory() {
        //Reset the current places
        shownPlaces.clear();

        //Go through the places
        for (Pair<Place, Marker> place : places) {
            switch (type.getId()) {
            //Show all of the places
            case PlaceType.ALL:
                showPlace(place, true);
                break;
            //Show only the favorite places
            case PlaceType.FAVORITES:
                showPlace(place, placesManager.isFavorite(place.first));
                break;
            //Show the places for the current category
            default:
                showPlace(place, place.first.isOfType(type));
                break;
            }
        }

        //Filter also by the search String if there is one
        filterBySearchString();
    }

    /**
     * Filters the current places by the entered search String
     */
    private void filterBySearchString() {
        //If there is no search String, just show everything
        if (searchString.isEmpty()) {
            for (Pair<Place, Marker> place : shownPlaces) {
                place.second.setVisible(true);
            }
            return;
        }

        //Keep track of the shown place if there's only one
        Marker shownPlace = null;
        boolean onePlace = false;
        for (Pair<Place, Marker> mapPlace : shownPlaces) {
            boolean visible = mapPlace.first.getName().toLowerCase().contains(searchString.toLowerCase());
            mapPlace.second.setVisible(visible);
            if (visible) {
                //If onePlace is already set, then set it back to false
                //  since there will be more than 2
                if (onePlace) {
                    onePlace = false;
                }

                if (shownPlace == null) {
                    //If there's no shown place, set it
                    shownPlace = mapPlace.second;
                    onePlace = true;
                }
            }
        }

        //If you're showing only one place, focus on that place
        if (onePlace && map != null) {
            map.animateCamera(CameraUpdateFactory.newLatLng(shownPlace.getPosition()));
        }
    }

    @Override
    public void onMapReady(GoogleMap googleMap) {
        map = googleMap;

        //Set the camera's center position to the McGill campus
        CameraPosition cameraPosition = new CameraPosition.Builder().target(new LatLng(45.504435, -73.576006))
                .zoom(14).bearing(-54).tilt(0).build();
        map.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));

        //Show the user's location if we have the permission to
        if (Utils.requestPermission(this, Manifest.permission.ACCESS_FINE_LOCATION, LOCATION_REQUEST)) {
            //noinspection MissingPermission
            map.setMyLocationEnabled(true);
        }
        //If we don't, it will be requested

        //Go through all of the places
        for (Place place : placesManager.getPlaces()) {
            //Create a MapPlace for this
            Marker marker = map
                    .addMarker(new MarkerOptions().position(place.getCoordinates()).draggable(false).visible(true));

            //Add it to the list
            places.add(new Pair<>(place, marker));
        }

        //Filter
        filterByCategory();

        map.setOnMarkerClickListener(this);
    }

    @Override
    public boolean onMarkerClick(Marker marker) {
        //If there was a marker that was selected before set it back to red
        if (place != null) {
            place.second.setIcon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_RED));
        }
        //Pull up the info container
        infoContainer.setVisibility(View.VISIBLE);

        //Find the concerned place
        place = null;
        for (Pair<Place, Marker> mapPlace : places) {
            if (mapPlace.second.equals(marker)) {
                place = mapPlace;
                break;
            }
        }

        if (place == null) {
            Timber.e("Tapped place marker was not found");
            return false;
        }

        //Set it to blue
        place.second.setIcon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_AZURE));

        //Set up the info
        title.setText(place.first.getName());
        address.setText(place.first.getAddress());

        //Set up the favorite text
        favorite.setText(
                placesManager.isFavorite(place.first) ? R.string.map_favorites_remove : R.string.map_favorites_add);

        return false;
    }
}