SDPTeam15/PolyEvents

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

Summary

Maintainability
A
0 mins
Test Coverage
A
90%
package com.github.sdpteam15.polyevents.view.fragments

import android.annotation.SuppressLint
import android.app.DatePickerDialog
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.transition.Slide
import android.transition.TransitionManager
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.*
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.RecyclerView
import com.github.sdpteam15.polyevents.R
import com.github.sdpteam15.polyevents.helper.HelperFunctions
import com.github.sdpteam15.polyevents.helper.HelperFunctions.changeFragment
import com.github.sdpteam15.polyevents.helper.NotificationsHelper
import com.github.sdpteam15.polyevents.helper.NotificationsScheduler
import com.github.sdpteam15.polyevents.model.callback.UserModifiedInterface
import com.github.sdpteam15.polyevents.model.database.local.entity.EventLocal
import com.github.sdpteam15.polyevents.model.database.local.room.LocalDatabase
import com.github.sdpteam15.polyevents.model.database.remote.Database.currentDatabase
import com.github.sdpteam15.polyevents.model.database.remote.login.UserLogin
import com.github.sdpteam15.polyevents.model.entity.UserEntity
import com.github.sdpteam15.polyevents.model.entity.UserProfile
import com.github.sdpteam15.polyevents.model.entity.UserRole
import com.github.sdpteam15.polyevents.model.observable.Observable
import com.github.sdpteam15.polyevents.model.observable.ObservableList
import com.github.sdpteam15.polyevents.view.PolyEventsApplication
import com.github.sdpteam15.polyevents.view.activity.EditProfileActivity
import com.github.sdpteam15.polyevents.view.activity.MainActivity
import com.github.sdpteam15.polyevents.view.adapter.ProfileAdapter
import com.github.sdpteam15.polyevents.viewmodel.EventLocalViewModel
import com.github.sdpteam15.polyevents.viewmodel.EventLocalViewModelFactory
import java.time.LocalDate
import java.time.format.DateTimeFormatter

/**
 *  [Fragment] subclass that represents the profile page allowing the user to modify its private information
 *  @param userId the id of the user we display the information or null if it is the current user of the application
 */
class ProfileFragment(private val userId: String? = null) : Fragment(), UserModifiedInterface {

    companion object {
        // for testing purposes
        lateinit var localDatabase: LocalDatabase
        lateinit var eventLocalViewModel: EventLocalViewModel
        lateinit var notificationsScheduler: NotificationsScheduler
    }

    //Return currentUser if we are not in test, but we can use a fake user in test this way
    var currentUser: UserEntity? = null
        get() = field ?: currentDatabase.currentUser

    val userInfoLiveData = Observable<UserEntity>()
    private val adminMode = userId != null
    private val obsDate = Observable<LocalDate>()
    private lateinit var profileNameET: EditText
    private lateinit var profileEmailET: EditText
    private lateinit var profileUsernameET: EditText
    private val currentUID: String
        get() = userId ?: currentUser!!.uid
    private lateinit var viewR: View

    /**
     * Recycler containing all the profile
     */
    private lateinit var recyclerView: RecyclerView

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val viewRoot = inflater.inflate(R.layout.fragment_profile, container, false)
        //If the user is not logged in, redirect him to the login page
        if (currentUser == null) {
            changeFragment(activity, MainActivity.fragments[R.id.ic_login])
        } else {
            localDatabase = (requireActivity().application as PolyEventsApplication).localDatabase
            eventLocalViewModel = EventLocalViewModelFactory(
                localDatabase.eventDao()
            ).create(
                EventLocalViewModel::class.java
            )
            notificationsScheduler = NotificationsHelper(requireActivity().applicationContext)

            profileNameET = viewRoot.findViewById(R.id.id_profile_name_edittext)
            profileEmailET = viewRoot.findViewById(R.id.id_profile_email_edittext)
            profileUsernameET = viewRoot.findViewById(R.id.id_profile_username_edittext)

            //call method to bind the listerner and observer to the correct fields
            addListener(viewRoot)
            addObserver()

            obsDate.observe(this) {
                val europeanDateFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy")

                viewRoot.findViewById<EditText>(R.id.id_profile_birthday_edittext)
                    .setText(europeanDateFormatter.format(it.value))
            }

            if (adminMode) {
                //If an admin want to see the profile of somme user
                setupAdminMode(viewRoot)
                userInfoLiveData.observe(this) {
                    initProfileList(viewRoot, it.value)
                }
            } else {
                //If not an admin, display the profile information of the current user
                setupUserMode(viewRoot)
                currentDatabase.currentUserObservable.observe(this) {
                    initProfileList(viewRoot, it.value)
                }
            }
            getUserInformation()
        }
        viewR = viewRoot
        return viewRoot
    }

    private fun getUserInformation() {
        val observableDBAnswer = Observable<Boolean>()
        //Get user information in the database
        currentDatabase.userDatabase.getUserInformation(
            userInfoLiveData,
            currentUID
        ).updateOnce(requireActivity(), observableDBAnswer)

        HelperFunctions.showProgressDialog(
            requireActivity(), listOf(
                observableDBAnswer
            ), requireActivity().supportFragmentManager
        )
    }

    /**
     * Method that will make some edit text uneditable and some button invisible for the admin
     * @param viewRoot the current view of the fragment
     */
    private fun setupAdminMode(viewRoot: View) {
        viewRoot.findViewById<Button>(R.id.id_update_infos_button).visibility = View.INVISIBLE
        viewRoot.findViewById<Button>(R.id.id_logout_button).visibility = View.INVISIBLE
        viewRoot.findViewById<Button>(R.id.id_birthday_button).visibility = View.INVISIBLE
        viewRoot.findViewById<EditText>(R.id.id_profile_birthday_edittext).isEnabled = false
        viewRoot.findViewById<EditText>(R.id.id_profile_username_edittext).isEnabled = false
    }

    /**
     * Method that will display the button and edit text needed to update the user information
     * @param viewRoot the current view of the fragment
     */
    private fun setupUserMode(viewRoot: View) {
        viewRoot.findViewById<Button>(R.id.id_update_infos_button).visibility = View.VISIBLE
        viewRoot.findViewById<Button>(R.id.id_logout_button).visibility = View.VISIBLE
        viewRoot.findViewById<Button>(R.id.id_birthday_button).visibility = View.VISIBLE
        viewRoot.findViewById<EditText>(R.id.id_profile_birthday_edittext).isEnabled = true
        viewRoot.findViewById<EditText>(R.id.id_profile_username_edittext).isEnabled = true
    }

    /**
     * Add the observers to make the fragment works properly
     */
    private fun addObserver() {
        //When user Info live data is updated, set the correct value in the textview
        userInfoLiveData.observe(this) { userInfo ->
            val userInfoValue = userInfo.value
            profileNameET.setText(userInfoValue.name)
            profileEmailET.setText(userInfoValue.email)
            profileUsernameET.setText(userInfoValue.username)
            obsDate.postValue(userInfoValue.birthDate)
        }
    }

    /**
     * Add the listener to the buttons to make the fragment works properly
     * @param viewRoot the current view of the fragment
     */
    private fun addListener(viewRoot: View) {
        viewRoot.findViewById<Button>(R.id.id_update_infos_button).setOnClickListener {
            //Clear the previous map and add every field
            currentUser!!.username = profileUsernameET.text.toString()
            if (obsDate.value != null) {
                currentUser!!.birthDate = obsDate.value
            }
            //Call the DB to update the user information and getUserInformation once it is done
            currentDatabase.userDatabase.updateUserInformation(
                currentUser!!
            ).observe(requireActivity()) { newValue ->
                if (newValue.value) {
                    val observableDBAnswer = Observable<Boolean>()
                    currentDatabase.userDatabase.getUserInformation(
                        userInfoLiveData,
                        currentUser!!.uid
                    ).updateOnce(requireActivity(), observableDBAnswer)

                    HelperFunctions.showProgressDialog(
                        requireActivity(), listOf(
                            observableDBAnswer
                        ), requireActivity().supportFragmentManager
                    )
                } else {
                    HelperFunctions.showToast(getString(R.string.fail_to_update), activity)
                }
            }
        }

        viewRoot.findViewById<Button>(R.id.id_birthday_button).setOnClickListener {
            val date = obsDate.value ?: LocalDate.now()
            val dialog = DatePickerDialog(
                requireContext(),
                { _: DatePicker, year: Int, month: Int, day: Int ->
                    obsDate.postValue(LocalDate.of(year, month + 1, day))
                },
                date.year,
                date.monthValue - 1,
                date.dayOfMonth
            )
            dialog.show()
        }

        //Logout button handler
        viewRoot.findViewById<Button>(R.id.id_logout_button).setOnClickListener { _ ->
            UserLogin.currentUserLogin.signOut()
            // On logout delete all user subscribed and followed events from local cache
            val eventsLocalObservable = ObservableList<EventLocal>()
            eventsLocalObservable.observe(requireActivity()) {
                it.value.forEach {
                    notificationsScheduler.cancelNotification(it.eventBeforeNotificationId)
                    notificationsScheduler.cancelNotification(it.eventStartNotificationId)
                    eventLocalViewModel.delete(it)
                }
            }
            eventLocalViewModel.getAllEvents(eventsLocalObservable)

            changeFragment(activity, MainActivity.fragments[R.id.ic_login])
        }
    }

    fun initProfileList(viewRoot: View, user: UserEntity) {
        user.userProfiles.observeRemove(this) {
            if (it.sender != currentDatabase) {
                currentDatabase.userDatabase.removeProfileFromUser(it.value, user)
                    .observeOnce(this) {
                        if (!it.value)
                            HelperFunctions.showToast(
                                getString(R.string.fail_to_remove_profiles),
                                context
                            )
                    }
            }
        }
        user.userProfiles.observeAdd(this) {
            if (it.sender != currentDatabase)
                currentDatabase.userDatabase.addUserProfileAndAddToUser(it.value, user)
                    .observeOnce(this) {
                        if (!it.value)
                            HelperFunctions.showToast(
                                getString(R.string.fail_to_add_profiles),
                                context
                            )
                    }
        }

        recyclerView = viewRoot.findViewById(R.id.id_recycler_profile_list)

        recyclerView.adapter = ProfileAdapter(this, user, user.userProfiles)

        viewRoot.findViewById<ImageButton>(R.id.id_add_profile_button)
            .setOnClickListener { createProfilePopup(user) }
    }

    @SuppressLint("InflateParams")
    private fun createProfilePopup(user: UserEntity) {
        // Initialize a new layout inflater instance
        val inflater: LayoutInflater =
            (activity)!!.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater

        // Inflate a custom view using layout inflater
        val view = inflater.inflate(R.layout.popup_profile, null)

        // Initialize a new instance of popup window
        val popupWindow = PopupWindow(
            view, // Custom view to show in popup window
            LinearLayout.LayoutParams.MATCH_PARENT, // Width of popup window
            LinearLayout.LayoutParams.WRAP_CONTENT // Window height
        )

        val slideIn = Slide()
        slideIn.slideEdge = Gravity.TOP
        popupWindow.enterTransition = slideIn

        // Slide animation for popup window exit transition
        val slideOut = Slide()
        slideOut.slideEdge = Gravity.END
        popupWindow.exitTransition = slideOut

        // Get the widgets reference from custom view
        val profileName = view.findViewById<EditText>(R.id.id_edittext_profile_name)
        val confirmButton = view.findViewById<ImageButton>(R.id.id_confirm_add_item_button)

        //set focus on the popup
        popupWindow.isFocusable = true

        // Set a click listener for popup's button widget
        confirmButton.setOnClickListener {
            user.userProfiles.add(
                UserProfile(
                    profileName = profileName.text.toString()
                ), this
            )
            // Dismiss the popup window
            popupWindow.dismiss()
        }

        // Finally, show the popup window on app
        TransitionManager.beginDelayedTransition(this.recyclerView)
        popupWindow.showAtLocation(this.recyclerView, Gravity.CENTER, 0, 0)
    }

    fun editProfile(item: UserProfile) {
        val intent = Intent(activity, EditProfileActivity::class.java)
        intent.putExtra(
            EditProfileActivity.CALLER_RANK,
            if (currentUser!!.isAdmin()) UserRole.ADMIN.userRole else UserRole.PARTICIPANT.userRole
        )

        intent.putExtra(EditProfileActivity.EDIT_PROFILE_ID, item.pid.toString())
        EditProfileActivity.callback = this
        startActivity(intent)

        /*
        EditProfileActivity.end.observeOnce(this, false) {
            userInfoLiveData.value!!.loadSuccess = false
            userInfoLiveData.value!!.userProfiles.observeOnce(this) {
                recyclerView.adapter!!.notifyDataSetChanged()
            }
        }*/
    }

    override fun profileHasChanged() {
        currentDatabase.userDatabase.getUserInformation(
            userInfoLiveData,
            currentUID
        )
    }
}