ca.cs.ualberta.localpost.view.MapsView.java Source code

Java tutorial

Introduction

Here is the source code for ca.cs.ualberta.localpost.view.MapsView.java

Source

/**
 * The MIT License (MIT)
 * Copyright (c) 2014 Timotei Albu, David Chau-Tran, Alain Clark, Shawn Anderson, Mickael Zerihoun
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 *   this software and associated documentation files (the "Software"), to deal in
 *   the Software without restriction, including without limitation the rights to
 *   use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 *   the Software, and to permit persons to whom the Software is furnished to do so,
 *   subject to the following conditions:
 *   
 *   The above copyright notice and this permission notice shall be included in all
 *   copies or substantial portions of the Software.
 *   
 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 *  FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 *  COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 *  IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 *  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *  
 */

package ca.cs.ualberta.localpost.view;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.location.Address;
import android.location.Geocoder;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import ca.cs.ualberta.localpost.controller.Serialize;
import ca.cs.ualberta.localpost.model.RootCommentModel;
import ca.cs.ualberta.localpost.model.StandardUserModel;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.GoogleMap.OnInfoWindowClickListener;
import com.google.android.gms.maps.GoogleMap.OnMarkerDragListener;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

/**
 * MapsView is a class that communicates with several other views to display users or comments location on map
 * Called when user submits or edits a comment; when user location is changed and when the map thread option is selected
 * @author team01
 *
 */
public class MapsView extends FragmentActivity implements OnInfoWindowClickListener, OnMarkerDragListener {

    // Google map object
    private GoogleMap googleMap;
    // Google marker to display on map
    private Marker googleMarker;
    // Coordinates based on latitude and longitude
    private LatLng latLng;
    // Address object containing all info about a certain point on map
    private Address address;
    // Model of the user
    private StandardUserModel user;
    // Array containing all comments
    private ArrayList<String> arrayComments;
    private RootCommentModel commentModel;

    // Variables used for receiving/reading intent from different applications using Google Maps
    private String MAP_VIEW_TYPE = "mapviewtype";
    private String THREAD_VIEW = "threadview";
    private String SUBMIT_VIEW = "submitvew";
    private String EDIT_COMMENT_VIEW = "editview";
    private String EDIT_USER_LOCATION_VIEW = "userlocationview";
    private String EDIT_COMMENT_MODEL = "editcomment";
    private String THREAD_COMMENT_MODEL = "threadcommentmodel";
    private String INTENT_OBJECT;
    private String INTENT_PURPOSE;

    Gson gson = new Gson();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.maps_view);

        // If map is not already setUp, then set it up
        setUpMapIfNeeded();

        // Load current user
        try {
            user = Serialize.loaduser(getApplicationContext());
        } catch (Exception e) {
            e.printStackTrace();
        }

        // Receive intent
        Intent extrasData = getIntent();
        INTENT_PURPOSE = extrasData.getStringExtra(MAP_VIEW_TYPE);

        // The following checks conditions to determine how to display the map
        // depending on where the intent was sent from and with what purpose
        //
        // Thread View
        if (INTENT_PURPOSE.equals(THREAD_VIEW)) {
            // Retrieve the comment model passed through intent
            INTENT_OBJECT = extrasData.getStringExtra(THREAD_COMMENT_MODEL);

            // Convert GSON string to ArrayList Type
            arrayComments = gson.fromJson(INTENT_OBJECT, new TypeToken<ArrayList<String>>() {
            }.getType());

            markerThreadView(arrayComments);
        }
        // Evaluates true when submitting a comment
        else if (INTENT_PURPOSE.equals(SUBMIT_VIEW) || INTENT_PURPOSE.equals(EDIT_USER_LOCATION_VIEW)) {
            // Set address to the users default location
            address = user.getAddress();

            // Set coordinates to the users default location
            latLng = new LatLng(address.getLatitude(), address.getLongitude());

            commentMarker();
        }
        // Evaluates true when editing a comment
        else if (INTENT_PURPOSE.equals(EDIT_COMMENT_VIEW)) {
            // Retrieve the comment model passed through intent
            INTENT_OBJECT = extrasData.getStringExtra(EDIT_COMMENT_MODEL);

            // Convert the gson string to a Root Comment Model
            commentModel = gson.fromJson(INTENT_OBJECT, RootCommentModel.class);

            // Open the map at the location where the comment was made
            latLng = new LatLng(commentModel.getAddress().getLatitude(), commentModel.getAddress().getLongitude());
            address = commentModel.getAddress();
            commentMarker();
        }

        // Getting reference to btn_find of the layout maps_view
        Button btn_find = (Button) findViewById(R.id.btn_find);

        // Defining button click event listener for the find button
        OnClickListener findClickListener = new FindButton();

        // Setting button click event listener for the find button
        btn_find.setOnClickListener(findClickListener);
    }

    /**
     * Arraylist of comments are sent to be displayed as markers on map
     * @param commentThread
     */
    @SuppressLint("SimpleDateFormat")
    private void markerThreadView(ArrayList<String> commentThread) {
        // Counter var and a date format one
        int i;
        SimpleDateFormat format = new SimpleDateFormat("HH:mm MM/dd/yy");

        // Clear all markers on map
        googleMap.clear();

        RootCommentModel fromJson = gson.fromJson(commentThread.get(0), RootCommentModel.class);

        // ArrayList of markers
        final List<Marker> mMarker = new ArrayList<Marker>();
        latLng = new LatLng(fromJson.getAddress().getLatitude(), fromJson.getAddress().getLongitude());

        // Store the location of the Top level comment for zooming purposes
        LatLng rootLocation = latLng;

        mMarker.add(googleMap.addMarker(new MarkerOptions().position(latLng)
                .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_AZURE))
                .title(fromJson.getContent() + " @ " + format.format(new Date(fromJson.getTimestamp())))));

        LatLngBounds.Builder mapBounds = LatLngBounds.builder();
        mapBounds = mapBounds.include(rootLocation);

        for (i = 1; i < commentThread.size(); i++) {

            RootCommentModel comment = gson.fromJson(commentThread.get(i), RootCommentModel.class);

            address = comment.getAddress();

            // Creating an instance of GeoPoint, to display in Google Map
            latLng = new LatLng(address.getLatitude(), address.getLongitude());

            mapBounds.include(latLng);

            mMarker.add(googleMap.addMarker(new MarkerOptions().position(latLng)
                    .title(comment.getAuthor() + " @ " + format.format(new Date(comment.getTimestamp())))));

        }
        LatLngBounds bounds = mapBounds.build();

        // Calculates where all markers would be and positions camera appropriately.
        // Source: http://stackoverflow.com/a/17825157
        try {
            googleMap.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 50)); //This line will cause the exception first times when map is still not "inflated"
            System.out.println("Set with padding");
        } catch (IllegalStateException e) {
            e.printStackTrace();
            googleMap.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 400, 400, 0));
            System.out.println("Set with wh");
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        setUpMapIfNeeded();
    }

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

    /**
     * This is where we can add markers or lines, add listeners or move the camera. In this case, we
     * just add a marker near Africa.
     * <p>
     * This should only be called once and when we are sure that {@link #mMap} is not null.
     */
    private void setUpMap() {
        // Enable my location
        googleMap.setMyLocationEnabled(true);
        // Setting the click event listener for the marker
        googleMap.setOnInfoWindowClickListener(this);
        googleMap.setOnMarkerDragListener(this);
    }

    /**
     * Method that instantiates the map if it hasn't been created yet
     */
    private void setUpMapIfNeeded() {
        // Do a null check to confirm that we have not already instantiated the map.
        if (googleMap == null) {
            // Try to obtain the map from the SupportMapFragment.
            googleMap = ((SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.mapView)).getMap();
            // Check if we were successful in obtaining the map.
            if (googleMap != null) {
                setUpMap();
            }
        }
    }

    /**
     * Initialize the marker when the map view is opened
     */
    private void commentMarker() {
        // Clear all marker currently on the map
        googleMap.clear();

        googleMarker = googleMap.addMarker(new MarkerOptions().position(latLng)
                .title("I'm here @ " + address.getAddressLine(0)).draggable(true).visible(true));

        googleMarker.showInfoWindow();

        // Pointing map view at my current location
        googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, 15.5f));
    }

    /**
     * Function that creates a list of addresses based on search query location 
     * @return a list of i addresses
     */
    private List<Address> addresses(int numberOfAddresses) {
        Geocoder geocoder = new Geocoder(this, Locale.getDefault());
        List<Address> addresses = null;
        try {
            addresses = geocoder.getFromLocation(latLng.latitude, latLng.longitude, numberOfAddresses);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return addresses;
    }

    /**
     * Looks up a location
     * @author timotei
     *
     */
    private final class FindButton implements OnClickListener {
        @Override
        public void onClick(View v) {
            // Getting reference to EditText to get the user input location
            EditText etLocation = (EditText) findViewById(R.id.et_location);

            // Getting user input location
            String location = etLocation.getText().toString();

            if (location != null && !location.equals("")) {
                new GeocoderTask().execute(location);
            }

            // Minimize the keyboard after the 'Find' button is pressed
            // Source: http://stackoverflow.com/questions/3400028/close-virtual-keyboard-on-button-press
            InputMethodManager inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);

            inputManager.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(),
                    InputMethodManager.HIDE_NOT_ALWAYS);
        }
    }

    // An AsyncTask class for accessing the GeoCoding Web Service
    private class GeocoderTask extends AsyncTask<String, Void, List<Address>> {

        @Override
        protected List<Address> doInBackground(String... locationName) {
            // Creating an instance of Geocoder class
            Geocoder geocoder = new Geocoder(getBaseContext());
            List<Address> addresses = null;

            try {
                // Getting a maximum of 3 Address that matches the input text
                addresses = geocoder.getFromLocationName(locationName[0], 3);
            } catch (IOException e) {
                e.printStackTrace();
            }
            return addresses;
        }

        @Override
        protected void onPostExecute(List<Address> addresses) {

            if (addresses == null || addresses.size() == 0) {
                Toast.makeText(getBaseContext(), "No Location found", Toast.LENGTH_SHORT).show();
            }

            // Clears all the existing markers on the map
            googleMap.clear();

            int i;
            // Adding Markers on Google Map for each matching address
            for (i = 0; i < addresses.size(); i++) {

                address = (Address) addresses.get(i);

                // Creating an instance of GeoPoint, to display in Google Map
                latLng = new LatLng(address.getLatitude(), address.getLongitude());

                String addressText = String.format("%s, %s",
                        address.getMaxAddressLineIndex() > 0 ? address.getAddressLine(0) : "",
                        address.getCountryName());

                googleMap.addMarker(
                        new MarkerOptions().position(latLng).title(addressText).draggable(true).visible(true))
                        .showInfoWindow();
            }

            // If search return one location, zoom to it, else have just a view of the map
            if (i == 1) {
                // Zooms camera with animation to the highest possible zoom at the current map location
                googleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(latLng, 15.5f));
            } else {
                googleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(latLng, 15.5f));
            }
        }
    }

    /**
     * Dialog box to determine whether the user is sure about the selection
     */
    private void alertDialog() {

        AlertDialog.Builder builder = new AlertDialog.Builder(this);

        builder.setMessage(R.string.dialog_message).setTitle(R.string.dialog_title)
                .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        finish();
                    }
                }).setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        // Close dialog
                        dialog.cancel();
                    }
                });

        AlertDialog dialog = builder.create();
        dialog.show();
    }

    /**
     * Method that passes an object of the address of the marker back to the previous activity, whose InfoWindow has been clicked      
     */
    @Override
    public void onInfoWindowClick(Marker marker) {
        if (INTENT_PURPOSE.equals(THREAD_VIEW)) {
            ;
        } else {
            Intent returnIntent = new Intent();
            Gson gson = new Gson();
            String string = gson.toJson(address);
            returnIntent.putExtra("address", string);
            setResult(RESULT_OK, returnIntent);
            alertDialog();
        }
    }

    /**
     * Called repeatedly while marker is being dragged
     */
    @Override
    public void onMarkerDrag(Marker arg0) {
        // Do no thing
    }

    /**
     * Called when a marker has finished being dragged
     */
    @Override
    public void onMarkerDragEnd(Marker marker) {
        latLng = marker.getPosition();

        googleMap.clear();

        // Get an address for current marker
        List<Address> addresses = addresses(1);
        address = (Address) addresses.get(0);

        marker = googleMap.addMarker(new MarkerOptions().position(latLng)
                .title("I'm here now @ " + address.getAddressLine(0)).draggable(true).visible(true));

        marker.showInfoWindow();

        // Pointing map view at my current location
        googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, 15.5f));

    }

    /**
     * Called when a marker starts being dragged
     */
    @Override
    public void onMarkerDragStart(Marker marker) {
        // Minimize the keyboard after the 'Find' button is pressed
        // Source: http://stackoverflow.com/questions/3400028/close-virtual-keyboard-on-button-press
        InputMethodManager inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);

        inputManager.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(),
                InputMethodManager.HIDE_NOT_ALWAYS);
    }
}