procrastinot-team/procrastinot

View on GitHub
app/src/main/java/com/github/mateo762/myapplication/profile/ProfileActivity.kt

Summary

Maintainability
B
4 hrs
Test Coverage
A
90%
package com.github.mateo762.myapplication.profile

import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.provider.MediaStore
import android.view.MenuItem
import android.view.View
import androidx.activity.viewModels
import com.bumptech.glide.Glide
import com.github.mateo762.myapplication.BaseActivity
import com.github.mateo762.myapplication.R
import com.github.mateo762.myapplication.coach_rating.CoachRatingViewModel
import com.github.mateo762.myapplication.databinding.ActivityProfileBinding
import com.github.mateo762.myapplication.followers.UserRepository
import com.github.mateo762.myapplication.username.UsernameActivity
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.UserProfileChangeRequest
import com.google.firebase.database.DatabaseReference
import com.google.firebase.database.ktx.database
import com.google.firebase.ktx.Firebase
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import javax.inject.Inject

/**
 * Activity for displaying the profile information.
 */
abstract class ProfileActivity : BaseActivity(), CoroutineScope {

    // Hack to solve the jacoco report problem with hilt activity, which still doesn't have a
    // solution.
    //
    // More info can be found: https://issuetracker.google.com/issues/161300933#comment5
    @AndroidEntryPoint
    class EntryPoint : ProfileActivity()

    @Inject
    lateinit var userRepository: UserRepository
    @Inject
    lateinit var userImageStorageService: UserImageStorageService

    private val coachRatingViewModel: CoachRatingViewModel by viewModels()
    private val profileViewModel: ProfileViewModel by viewModels()

    private val job = Job()

    override val coroutineContext = job + Dispatchers.Main

    private var imageUri: Uri? = null

    private val user = FirebaseAuth.getInstance().currentUser
    private lateinit var db: DatabaseReference
    lateinit var binding: ActivityProfileBinding
    private lateinit var uid: String

    private lateinit var habitsAdapter: ProfileHabitsAdapter
    private lateinit var galleryAdapter: ProfileGalleryAdapter

    companion object {
        private val TAG = ProfileActivity::class.java.simpleName
        const val USER_ID_EXTRA = "userId"
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityProfileBinding.inflate(layoutInflater)
        setContentView(binding.root)

        db = Firebase.database.reference

        setupToolbar()
        handleProfileInfoChange(false)

        binding.coachRatingView.setViewModel(coachRatingViewModel)

        uid = intent.getStringExtra(USER_ID_EXTRA) ?: userRepository.getUserUid()
        val currentUserId = userRepository.getUserUid()

        checkIfDifferentUser(currentUserId)

        setOnClickListeners(currentUserId)

        habitsAdapter = ProfileHabitsAdapter()
        binding.habitsRecyclerView.adapter = habitsAdapter
        galleryAdapter = ProfileGalleryAdapter()
        binding.galleryRecyclerView.adapter = galleryAdapter

        setLiveDataObservers()
    }

    override fun onResume() {
        super.onResume()

        profileViewModel.getUserInfo(uid)
        profileViewModel.getHabitImages(uid)
        profileViewModel.getFollowingNumber(uid)
        profileViewModel.getFollowersNumber(uid)
        profileViewModel.getHabits(uid)
        binding.coachRatingView.getRatingStats(uid)
    }

    @Override
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == 1000) {
            if (resultCode == Activity.RESULT_OK) {
                imageUri = data?.data!!
                binding.profileImage.setImageURI(imageUri)
            }
        }
    }

    private fun setupToolbar() {
        setSupportActionBar(binding.toolbar)
        title = "" // the title is not set directly on the xml, avoid having two titles per screen
        supportActionBar?.setDisplayHomeAsUpEnabled(true)
        supportActionBar?.setDisplayShowHomeEnabled(true)
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        return when (item.itemId) {
            android.R.id.home -> {
                finish()
                true
            }

            else -> super.onOptionsItemSelected(item)
        }
    }

    private fun followUser(currentUserId: String, targetUserId: String) {
        launch {
            userRepository.followUser(currentUserId, targetUserId)
            binding.btnFollow.visibility = View.GONE
            binding.btnUnfollow.visibility = View.VISIBLE
        }
    }

    private fun unfollowUser(currentUserId: String, targetUserId: String){
        launch {
            userRepository.unfollowUser(currentUserId, targetUserId)
            binding.btnFollow.visibility = View.VISIBLE
            binding.btnUnfollow.visibility = View.GONE
        }
    }

    private fun handleProfileInfoChange(isEditCLicked: Boolean) {
        binding.btnEdit.visibility = if (isEditCLicked) View.GONE else View.VISIBLE
        binding.btnSave.visibility = if (isEditCLicked) View.VISIBLE else View.GONE
        
        binding.nameTextView.visibility = if (isEditCLicked) View.GONE else View.VISIBLE
        binding.nameEditText.isEnabled = isEditCLicked
        binding.nameEditText.isClickable = isEditCLicked
        binding.nameEditText.visibility = if (isEditCLicked) View.VISIBLE else View.GONE
        binding.emailTextView.visibility = if (isEditCLicked) View.GONE else View.VISIBLE
        binding.emailEditText.isEnabled = isEditCLicked
        binding.emailEditText.isClickable = isEditCLicked
        binding.emailEditText.visibility = if (isEditCLicked) View.VISIBLE else View.GONE
        binding.usernameTextView.visibility = if (isEditCLicked) View.GONE else View.VISIBLE
        binding.changeUsernameButton.visibility = if (isEditCLicked) View.VISIBLE else View.GONE
    }

    private fun setLiveDataObservers() {
        profileViewModel.habitLiveData.observe(this) {
            habitsAdapter.habits = it
        }
        profileViewModel.habitImagesLiveData.observe(this) {
            galleryAdapter.galleryItems = it
        }
        profileViewModel.statsLiveData.observe(this) { statsUiModel ->
            setStatsData(statsUiModel)
        }

        profileViewModel.followingLiveData.observe(this) {
            binding.followingTextView.text = getString(R.string.following, it.toString())
        }
        profileViewModel.followersLiveData.observe(this) {
            binding.followersTextView.text = getString(R.string.followers, it.toString())
        }
        profileViewModel.userInfoLiveData.observe(this) { userInfo ->
            binding.nameTextView.text = userInfo.name
            binding.emailTextView.text = userInfo.email
            binding.usernameTextView.text = userInfo.username

            Glide.with(applicationContext)
                .load(userInfo.url)
                .placeholder(R.mipmap.ic_launcher)
                .into(binding.profileImage)
        }
    }

    private fun onSaveButtonClicked(currentUserId: String) {
        // We save the updated value to your database or data storage
        val newName = binding.nameEditText.text.toString()
        val newEmail = binding.emailEditText.text.toString()
        binding.nameTextView.text = newName
        binding.emailTextView.text = newEmail

        handleProfileInfoChange(false)

        binding.profileImage.setOnClickListener { }

        db.child("users").child(uid).child("name").setValue(newName)
        db.child("users").child(uid).child("email").setValue(newEmail)

        userImageStorageService.storeImage(currentUserId, imageUri)

        val profileUpdates = UserProfileChangeRequest.Builder()
            .setDisplayName(newName)
            .build()

        user?.updateProfile(profileUpdates)
    }

    private fun onEditButtonClicked() {
        // We enable the name and email edit texts such that they can be edited
        handleProfileInfoChange(true)
        binding.nameEditText.setText(binding.nameTextView.text)
        binding.emailEditText.setText(binding.emailTextView.text)

        binding.profileImage.setOnClickListener {
            val openGalleryIntent =
                Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
            startActivityForResult(openGalleryIntent, 1000)
        }
    }

    private fun onChangeUsernameClicked() {
        val intent = Intent(this, UsernameActivity.EntryPoint::class.java).apply {
            putExtra(UsernameActivity.OLD_USERNAME_KEY, binding.usernameTextView.text)
        }
        startActivity(intent)
    }

    private fun setOnClickListeners(currentUserId: String) {
        binding.btnFollow.setOnClickListener {
            followUser(currentUserId, uid)
        }
        binding.btnUnfollow.setOnClickListener {
            unfollowUser(currentUserId, uid)
        }
        binding.changeUsernameButton.setOnClickListener {
            onChangeUsernameClicked()
        }
        binding.btnEdit.setOnClickListener {
            onEditButtonClicked()
        }
        binding.btnSave.setOnClickListener {
            onSaveButtonClicked(currentUserId)
        }
    }

    private fun checkIfDifferentUser(currentUserId: String) {
        if (uid == currentUserId) {
            binding.btnFollow.visibility = View.GONE
            binding.btnUnfollow.visibility = View.GONE
            binding.btnEdit.visibility = View.VISIBLE
        } else {
            binding.btnEdit.visibility = View.GONE
            // Check if the user is already following the profile
            launch {
                val isFollowing = userRepository.checkIfUserFollows(currentUserId, uid)
                if (isFollowing) {
                    // If the user is following the profile, show the 'btnUnfollow' button
                    binding.btnFollow.visibility = View.GONE
                    binding.btnUnfollow.visibility = View.VISIBLE
                } else {
                    binding.btnFollow.visibility = View.VISIBLE
                    binding.btnUnfollow.visibility = View.GONE
                }
            }
        }
    }

    private fun setStatsData(statsUiModel: ProfileStatsUiModel) {
        binding.habitCountText.text =
            getString(R.string.posted_habits, statsUiModel.totalNumberOfHabits.toString())
        binding.avgPerWeekText.text =
            getString(R.string.avg_days_week, statsUiModel.averageDaysInWeek.toString())

        binding.earliestTextView.text = if (statsUiModel.earliestStart != "0") {
            getString(R.string.earlystart, statsUiModel.earliestStart)
        } else {
            getString(R.string.na)
        }
        binding.latestTextView.text = if (statsUiModel.earliestStart != "0") {
            getString(R.string.lateend, statsUiModel.latestEnd)
        } else {
            getString(R.string.na)
        }
    }
}