raychenon/gray-sky-weather

View on GitHub
app/src/main/java/io/betterapps/graysky/MainActivity.kt

Summary

Maintainability
A
3 hrs
Test Coverage
package io.betterapps.graysky

import android.app.Activity
import android.content.Context
import android.content.Intent
import android.location.Location
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.google.android.libraries.places.api.Places
import com.google.android.libraries.places.api.model.Place
import com.google.android.libraries.places.widget.Autocomplete
import com.google.android.libraries.places.widget.AutocompleteActivity
import com.google.android.libraries.places.widget.model.AutocompleteActivityMode
import es.dmoral.toasty.Toasty
import io.betterapps.graysky.const.GlobalConstants
import io.betterapps.graysky.data.db.entities.LocationEntity
import io.betterapps.graysky.data.domains.GeoLocation
import io.betterapps.graysky.data.domains.LocationName
import io.betterapps.graysky.geoloc.UserLocationDelegate
import io.betterapps.graysky.ui.main.MainViewModel
import io.betterapps.graysky.ui.weatherforecast.WeatherForecastFragment
import io.betterapps.graysky.ui.weatherforecast.onDeleteLocation
import kotlinx.android.synthetic.main.main_activity.*
import org.koin.android.viewmodel.ext.android.viewModel
import timber.log.Timber

class MainActivity : AppCompatActivity(), onDeleteLocation {

    // lazy inject
    val mainViewModel: MainViewModel by viewModel()

    private lateinit var userLocationDelegate: UserLocationDelegate
    private lateinit var locationNames: MutableList<LocationName>
    private var lastUserKnownLocation: GeoLocation? = null

    companion object {
        private val BUNDLE_GEOLOC = "GEOLOC_EXTRA"

        fun createIntent(context: Context, geolocEnabled: Boolean): Intent {
            val intent = Intent(context, MainActivity::class.java)
            intent.putExtra(BUNDLE_GEOLOC, geolocEnabled)
            return intent
        }
    }

    init {
        locationNames = mutableListOf()
        locationNames.addAll(GlobalConstants.CITIES)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContentView(R.layout.main_activity)

        mainViewModel.initialize()

        val extras = intent.extras
        extras?.let {
            val enabled = it.getBoolean(BUNDLE_GEOLOC)
            if (enabled) {
                userLocationDelegate = UserLocationDelegate(this)
                if (savedInstanceState == null) {
                    launchPermission()
                }
            } else {
                displayWeatherFromLocations(GlobalConstants.USER_LOCATION)
            }
        }

        // Initialize the SDK
        Places.initialize(applicationContext, getString(R.string.cloud_platform_api))
    }

    fun showWeatherFragments(locations: List<LocationName>) {
        val ft = supportFragmentManager.beginTransaction()
        var position = 0
        for (location in locations) {

            val fragment = WeatherForecastFragment.newInstance(
                location.name, location.geoLocation.latitude, location.geoLocation.longitude,
                location.distanceInKm, position++)
            fragment.setDeleteListener(this)
            ft.add(
                R.id.main_container,
                fragment,
                location.name
            )
        }
        ft.commitNow()
    }

    private fun launchPermission() {
        if (!userLocationDelegate.checkLocationPermission(this)) {
            userLocationDelegate.requestPermissions(::showRationale)
        } else {
            userLocationDelegate.getLastLocation(
                { loc ->
                    displayWeatherOrError(loc)
                }
            )
        }
    }

    private val AUTOCOMPLETE_REQUEST_CODE = 1

    fun launchAutocomplete(): Unit {

        // Set the fields to specify which types of place data to
        // return after the user has made a selection.
        val fields = listOf(
            Place.Field.ID,
            Place.Field.NAME,
            Place.Field.LAT_LNG,
            Place.Field.ADDRESS
        )

        // Start the autocomplete intent.
        val intent = Autocomplete.IntentBuilder(AutocompleteActivityMode.FULLSCREEN, fields)
            .build(this)
        startActivityForResult(intent, AUTOCOMPLETE_REQUEST_CODE)
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {

        if (requestCode == AUTOCOMPLETE_REQUEST_CODE) {
            when (resultCode) {
                Activity.RESULT_OK -> {
                    data?.let {
                        val place = Autocomplete.getPlaceFromIntent(data)
                        Timber.i("onActivityResult Place: ${place.name}, ${place.id}, ${place.latLng} , address = ${place.address} , \n , ${place.toString()}")

                        mainViewModel.addLocation(
                            LocationEntity(
                                place.id!!,
                                place.name!!,
                                place.address!!,
                                place.latLng!!.latitude,
                                place.latLng!!.longitude
                            )
                        )

                        val geolocation = lastUserKnownLocation ?: GlobalConstants.USER_LOCATION
                        displayWeatherFromLocations(geolocation)
                    }
                }
                AutocompleteActivity.RESULT_ERROR -> {
                    // TODO: Handle the error.
                    data?.let {
                        val status = Autocomplete.getStatusFromIntent(data)
                        Timber.i("onActivityResult ${status.statusMessage}")
                    }
                }
                Activity.RESULT_CANCELED -> {
                    Timber.i("onActivityResult cancelled")
                }
            }
            return
        }
        super.onActivityResult(requestCode, resultCode, data)
    }

    override fun onCreateOptionsMenu(menu: Menu?): Boolean {
        val inflater = menuInflater
        inflater.inflate(R.menu.menu_weather, menu)
        return true
    }

    override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
        R.id.action_add -> {
            launchAutocomplete()
            true
        }
        else -> {
            // If we got here, the user's action was not recognized.
            // Invoke the superclass to handle it.
            super.onOptionsItemSelected(item)
        }
    }

    private fun showRationale() {
        Toasty.error(this, R.string.permission_rationale, Toast.LENGTH_SHORT, true).show()
    }

    private fun showErrorLastLocation() {
        Toasty.error(this, R.string.permission_last_location_not_found, Toast.LENGTH_LONG, true)
            .show()
    }

    private fun displayWeatherOrError(location: Location?) {

        location?.let {
            val geoLocation = location2Geolocation(it)
            lastUserKnownLocation = geoLocation
            displayWeatherFromLocations(geoLocation)
        } ?: run {
            showErrorLastLocation()
        }
    }

    private fun displayWeatherFromLocations(currentUserlocation: GeoLocation) {
        // clean previous views
        main_container.removeAllViews()

        mainViewModel.sortByDistance(currentUserlocation)
            .observe(
                this,
                androidx.lifecycle.Observer {
                    it?.let { locations ->
                        showWeatherFragments(
                            locations
                        )
                    }
                }
            )
    }

    private fun location2Geolocation(location: Location): GeoLocation =
        GeoLocation(location.latitude, location.longitude)

    /**
     * Callback received when a permissions request has been completed.
     */
    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<String>,
        grantResults: IntArray
    ) {
        Timber.i("onRequestPermissionResult")
        userLocationDelegate.onRequestPermissionsResultDelete(
            requestCode,
            permissions,
            grantResults,
            { loc -> displayWeatherOrError(loc) }
        )
    }

    override fun onDelete(position: Int, locationName: String) {
        dialogDeleteConfirmation(position, locationName).show()
    }

    private fun dialogDeleteConfirmation(position: Int, locationName: String): AlertDialog {
        val builder = AlertDialog.Builder(this)
        builder.setTitle(R.string.location_delete_title)
        builder.setMessage(getString(R.string.location_delete_description, locationName))
            .setCancelable(false)
            .setPositiveButton(R.string.location_delete_confirm) { dialog, id ->
                main_container.removeViewAt(position)
                mainViewModel.deleteLocation(locationName)
            }
            .setNegativeButton(R.string.location_delete_no) { dialog, id ->
                dialog.cancel()
            }
        return builder.create()
    }
}