whitewind664/sdp

View on GitHub
app/src/main/java/com/github/gogetters/letsgo/activities/MapsActivity.kt

Summary

Maintainability
A
0 mins
Test Coverage
F
51%
package com.github.gogetters.letsgo.activities

import android.Manifest
import android.app.AlertDialog
import android.content.DialogInterface
import android.content.Intent
import android.content.pm.PackageManager
import android.location.Location
import android.os.Bundle
import android.util.Log
import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.github.gogetters.letsgo.R
import com.github.gogetters.letsgo.database.Database
import com.github.gogetters.letsgo.database.user.FirebaseUserBundleProvider
import com.github.gogetters.letsgo.database.user.LetsGoUser
import com.github.gogetters.letsgo.util.PermissionUtils.isPermissionGranted
import com.github.gogetters.letsgo.util.PermissionUtils.requestPermission
import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationServices
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.*
import kotlin.math.max
import kotlin.math.min


class MapsActivity : AppCompatActivity(), OnMapReadyCallback,
    GoogleMap.OnMyLocationButtonClickListener,
    GoogleMap.OnMyLocationClickListener, ActivityCompat.OnRequestPermissionsResultCallback {
    companion object {
        private const val LOCATION_PERMISSION_REQUEST_CODE: Int = 1
        private val EPFL_LAT = 46.51899505106699
        private val EPFL_LNG = 6.563449219980816
        private val EPFL: LatLng = LatLng(EPFL_LAT, EPFL_LNG)
        private const val INIT_ZOOM = 10f
        private const val TOAST_DURATION = Toast.LENGTH_SHORT
        private const val MARKER_DISPLAY_PADDING = 0

        // indices in the dialog
        private const val DIALOG_CANCEL_IDX = 1
        private const val DIALOG_CHAT_IDX = 0
    }

    private var isMapReady = false

    private val userBundleProvider = FirebaseUserBundleProvider
    private var permissionDenied = false
    private lateinit var mMap: GoogleMap
    private lateinit var fusedLocationClient: FusedLocationProviderClient
    private var userMarkers: Map<Marker, String> = emptyMap()
    private var otherUsersActivated: Boolean = false

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_maps)

        // Obtain the SupportMapFragment
        val mapFragment = supportFragmentManager
            .findFragmentById(R.id.map) as SupportMapFragment
        mapFragment.getMapAsync(this)

        fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
    }

    /**
     * Manipulates the map once available.
     * If Google Play services is not installed on the device, the user will be prompted to install
     * it inside the SupportMapFragment. This method will only be triggered once the user has
     * installed Google Play services and returned to the app.
     */
    override fun onMapReady(googleMap: GoogleMap) {
        mMap = googleMap
        isMapReady = true

        // Move the camera to EPFL
        mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(EPFL, INIT_ZOOM))
        mMap.setOnMarkerClickListener { marker ->
            displayUserInfo(marker.tag as String)
            true
        }

        // set listeners for buttons
        val showPlayersButton = findViewById<Button>(R.id.map_button_showPlayers)
        showPlayersButton.setOnClickListener {
            this.activateAndUpdateOtherPlayers()
        }

        googleMap.setOnMyLocationButtonClickListener(this)
        googleMap.setOnMyLocationClickListener(this)
        enableLocation()
        sendLocation()
    }


    private fun enableLocation() {
        if (!::mMap.isInitialized)
            return

        if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
            == PackageManager.PERMISSION_GRANTED
        ) {
            mMap.isMyLocationEnabled = true
        } else {
            // Permission to access the location is missing. Show rationale and request permission
            requestPermission(this, LOCATION_PERMISSION_REQUEST_CODE, Manifest.permission.ACCESS_FINE_LOCATION)
        }
    }

    private fun sendLocation() {
        try {
            val userBundle = userBundleProvider.getUserBundle()
            if (!permissionDenied && userBundle != null) {
                Log.i("MapsActivity", "Create request")
                fusedLocationClient.lastLocation
                    .addOnSuccessListener { location: Location? ->
                        // Got last known location. In some rare situations this can be null.
                        if (location == null) {
                            Toast.makeText(
                                this,
                                resources.getString(R.string.map_permissionSharingFailed),
                                TOAST_DURATION
                            ).show()
                            Log.v("MapsActivity", "Location could not be shared")
                        } else {
                            // share the location with other users
                            userBundle.getUser().shareLocation(
                                LatLng(
                                    location.latitude,
                                    location.longitude
                                )
                            )
                        }
                    }
            }
        } catch (e: SecurityException) {
            Log.e("Exception: %s", e.message, e)
        }
    }

    /**
     *  Gets the positions of other users and displays them on the map
     */
    private fun activateAndUpdateOtherPlayers() {
        if (!::mMap.isInitialized)
            return

        otherUsersActivated = true

        Database.getAllLocations().thenApply {
            if (it == null || it.isEmpty()) {
                Toast.makeText(
                    this,
                    resources.getString(R.string.map_noPlayersFound),
                    TOAST_DURATION
                ).show()
            } else {
                setOtherPlayers(it)
            }
        }

    }

    private fun setOtherPlayers(updatedUsers: Map<LatLng, String>) {
        if (otherUsersActivated) {
            removeAllOtherPlayers()
            var allPositions: LatLngBounds.Builder = LatLngBounds.Builder()
            for ((playerPosition, id) in updatedUsers.entries) {
                Log.d("TEST MAP", "fetched id $id")
                val marker = mMap.addMarker(MarkerOptions().position(playerPosition).title("$id").icon(BitmapDescriptorFactory.fromResource(R.drawable.person_pin)))
                marker.tag = id  // the userId needs to be stored
                userMarkers = userMarkers + Pair(marker, id)
                allPositions.include(marker.position)
            }
            // update the camera zoom
            val cu = CameraUpdateFactory.newLatLngBounds(allPositions.build(), MARKER_DISPLAY_PADDING)
            mMap.animateCamera(cu)
        }
    }

    /**
     * Removes all markers of other users from the map
     */
    private fun removeAllOtherPlayers() {
        for ((marker, _) in userMarkers.entries) {
            marker.remove()
        }
        userMarkers = emptyMap()
    }

    override fun onMyLocationButtonClick(): Boolean {
        // Return false so that we don't consume the event and the default behavior still occurs
        // (the camera animates to the user's current position).
        return false
    }

    override fun onMyLocationClick(location: Location) {
        // TODO display information about my user when clicking on own position
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<String>,
        grantResults: IntArray
    ) {
        if (requestCode != LOCATION_PERMISSION_REQUEST_CODE) return
        if (isPermissionGranted(permissions, grantResults, Manifest.permission.ACCESS_FINE_LOCATION)) {
            // Enable the my location layer if the permission has been granted.
            enableLocation()
        } else {
            // Permission was denied
            permissionDenied = true
        }
    }

    /**
     *  Show error message when permission was denied
     */
    override fun onResumeFragments() {
        super.onResumeFragments()
        if (permissionDenied) {
            // Permission was not granted, display error dialog.
            Toast.makeText(this, resources.getString(R.string.map_permissionDeniedError), TOAST_DURATION).show()
            permissionDenied = false
        }
    }

    /**
     * Called when clicked on a marker on the map. Displays a dialog that allows to open a chat with a found user.
     */
    private fun displayUserInfo(otherId: String) {
        val dialogTexts = arrayOf<CharSequence>(
            resources.getString(R.string.map_dialogOpenChat),
            resources.getString(R.string.map_dialogCancel)
        )
        val builder: AlertDialog.Builder = AlertDialog.Builder(this)
        builder.setTitle(resources.getString(R.string.map_dialogTitlePrefix))
        builder.setItems(dialogTexts, DialogInterface.OnClickListener { dialog, clickedIndex ->
            if (clickedIndex == DIALOG_CANCEL_IDX) {
                dialog.dismiss()
                return@OnClickListener
            } else {
                if (userBundleProvider.getUserBundle() == null) {
                    // open login
                    val intent = Intent(this, LoginActivity::class.java)
                    startActivity(intent)
                } else {
                    // open chat with the user
                    val intent = Intent(this, ChatActivity::class.java)
                    intent.putExtra(ChatNewMessageActivity.KEY, LetsGoUser(otherId))
                    startActivity(intent)
                }
            }
        })
        builder.show()
    }
}