BlindlyTeam/Blindly

View on GitHub
app/src/main/java/ch/epfl/sdp/blindly/weather/WeatherActivity.kt

Summary

Maintainability
A
1 hr
Test Coverage
A
94%
package ch.epfl.sdp.blindly.weather

import android.content.Intent
import android.content.Intent.ACTION_INSERT
import android.os.Bundle
import android.provider.CalendarContract.*
import android.view.MenuItem
import android.view.View
import android.view.View.VISIBLE
import android.widget.*
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import ch.epfl.sdp.blindly.R
import ch.epfl.sdp.blindly.location.BlindlyLatLng
import ch.epfl.sdp.blindly.main_screen.profile.settings.LAUSANNE_LATLNG
import dagger.hilt.android.AndroidEntryPoint
import java.util.*
import java.util.concurrent.TimeUnit
import javax.inject.Inject


@AndroidEntryPoint
class WeatherActivity : AppCompatActivity(), WeatherService.WeatherResultCallback,
    SwipeRefreshLayout.OnRefreshListener {
    companion object {
        const val LOCATION = "location"
        private const val WEATHER_PREFIX = "weather_day_"
        private const val CALENDAR_EVENT_TITLE = "Blindly Date"
        private const val DEF_TYPE = "id"
    }

    @Inject
    lateinit var weather: WeatherService

    private lateinit var location: BlindlyLatLng
    private lateinit var calendarView: CalendarView
    private var calendar = Calendar.getInstance()
    private val today = Calendar.getInstance()

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

        val refreshLayout = findViewById<SwipeRefreshLayout>(R.id.swiperefresh)
        val eventButton = findViewById<Button>(R.id.eventButton)
        calendarView = findViewById(R.id.dateCalendarView)
        calendarView.firstDayOfWeek = Calendar.MONDAY

        // make today as the first clickable day
        calendarView.minDate = calendar.timeInMillis
        computeDayIndexAndSetBackground(
            today.get(Calendar.YEAR),
            today.get(Calendar.MONTH),
            today.get(Calendar.DAY_OF_MONTH)
        )

        // listen to changes to calendar and update weather images
        calendarView.setOnDateChangeListener { _, year, month, dayOfMonth ->
            computeDayIndexAndSetBackground(year, month, dayOfMonth)
        }

        //set calendar event if button is clicked
        eventButton.setOnClickListener {
            setCalendarEvent(CALENDAR_EVENT_TITLE, calendar)
        }

        refreshLayout.setOnRefreshListener(this)
        location = (intent.extras?.get(LOCATION) ?: BlindlyLatLng(LAUSANNE_LATLNG)) as BlindlyLatLng
        weather.nextWeek(location, callback = this)
        setRefreshing(true)

    }

    private fun temperatureToString(temperature: Double, unit: TemperatureUnit) =
        when (unit) {
            TemperatureUnit.IMPERIAL -> String.format(
                getString(R.string.fahrenheit_temperature),
                temperature.toInt()
            );
            TemperatureUnit.METRIC -> String.format(
                getString(R.string.celsius_temperature),
                temperature.toInt()
            );
        }

    /**
     * Maps the weather info to the view
     *
     * @param weather the weather to be shown
     */
    private fun setWeatherInfo(weather: WeekWeather) {
        weather.daily.forEachIndexed { index, dayWeather ->
            dayWeather.weather[0].getIconDrawableId()?.let { setIcon(index, it) }
            val unit = dayWeather.temperature.unit
            setDay(index, temperatureToString(dayWeather.temperature.day, unit))
            setEvening(index, temperatureToString(dayWeather.temperature.evening, unit))
            dayWeather.day?.let { setDayName(index, it) }
            setContainerVisibility(index, VISIBLE)
        }
    }

    private fun setIcon(index: Int, drawableId: Int) {
        val iconId = resources.getIdentifier(
            "${WEATHER_PREFIX}${(index + 1)}_icon",
            DEF_TYPE ,
            packageName
        )
        findViewById<ImageView>(iconId)?.setImageDrawable(
            ContextCompat.getDrawable(
                applicationContext,
                drawableId
            )
        )
    }

    private fun setDay(index: Int, text: String) {
        val day = resources.getIdentifier(
            "${WEATHER_PREFIX}${(index + 1)}_day", "id",
            packageName
        )
        findViewById<TextView>(day)?.text = text
    }

    private fun setEvening(index: Int, text: String) {
        val evening = resources.getIdentifier(
            "${WEATHER_PREFIX}${(index + 1)}_evening",
            DEF_TYPE ,
            packageName
        )
        findViewById<TextView>(evening)?.text = text
    }

    private fun setDayName(index: Int, text: String) {
        val dayName = resources.getIdentifier(
            "${WEATHER_PREFIX}${(index + 1)}_name",
            DEF_TYPE ,
            packageName
        )
        findViewById<TextView>(dayName)?.text = text
    }

    private fun setContainerVisibility(index: Int, visibility: Int) {
        val containerId = resources.getIdentifier(
            "${WEATHER_PREFIX}${(index + 1)}",
            DEF_TYPE ,
            packageName
        )
        findViewById<View>(containerId)?.visibility = visibility
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        val id: Int = item.itemId
        if (id == R.id.menu_refresh) {
            // We manually set refreshing so it's clearer that the app is doing a refresh
            setRefreshing(true)
            onRefresh()
        }
        return super.onOptionsItemSelected(item)
    }

    override fun onWeatherFailure(e: Exception) {
        runOnUiThread {
            val duration = Toast.LENGTH_LONG
            val toast = Toast.makeText(
                applicationContext,
                getString(R.string.weather_update_failed),
                duration
            )
            toast.show()
            setRefreshing(false)
        }
    }

    override fun onWeatherResponse(weather: WeekWeather) {
        // As the request is performed on another thread we have to switch back to the UI thread to
        // perform the updates
        runOnUiThread {
            setWeatherInfo(weather)
            setRefreshing(false)
        }
    }

    private fun setRefreshing(isRefreshing: Boolean) {
        findViewById<SwipeRefreshLayout>(R.id.swiperefresh).isRefreshing = isRefreshing
    }

    /**
     * Refresh weather data when user swipes down
     *
     */
    override fun onRefresh() {
        weather.nextWeek(location, callback = this)
    }

    /**
     * Forms a new Calendar Event in Google Calendar App,
     * closes the current activity as this page is no longer needed.
     *
     * @param title Title of the event to add to Calendar
     * @param date Date of the event to add to Calendar
     */
    private fun setCalendarEvent(title: String, date: Calendar) {
        val calIntent = Intent(ACTION_INSERT)
        calIntent.data = Events.CONTENT_URI
        calIntent.putExtra(Events.TITLE, title)
        calIntent.putExtra(EXTRA_EVENT_ALL_DAY, true)
        calIntent.putExtra(
            EXTRA_EVENT_BEGIN_TIME,
            date.timeInMillis
        )
        calIntent.putExtra(
            EXTRA_EVENT_END_TIME,
            date.timeInMillis
        )
        finish()
        startActivity(calIntent)
    }

    /**
     * Takes the new date, compares with today and gets the result as an index.
     * Then sends the result to setColoredBackgroundForSelectedDay to highlight
     * the weather of the chosen day
     *
     * @param year Year of selected date
     * @param month Month of selected date
     * @param dayOfMonth Day of selected date
     */
    private fun computeDayIndexAndSetBackground(year: Int, month: Int, dayOfMonth: Int) {
        calendar.set(year, month, dayOfMonth)
        calendarView.date = calendar.timeInMillis
        val selectedDate = Calendar.getInstance()
        selectedDate.set(year, month, dayOfMonth)
        val diff: Long = selectedDate.timeInMillis - today.timeInMillis
        val dayIndex = TimeUnit.MILLISECONDS.toDays(diff)

        setColoredBackgroundForSelectedDay(dayIndex)
    }

    /**
     * If index is between the days we showed, (0 to 5 included, with 0 as today)
     * then highlight that day, otherwise set the background to white.
     *
     * @param indexOfDay Index of the day chosen (with 0 as today)
     */
    private fun setColoredBackgroundForSelectedDay(indexOfDay: Long) {
        for (i in 0..5) {
            val layoutId = resources.getIdentifier(
                "${WEATHER_PREFIX}${(i + 1)}",
                DEF_TYPE ,
                packageName
            )
            if (i.toLong() == indexOfDay) {

                findViewById<LinearLayout>(layoutId).setBackgroundColor(
                    ContextCompat.getColor(
                        applicationContext,
                        R.color.blindly_blue_transparent
                    )
                )
            } else {
                findViewById<LinearLayout>(layoutId).setBackgroundColor(
                    ContextCompat.getColor(
                        applicationContext,
                        R.color.white
                    )
                )
            }
        }
    }

}