SDPTeam15/PolyEvents

View on GitHub
app/src/main/java/com/github/sdpteam15/polyevents/view/activity/admin/EventManagementActivity.kt

Summary

Maintainability
A
2 hrs
Test Coverage
A
96%
package com.github.sdpteam15.polyevents.view.activity.admin

import android.annotation.SuppressLint
import android.app.DatePickerDialog
import android.app.TimePickerDialog
import android.os.Bundle
import android.view.View
import android.widget.*
import androidx.appcompat.app.AppCompatActivity
import com.github.sdpteam15.polyevents.R
import com.github.sdpteam15.polyevents.helper.HelperFunctions
import com.github.sdpteam15.polyevents.helper.HelperFunctions.localDatetimeToString
import com.github.sdpteam15.polyevents.helper.HelperFunctions.showProgressDialog
import com.github.sdpteam15.polyevents.model.database.remote.Database.currentDatabase
import com.github.sdpteam15.polyevents.model.entity.Event
import com.github.sdpteam15.polyevents.model.entity.UserEntity
import com.github.sdpteam15.polyevents.model.entity.Zone
import com.github.sdpteam15.polyevents.model.observable.Observable
import com.github.sdpteam15.polyevents.model.observable.ObservableList
import com.github.sdpteam15.polyevents.view.PolyEventsApplication
import kotlinx.coroutines.Dispatchers
import java.time.LocalDateTime

/**
 * Activity to manage the event
 */
class EventManagementActivity : AppCompatActivity() {
    companion object {
        /**
         * All constant needed for the current activity
         */
        const val MIN_PART_NB = 1
        const val EMPTY_PART_NB = "0"
        const val TYPE_START = "start"
        const val TYPE_END = "END"
        val dateStart = Observable<LocalDateTime>()
        val dateEnd = Observable<LocalDateTime>()
        private const val defaultPartNb = "10"

        /**
         * Create a LocalDateTime from the parameters and post its value into the correct observable
         * @param type which date field to update (TYPE_START or TYPE_END)
         * @param year the year of the date
         * @param month the month of the date
         * @param day the day of the date
         * @param hour the hour of the date
         * @param minute the minute of the date
         */
        fun postValueDate(type: String, year: Int, month: Int, day: Int, hour: Int, minute: Int) {
            if (type == TYPE_START) {
                dateStart.postValue(
                    LocalDateTime.of(
                        year,
                        month,
                        day,
                        hour,
                        minute
                    )
                )
            } else {
                dateEnd.postValue(
                    LocalDateTime.of(
                        year,
                        month,
                        day,
                        hour,
                        minute
                    )
                )
            }
        }
    }

    private val zoneName = ArrayList<String>()
    private val mapIndexToId: MutableMap<Int, String> = mutableMapOf()
    private val organiserName = ArrayList<String>()
    private val mapIndexToOrganiserId: MutableMap<Int, String> = mutableMapOf()
    private val zoneObserver = ObservableList<Zone>()
    private val organiserObserver = ObservableList<UserEntity>()
    private lateinit var dialogStartDate: DatePickerDialog
    private lateinit var dialogEndDate: DatePickerDialog
    private lateinit var dialogStartTime: TimePickerDialog
    private lateinit var dialogEndTime: TimePickerDialog
    private var isCreation: Boolean = true
    private var curId = ""
    private val observableEvent = Observable<Event>()

    // True if the current user is not an admin and it is not modifying a currently existing event edit request
    private var isActivityProvider = false

    // True if the current user is not an admin and he is currently editing an event edit request.
    private var isModificationActivityProvider = false

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_event_management)
        supportActionBar!!.setDisplayShowHomeEnabled(true)

        // Get intent value (if not NEW_EVENT_ID we know that we are in edition mode)
        val id = intent.getStringExtra(EventManagementListActivity.EVENT_ID_INTENT)!!

        // See if there are intents related to managers so that we can display everything accordingly.
        isActivityProvider = intent.hasExtra(EventManagementListActivity.INTENT_MANAGER)

        isModificationActivityProvider =
            intent.hasExtra(EventManagementListActivity.INTENT_MANAGER_EDIT)

        isCreation = id == EventManagementListActivity.NEW_EVENT_ID
        curId = id

        setupSpinnerAdapter()
        setupDateListener()

        // Default date
        dateStart.postValue(LocalDateTime.now().withSecond(0).withNano(0))
        dateEnd.postValue(LocalDateTime.now().withSecond(0).withNano(0))

        // Call all the setup method
        setDateListener()
        setTimeListener()
        setButtonListener()
        setupSwitchListener()
        setupViewInActivity(false)
        manageButtonSetup()
    }

    /**
     * Setup observers that will observe the date start and date end live data. This will update the text field accordingly every time a change occurs.
     */
    private fun setupDateListener() {
        dateStart.observe(this) {
            updateTextDate(TYPE_START)
        }
        dateEnd.observe(this) {
            updateTextDate(TYPE_END)
        }
    }

    /**
     * Get all the resources from the database and setup all the adapter for the spinner in the activity
     */
    private fun setupSpinnerAdapter() {
        val adapter =
            ArrayAdapter(this, android.R.layout.simple_spinner_dropdown_item, zoneName)
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
        findViewById<Spinner>(R.id.id_spinner_zone).adapter = adapter

        // Add all the zones retrieve from the database to the spinner
        zoneObserver.observeAdd(this) {
            val name = it.value.zoneName!!
            zoneName.add(name)
            mapIndexToId[zoneName.indexOf(name)] = it.value.zoneId!!
            adapter.notifyDataSetChanged()
        }

        // Get all zones from the database or redirect if there is a problem
        val obsZoneEnded = currentDatabase.zoneDatabase.getActiveZones(zoneObserver).observe(this) {
            if (!it.value) {
                HelperFunctions.showToast(getString(R.string.failed_get_zones), this)
                finish()
            }
        }.then

        // We only allow to choose the user if the current user is an admin
        // if the current user is an activity provider which will propose a event edit request, we will simply put its user id.
        if (!isActivityProvider) {
            val adapter2 =
                ArrayAdapter(this, android.R.layout.simple_spinner_dropdown_item, organiserName)
            adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
            findViewById<Spinner>(R.id.spinner_organiser).adapter = adapter2

            // Get all users from the database or redirect if there is a problem
            val obsUserEnded =
                currentDatabase.userDatabase.getListAllUsers(organiserObserver).observe(this) {
                    if (!it.value) {
                        HelperFunctions.showToast(getString(R.string.failed_get_zones), this)
                        finish()
                    }
                }.then

            // Display a loading screen while the queries with the database are not over
            showProgressDialog(
                this, listOf(
                    obsUserEnded, obsZoneEnded
                ), supportFragmentManager
            )

            // Add all the zones retrieve from the database to the spinner
            organiserObserver.observeAdd(this) {
                val name = it.value.name!!
                organiserName.add(name)
                mapIndexToOrganiserId[organiserName.indexOf(name)] = it.value.uid
                adapter2.notifyDataSetChanged()
            }
        } else {
            // Display a loading screen while the queries with the database are not over
            showProgressDialog(
                this, listOf(
                    obsZoneEnded
                ), supportFragmentManager
            )
        }
    }

    /**
     * Setup the lister on the switch limitedEvent
     */
    private fun setupSwitchListener() {
        val nbpart = findViewById<EditText>(R.id.it_et_nb_part)
        findViewById<Switch>(R.id.id_swt_limited_event).setOnCheckedChangeListener { _, isChecked ->
            if (isChecked) {
                nbpart.visibility = View.VISIBLE
                nbpart.setText(defaultPartNb)
            } else {
                nbpart.visibility = View.INVISIBLE
                nbpart.setText(EMPTY_PART_NB)
            }
        }
    }

    /**
     * Add the listeners to the dialog buttons
     */
    private fun setButtonListener() {
        findViewById<Button>(R.id.id_start_date_button).setOnClickListener {
            dialogStartDate.show()
        }
        findViewById<Button>(R.id.id_btn_end_date).setOnClickListener {
            dialogEndDate.show()
        }
        findViewById<Button>(R.id.id_start_time_button).setOnClickListener {
            dialogStartTime.show()
        }
        findViewById<Button>(R.id.id_btn_end_time).setOnClickListener {
            dialogEndTime.show()
        }
    }

    /**
     * Return a formatted string with all the tags
     * @param tag All the tags in the event
     * @return the format string
     */
    private fun formatTagText(tag: MutableList<String>): String {
        if (tag.size != 0) {
            val txt = tag.map {
                "$it, "
            }.reduce { v, v2 ->
                v + v2
            }
            return txt.substring(0, txt.length - 2)
        }
        return ""
    }

    /**
     * Setup all the views in activity, i.e. set default values in field and which field is displayed
     * @param onCallback if the call to this method result from a callback or not (information from database or not)
     */
    @SuppressLint("UseSwitchCompatOrMaterialCode")
    private fun setupViewInActivity(onCallback: Boolean) {
        val btnManage = findViewById<Button>(R.id.id_manage_event_button)
        val switch = findViewById<Switch>(R.id.id_swt_limited_event)
        val descET = findViewById<EditText>(R.id.id_description_event_edittext)
        val nameET = findViewById<EditText>(R.id.id_event_management_name_et)
        val nbPart = findViewById<EditText>(R.id.it_et_nb_part)
        val tagsEt = findViewById<EditText>(R.id.event_management_tags_edit)
        val spinnerOrg = findViewById<Spinner>(R.id.spinner_organiser)
        val spinnerZone = findViewById<Spinner>(R.id.id_spinner_zone)

        if (isActivityProvider) {
            spinnerOrg.visibility = View.GONE
            findViewById<TextView>(R.id.id_tv_spinner_organiser).visibility = View.GONE
        } else {
            findViewById<TextView>(R.id.id_tv_spinner_organiser).visibility = View.VISIBLE
            spinnerOrg.visibility = View.VISIBLE
        }

        if (!onCallback) {
            btnManage.text = getString(R.string.create_event_btn_text)
            nameET.setText("")
            descET.setText("")
            switch.isChecked = false
            nbPart.visibility = View.INVISIBLE
            nbPart.setText(EMPTY_PART_NB)
        } else {
            val event = observableEvent.value!!
            // Set all the texts in the view
            btnManage.text = getString(R.string.update_event_btn_text)
            tagsEt.setText(formatTagText(event.tags))
            nbPart.setText(EMPTY_PART_NB)
            nameET.setText(event.eventName)
            descET.setText(event.description)
            nbPart.visibility = View.INVISIBLE
            switch.isChecked = event.isLimitedEvent()

            if (event.isLimitedEvent()) {
                nbPart.setText(event.getMaxNumberOfSlots().toString())
                nbPart.visibility = View.VISIBLE
            }

            // Update the date and the corresponding date fields
            dateStart.postValue(event.startTime!!)
            dateEnd.postValue(event.endTime!!)
            updateTextDate(TYPE_END)
            updateTextDate(TYPE_START)

            // Observe the list to select the correct organiser
            organiserObserver.observe(this) {
                var idx = 0
                for (u in it.value.withIndex()) {
                    if (u.value.uid == event.organizer) {
                        idx = u.index
                        break
                    }
                }
                // Run on main thread to be able to make a selection
                PolyEventsApplication.application.applicationScope.launch(Dispatchers.Main) {
                    spinnerOrg.setSelection(idx)
                }
            }

            // Observe the list to select the correct zone
            zoneObserver.observe(this) {
                var idx = 0
                for (u in it.value.withIndex()) {
                    if (u.value.zoneId == event.zoneId) {
                        idx = u.index
                        break
                    }
                }
                // Run on main thread to be able to make a selection
                PolyEventsApplication.application.applicationScope.launch(Dispatchers.Main) {
                    spinnerZone.setSelection(idx)
                }
            }
        }
    }

    /**
     * Set the correct listerner on the manage button depending if we are in creation or edit mode
     * If in edit mode, also get the event information
     */
    private fun manageButtonSetup() {
        val btnManage = findViewById<Button>(R.id.id_manage_event_button)
        if (isCreation) {
            btnManage.setOnClickListener {
                handleCreateClick()
            }
        } else {
            btnManage.setOnClickListener {
                handleUpdateClick()
            }

            val infoGotten = Observable<Boolean>()

            // Get the correct information depending on if we edit an event edit request
            if (isModificationActivityProvider) {
                HelperFunctions.showToast(
                    "├ža passe ",
                    this
                )
                currentDatabase.eventDatabase.getEventEditFromId(curId, observableEvent)
                    .observe(this) {
                        if (it.value) {
                            setupViewInActivity(true)
                        } else {
                            HelperFunctions.showToast(
                                getString(R.string.failed_get_event_information),
                                this
                            )
                            finish()
                        }
                    }.then.updateOnce(this, infoGotten)
            } else {
                // Or if we edit an event
                currentDatabase.eventDatabase.getEventFromId(curId, observableEvent)
                    .observe(this) {
                        if (it.value) {
                            setupViewInActivity(true)
                        } else {
                            HelperFunctions.showToast(
                                getString(R.string.failed_get_event_information),
                                this
                            )
                            finish()
                        }
                    }.then.updateOnce(this, infoGotten)
            }
            // Show a waiting screen until all the information from the database are retrieved
            showProgressDialog(this, listOf(infoGotten), supportFragmentManager)
        }
    }

    /**
     * Handle the click on the create button
     */
    private fun handleCreateClick() {
        if (verifyCondition()) {
            val createEnded = Observable<Boolean>()
            if (isActivityProvider) {
                currentDatabase.eventDatabase.createEventEdit(getInformation()).observe(this) {
                    redirectOrDisplayError(
                        getString(R.string.event_edit_request_successfully_sent),
                        getString(R.string.event_edit_request_error),
                        it.value
                    )
                }.then.updateOnce(this, createEnded)
            } else {
                currentDatabase.eventDatabase.createEvent(getInformation()).observe(this) {
                    redirectOrDisplayError(
                        getString(R.string.event_creation_success),
                        getString(R.string.event_creation_failed),
                        it.value
                    )
                }.then.updateOnce(this, createEnded)
            }
            // Show a waiting screen until the creation is done for the database
            showProgressDialog(this, listOf(createEnded), supportFragmentManager)
        }
    }

    /**
     * Handle the click on the update button
     */
    private fun handleUpdateClick() {
        if (verifyCondition()) {
            val updateEnded = Observable<Boolean>()
            if (isActivityProvider) {
                if (isModificationActivityProvider) {
                    currentDatabase.eventDatabase.updateEventEdit(getInformation())
                        .observe(this) {
                            redirectOrDisplayError(
                                getString(R.string.event_edit_request_successfully_sent),
                                getString(R.string.event_edit_request_error),
                                it.value
                            )
                        }.then.updateOnce(this, updateEnded)
                } else {
                    currentDatabase.eventDatabase.createEventEdit(getInformation())
                        .observe(this) {
                            redirectOrDisplayError(
                                getString(R.string.event_edit_request_successfully_sent),
                                getString(R.string.event_edit_request_error),
                                it.value
                            )
                        }
                }
            } else {
                currentDatabase.eventDatabase.updateEvent(getInformation()).observe(this) {
                    redirectOrDisplayError(
                        getString(R.string.event_update_success),
                        getString(R.string.failed_to_update_event_info),
                        it.value
                    )
                }.then.updateOnce(this, updateEnded)
            }
            // Show a waiting screen until the update is done for the database
            showProgressDialog(this, listOf(updateEnded), supportFragmentManager)
        }
    }

    /**
     * Update the text field of the date given in parameter (date should have been modified before calling the function)
     * @param type which date field to update (TYPE_START or TYPE_END)
     */
    private fun updateTextDate(type: String) {
        if (TYPE_START == type) {
            findViewById<TextView>(R.id.et_start_date).text =
                localDatetimeToString(dateStart.value!!)
        } else {
            findViewById<TextView>(R.id.et_end_date).text = localDatetimeToString(dateEnd.value!!)
        }
    }

    /**
     * Redirect to the event list activity with a success message if the query to the database is successful otherwise display an error message and stay on actvitiy
     * @param msgSuccess the message to display in case of successs
     * @param msgError the message to display in case of error
     * @param success if the query is successful
     */
    private fun redirectOrDisplayError(msgSuccess: String, msgError: String, success: Boolean) {
        if (success) {
            HelperFunctions.showToast(msgSuccess, this)
            finish()
        } else {
            HelperFunctions.showToast(msgError, this)
        }
    }

    /**
     * Create an event from written information by the user in the UI activity
     * @return An event created from all the information
     */
    private fun getInformation(): Event {
        val eventId: String? = if (isCreation) {
            null
        } else {
            if (isModificationActivityProvider) {
                observableEvent.value!!.eventId
            } else {
                curId
            }
        }

        val name = findViewById<EditText>(R.id.id_event_management_name_et).text.toString()
        val desc = findViewById<EditText>(R.id.id_description_event_edittext).text.toString()
        val selectedZone = findViewById<Spinner>(R.id.id_spinner_zone).selectedItemPosition
        val selectedOrganiser = findViewById<Spinner>(R.id.spinner_organiser).selectedItemPosition
        val limitedEvent = findViewById<Switch>(R.id.id_swt_limited_event).isChecked
        val nbParticipant = findViewById<EditText>(R.id.it_et_nb_part).text.toString().toInt()

        val tags =
            if (findViewById<EditText>(R.id.event_management_tags_edit).text.toString() != "") {
                findViewById<EditText>(R.id.event_management_tags_edit)
                    .text
                    .toString()
                    .trim()
                    .split(",")
                    .map {
                        it.trim()
                    }
                    .toSet()
                    .toMutableList()
            } else {
                mutableListOf()
            }

        val zoneNa = zoneName[selectedZone]
        val zoneId = mapIndexToId[selectedZone]
        var status: Event.EventStatus? = null

        // Add the event organiser id and the status if the current user is not an admin -> event edit request
        val organiserId: String
        if (!isActivityProvider) {
            organiserId = mapIndexToOrganiserId[selectedOrganiser]!!
        } else {
            organiserId = currentDatabase.currentUser!!.uid
            status = Event.EventStatus.PENDING
        }
        var eventEditId: String? = null
        if (isModificationActivityProvider) {
            eventEditId = curId
        }

        return Event(
            eventId = eventId,
            eventEditId = eventEditId,
            zoneId = zoneId,
            zoneName = zoneNa,
            startTime = dateStart.value,
            endTime = dateEnd.value,
            eventName = name,
            description = desc,
            limitedEvent = limitedEvent,
            maxNumberOfSlots = nbParticipant,
            organizer = organiserId,
            status = status,
            tags = tags
        )
    }

    /**
     * Set all the listener methods in the date picker dialogs
     */
    private fun setDateListener() {
        dialogEndDate = DatePickerDialog(
            this, { _: DatePicker, year: Int, month: Int, day: Int ->
                postValueDate(
                    TYPE_END,
                    year,
                    month + 1,
                    day,
                    dateEnd.value!!.hour,
                    dateEnd.value!!.minute
                )
            }, dateEnd.value!!.year, dateEnd.value!!.monthValue - 1, dateEnd.value!!.dayOfMonth
        )

        dialogStartDate = DatePickerDialog(
            this,
            { _: DatePicker, year: Int, month: Int, day: Int ->
                postValueDate(
                    TYPE_START,
                    year,
                    month + 1,
                    day,
                    dateStart.value!!.hour,
                    dateStart.value!!.minute
                )
            },
            dateStart.value!!.year,
            dateStart.value!!.monthValue - 1,
            dateStart.value!!.dayOfMonth
        )
    }

    /**
     * Set all the listeners methods in the time picker dialogs
     */
    private fun setTimeListener() {
        dialogStartTime = TimePickerDialog(
            this, { _: TimePicker, hour: Int, minute: Int ->
                postValueDate(
                    TYPE_START,
                    dateStart.value!!.year,
                    dateStart.value!!.monthValue,
                    dateStart.value!!.dayOfMonth,
                    hour,
                    minute
                )
            },
            dateStart.value!!.hour,
            dateStart.value!!.minute,
            true
        )

        dialogEndTime = TimePickerDialog(
            this, { _: TimePicker, hour: Int, minute: Int ->
                postValueDate(
                    TYPE_END,
                    dateEnd.value!!.year,
                    dateEnd.value!!.monthValue,
                    dateEnd.value!!.dayOfMonth,
                    hour,
                    minute
                )
            },
            dateEnd.value!!.hour,
            dateEnd.value!!.minute,
            true
        )
    }

    /**
     * Verify that all the condition are satisfied. If it is not the case, display the corresponding error message
     * @return true if all the conditions are satisfied or false otherwise
     */
    private fun verifyCondition(): Boolean {
        var good = true
        if (findViewById<EditText>(R.id.id_event_management_name_et).text.toString() == "") {
            good = false
            HelperFunctions.showToast(getString(R.string.empty_name_field), this)
        }
        if (findViewById<EditText>(R.id.id_description_event_edittext).text.toString() == "") {
            good = false
            HelperFunctions.showToast(getString(R.string.description_not_empty), this)
        }

        if (dateEnd.value!!.isBefore(dateStart.value!!)) {
            good = false
            HelperFunctions.showToast(getString(R.string.end_date_before_start_date), this)
        }

        if (findViewById<Switch>(R.id.id_swt_limited_event).isChecked) {
            if (findViewById<EditText>(R.id.it_et_nb_part).text.toString()
                    .toInt() < MIN_PART_NB
            ) {
                good = false
                HelperFunctions.showToast(
                    getString(R.string.number_of_event_greater_than_0, MIN_PART_NB),
                    this
                )
            }
        }
        return good
    }
}