SDPTeam15/PolyEvents

View on GitHub
app/src/main/java/com/github/sdpteam15/polyevents/view/fragments/MapsFragment.kt

Summary

Maintainability
C
7 hrs
Test Coverage
F
44%
package com.github.sdpteam15.polyevents.view.fragments

import android.Manifest
import android.annotation.SuppressLint
import android.content.pm.PackageManager
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import com.github.sdpteam15.polyevents.R
import com.github.sdpteam15.polyevents.helper.HelperFunctions
import com.github.sdpteam15.polyevents.model.map.*
import com.github.sdpteam15.polyevents.view.PolyEventsApplication
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.GoogleMap.*
import com.google.android.gms.maps.MapFragment
import com.google.android.gms.maps.OnMapReadyCallback
import com.google.android.gms.maps.SupportMapFragment
import com.google.android.gms.maps.model.LatLng
import com.google.android.gms.maps.model.Marker
import com.google.android.gms.maps.model.Polygon
import com.google.android.gms.maps.model.Polyline
import com.google.android.material.floatingactionbutton.FloatingActionButton

/**
 * Fragment that displays the map
 */
class MapsFragment(private val mod: MapsFragmentMod) : Fragment(),
    OnMapReadyCallback,
    OnPolylineClickListener,
    OnPolygonClickListener, OnMarkerClickListener, OnInfoWindowClickListener, OnMarkerDragListener,
    OnMyLocationButtonClickListener, OnMapClickListener {


    companion object {
        const val TAG = "MapsFragment"
        var instance: MapsFragment? = null
    }

    private lateinit var locationButton: FloatingActionButton

    private lateinit var addNewAreaButton: FloatingActionButton
    private lateinit var deleteAreaButton: FloatingActionButton
    private lateinit var saveNewAreaButton: FloatingActionButton
    private lateinit var editAreaButton: FloatingActionButton

    private lateinit var locateMeButton: FloatingActionButton
    private lateinit var saveButton: FloatingActionButton
    private lateinit var heatmapButton: FloatingActionButton

    private lateinit var addNewRouteButton: FloatingActionButton
    private lateinit var removeRouteButton: FloatingActionButton
    private lateinit var saveNewRouteButton: FloatingActionButton


    var locationPermissionGranted = false
    private var useUserLocation = false

    // We have to do this since Cirrus doesn't allow us to test load google map
    private val showMap:Boolean
        get() = !PolyEventsApplication.inTest

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {

        val view = inflater.inflate(R.layout.fragment_maps, container, false)

        instance = this

        setUpButtons(view)

        setUpButtonsVisibility()

        GoogleMapHelperFunctions.getAllZonesFromDB(requireContext(), this, mod)

        setUpButtonsListeners()

        locationButton.tag = R.drawable.ic_location_on

        locateMeButton.tag = R.drawable.ic_locate_me

        return view
    }

    override fun onPause() {
        super.onPause()
        if(showMap){
            GoogleMapOptions.saveCamera()
            GoogleMapHeatmap.resetHeatmap()
        }

    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        if (showMap) {
            val mapFragment =
                childFragmentManager.findFragmentById(R.id.id_fragment_map) as SupportMapFragment?
            mapFragment?.getMapAsync(this)
            if (!locationPermissionGranted) {
                HelperFunctions.getLocationPermission(requireActivity()).observeOnce {
                    locationPermissionGranted = it.value
                    activateMyLocation()
                }
            }
        }
    }

    override fun onMapReady(googleMap: GoogleMap?) {
        if (showMap) {
            GoogleMapHelper.map = GoogleMapAdapter(googleMap)
            RouteMapHelper.getNodesAndEdgesFromDB(context, this)

        setMapListeners(googleMap!!)
        GoogleMapOptions.setUpMap(requireContext(), mod != MapsFragmentMod.EditZone, mod)

            if (useUserLocation) {
                activateMyLocation()
            }
        }
    }


    //----- HELPER FUNCTIONS -------

    /**
     * Gets the references of the floating buttons to set listeners and visibility
     * @param view view on which the buttons are
     */
    private fun setUpButtons(view: View) {
        locationButton = view.findViewById(R.id.id_location_button)

        addNewAreaButton = view.findViewById(R.id.addNewArea)
        deleteAreaButton = view.findViewById(R.id.id_delete_areas)
        saveNewAreaButton = view.findViewById(R.id.acceptNewArea)
        editAreaButton = view.findViewById(R.id.id_edit_area)

        locateMeButton = view.findViewById(R.id.id_locate_me_button)
        saveButton = view.findViewById(R.id.saveAreas)
        heatmapButton = view.findViewById(R.id.id_heatmap)

        addNewRouteButton = view.findViewById(R.id.addNewRoute)
        removeRouteButton = view.findViewById(R.id.removeRoute)
        saveNewRouteButton = view.findViewById(R.id.saveNewRoute)
    }

    /**
     * Sets the visibility of the buttons acording to the mode
     */
    private fun setUpButtonsVisibility() {
        when (mod) {
            MapsFragmentMod.Visitor -> {
                locateMeButton.visibility = View.VISIBLE
                heatmapButton.visibility = View.VISIBLE
                locationButton.visibility = View.VISIBLE

                addNewAreaButton.visibility = View.INVISIBLE
                deleteAreaButton.visibility = View.INVISIBLE
                saveNewAreaButton.visibility = View.INVISIBLE
                editAreaButton.visibility = View.INVISIBLE
                saveButton.visibility = View.INVISIBLE

                addNewRouteButton.visibility = View.INVISIBLE
                removeRouteButton.visibility = View.INVISIBLE
                saveNewRouteButton.visibility = View.INVISIBLE
            }
            MapsFragmentMod.EditZone -> {
                locateMeButton.visibility = View.INVISIBLE
                heatmapButton.visibility = View.INVISIBLE
                locationButton.visibility = View.INVISIBLE

                addNewAreaButton.visibility = View.VISIBLE
                deleteAreaButton.visibility = View.VISIBLE
                saveNewAreaButton.visibility = View.VISIBLE
                editAreaButton.visibility = View.VISIBLE
                saveButton.visibility = View.VISIBLE

                addNewRouteButton.visibility = View.INVISIBLE
                removeRouteButton.visibility = View.INVISIBLE
                saveNewRouteButton.visibility = View.INVISIBLE
            }
            MapsFragmentMod.EditRoute -> {
                locateMeButton.visibility = View.INVISIBLE
                heatmapButton.visibility = View.INVISIBLE
                locationButton.visibility = View.INVISIBLE

                addNewAreaButton.visibility = View.INVISIBLE
                deleteAreaButton.visibility = View.INVISIBLE
                saveNewAreaButton.visibility = View.INVISIBLE
                editAreaButton.visibility = View.INVISIBLE
                saveButton.visibility = View.INVISIBLE

                addNewRouteButton.visibility = View.VISIBLE
                removeRouteButton.visibility = View.VISIBLE
                saveNewRouteButton.visibility = View.INVISIBLE
            }
        }
    }

    /**
     * Sets the onClickListeners to the buttons
     */
    private fun setUpButtonsListeners() {
        addNewAreaButton.setOnClickListener { ZoneAreaMapHelper.createNewArea(requireContext()) }
        saveNewAreaButton.setOnClickListener { ZoneAreaMapHelper.saveNewArea(requireContext()) }
        editAreaButton.setOnClickListener { ZoneAreaMapHelper.editMode(requireContext()) }
        deleteAreaButton.setOnClickListener { ZoneAreaMapHelper.deleteMode(requireContext()) }

        addNewRouteButton.setOnClickListener { RouteMapHelper.createNewRoute(requireContext()) }
        removeRouteButton.setOnClickListener { RouteMapHelper.removeRoute() }
        saveNewRouteButton.setOnClickListener { RouteMapHelper.saveNewRoute() }

        heatmapButton.setOnClickListener { GoogleMapHeatmap.heatmap() }

        saveButton.setOnClickListener { ZoneAreaMapHelper.saveArea() }

        locationButton.setOnClickListener { switchLocationOnOff() }
        locateMeButton.setOnClickListener { moveToMyLocation() }
    }

    /**
     * Sets the listeners of the map
     */
    fun setMapListeners(googleMap: GoogleMap) {
        googleMap.setOnPolylineClickListener(this)
        googleMap.setOnPolygonClickListener(this)
        googleMap.setOnMarkerClickListener(this)
        googleMap.setOnMarkerDragListener(this)
        googleMap.setOnInfoWindowClickListener(this)
        googleMap.setOnMyLocationButtonClickListener(this)
        googleMap.setOnMapClickListener(this)
    }

    /**
     * Switches the style of the delete button for routes
     */
    fun switchIconDelete() {
        val removeRouteButton = requireView().findViewById<FloatingActionButton>(R.id.removeRoute)
        if (RouteMapHelper.deleteMode) {
            removeRouteButton.supportBackgroundTintList =
                resources.getColorStateList(R.color.red, null)
        } else {
            removeRouteButton.supportBackgroundTintList =
                resources.getColorStateList(R.color.teal_200, null)
        }
    }

    /**
     * Switches the style of the delete button for routes
     */
    fun switchIconDeleteArea() {
        val removeRouteButton =
            requireView().findViewById<FloatingActionButton>(R.id.id_delete_areas)
        if (ZoneAreaMapHelper.deleteMode) {
            removeRouteButton.supportBackgroundTintList =
                resources.getColorStateList(R.color.red, null)
        } else {
            removeRouteButton.supportBackgroundTintList =
                resources.getColorStateList(R.color.teal_200, null)
        }
    }

    /**
     * Set the visibility of the save button for routes
     */
    fun showSaveButton() {
        val removeRouteButton = requireView().findViewById<FloatingActionButton>(R.id.saveNewRoute)
        if (RouteMapHelper.tempPolyline != null) {
            removeRouteButton.visibility = View.VISIBLE
        } else {
            removeRouteButton.visibility = View.INVISIBLE
        }
    }

    //-----------START LISTENER---------------------------------------

    override fun onPolygonClick(polygon: Polygon) =
        GoogleMapActionHandler.onPolygonClickHandler(
            mod,
            requireContext(),
            polygon,
            activity = requireActivity(),
            lifecycle = this,
            locationActivated = useUserLocation
        )

    override fun onMarkerClick(marker: Marker): Boolean {
        GoogleMapActionHandler.onMarkerClickHandler(
            marker, mod,
            activity = requireActivity(), lifecycle = this, locationActivated = useUserLocation
        )

        //Return true to say that we don't want the event to go further (to the usual event when a marker is clicked)
        return true
    }

    override fun onPolylineClick(polyline: Polyline) =
        GoogleMapActionHandler.polylineClick(polyline)

    override fun onInfoWindowClick(marker: Marker) =
        GoogleMapActionHandler.onInfoWindowClickHandler(
            requireActivity(),
            this,
            marker,
            useUserLocation
        )

    override fun onMarkerDragEnd(marker: Marker) =
        GoogleMapActionHandler.interactionMarkerHandler(marker, MarkerDragMode.DRAG_END)

    override fun onMarkerDragStart(marker: Marker) =
        GoogleMapActionHandler.interactionMarkerHandler(marker, MarkerDragMode.DRAG_START)

    override fun onMarkerDrag(marker: Marker) =
        GoogleMapActionHandler.interactionMarkerHandler(marker, MarkerDragMode.DRAG)

    override fun onMyLocationButtonClick(): Boolean {
        // Return false to indicate that we did not consume the event. So the default behavior
        // still occurs (which is to move the camera to the current location.
        return false
    }

    override fun onMapClick(pos: LatLng?) =
        GoogleMapActionHandler.onMapClickHandler(pos)

    //-----------END LISTENER---------------------------------------

    /**
     * Activate "my location" tracking.
     * (source : https://developers.google.com/maps/documentation/android-sdk/location#kotlin)
     */
    private fun activateMyLocation() {
        if (context?.let {
                ContextCompat.checkSelfPermission(
                    it,
                    Manifest.permission.ACCESS_FINE_LOCATION
                )
            }
            == PackageManager.PERMISSION_GRANTED) {
            GoogleMapHelper.map!!.isMyLocationEnabled = true

            // Hide the built-in location button (but DO NOT disable it !)
            getBuiltInLocationButton().isVisible = false

            // Change the appearance of the location button.
            setLocationIcon(true)
            useUserLocation = true
        }
    }

    /**
     * Update the location button icon according to whether the location is on or not
     * @param locationIsOn : boolean, true if the location is currently used
     */
    private fun setLocationIcon(locationIsOn: Boolean) {
        val idOfResource: Int = if (locationIsOn) {
            R.drawable.ic_location_on
        } else {
            R.drawable.ic_location_off
        }
        requireView().findViewById<FloatingActionButton>(R.id.id_location_button)
            .setImageResource(idOfResource)
        locationButton.tag = idOfResource
    }

    /**
     * Switch the location to on or off according to the current state.
     * Accordingly update the icon.
     */
    @SuppressLint("MissingPermission")
    private fun switchLocationOnOff() {
        val locationIsOn = GoogleMapHelper.map!!.isMyLocationEnabled
        if (locationIsOn) {
            // Disable the location
            GoogleMapHelper.map!!.isMyLocationEnabled = false
            setLocationIcon(false)
            useUserLocation = false
        } else {
            activateMyLocation()
        }
    }

    /**
     * Get and return the built-in "my location" button from map fragment.
     * @return the view of the built-in location button
     */
    private fun getBuiltInLocationButton(): View {
        val mapFragment =
            childFragmentManager.findFragmentById(R.id.id_fragment_map) as SupportMapFragment?

        // Magic : https://stackoverflow.com/questions/36785542/how-to-change-the-position-of-my-location-button-in-google-maps-using-android-st
        return (mapFragment!!.requireView()
            .findViewById<View>(Integer.parseInt("1")).parent as View)
            .findViewById(Integer.parseInt("2"))
    }

    /**
     * Click on the built-in my location button so that
     * the default effect occurs (move camera to current location).
     */
    private fun moveToMyLocation() =
        getBuiltInLocationButton().performClick()

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<String>,
        grantResults: IntArray
    ) = HelperFunctions.onRequestPermissionsResult(requestCode, permissions, grantResults)
}