HumanLearning2021/HumanLearningApp

View on GitHub
app/src/main/java/com/github/HumanLearning2021/HumanLearningApp/view/dataset_editing/DisplayImageSetFragment.kt

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
package com.github.HumanLearning2021.HumanLearningApp.view.dataset_editing

import android.content.Context
import android.os.Bundle
import android.util.TypedValue
import android.view.*
import android.widget.AbsListView
import android.widget.BaseAdapter
import android.widget.GridView
import android.widget.ImageView
import androidx.core.view.get
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import com.github.HumanLearning2021.HumanLearningApp.R
import com.github.HumanLearning2021.HumanLearningApp.databinding.FragmentDisplayImageSetBinding
import com.github.HumanLearning2021.HumanLearningApp.hilt.ProductionDatabaseName
import com.github.HumanLearning2021.HumanLearningApp.model.*
import com.github.HumanLearning2021.HumanLearningApp.view.FragmentOptionsUtil
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import javax.inject.Inject

/**
 * Fragment to display all the pictures of a category.
 *
 * The name of the category and all the pictures of the category are displayed.
 *
 * Possible actions :
 * - Get the information of what the user can do by clicking on the info menu button.
 * - Can delete one or multiple pictures with a long click on one picture and then by clicking
 * on the trashcan icon (Possibility to select multiple pictures after the long click).
 * - Can set a picture as representative of the category with a long click and then by clicking
 * on the star icon (this action deletes the picture from the pictures of the category).
 */
@AndroidEntryPoint
class DisplayImageSetFragment : Fragment() {
    private lateinit var parentActivity: FragmentActivity

    @Inject
    lateinit var globalDatabaseManagement: UniqueDatabaseManagement

    @Inject
    @ProductionDatabaseName
    lateinit var dbName: String

    lateinit var dBManagement: DatabaseManagement

    @Inject
    lateinit var imageDisplayer: ImageDisplayer

    private var categorizedPicturesList = setOf<CategorizedPicture>()
    private var categorizedPicturesSelectedList = setOf<CategorizedPicture>()
    private var numberOfSelectedPictures = 0
    private lateinit var displayImageSetAdapter: DisplayImageSetAdapter
    private lateinit var datasetId: Id
    private lateinit var category: Category

    private val args: DisplayImageSetFragmentArgs by navArgs()
    private var _binding: FragmentDisplayImageSetBinding? = null
    private val binding get() = _binding!!

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        runBlocking {
            dBManagement = globalDatabaseManagement.accessDatabase(
                dbName
            )
        }
    }

    override fun onResume() {
        super.onResume()
        runBlocking {
            dBManagement = globalDatabaseManagement.accessDatabase(
                dbName
            )
        }
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        parentActivity = requireActivity()
        _binding = FragmentDisplayImageSetBinding.inflate(inflater, container, false)
        setHasOptionsMenu(true)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        category = args.category
        datasetId = args.datasetId

        /**
         * Retrieve all the pictures of the category and display them.
         */
        lifecycleScope.launch {
            categorizedPicturesList = dBManagement.getAllPictures(category)
            binding.textViewDisplayImageSet.text =
                category.name
            if (categorizedPicturesList.isNotEmpty()) {
                displayImageSetAdapter =
                    DisplayImageSetAdapter(
                        categorizedPicturesList,
                    )

                parentActivity.findViewById<GridView>(R.id.gridView_display_image_set).adapter =
                    displayImageSetAdapter
                setPictureItemListener()
            }
        }
        binding.gridViewDisplayImageSet.choiceMode = GridView.CHOICE_MODE_MULTIPLE_MODAL
        setGridViewMultipleChoiceModeListener()
    }

    override fun onDestroyView() {
        binding.gridViewDisplayImageSet.choiceMode = GridView.CHOICE_MODE_MULTIPLE
        super.onDestroyView()
        _binding = null
    }

    /**
     * Adapter of the grid displaying pictures of the category.
     *
     * @param pictures the pictures of the category
     * @constructor Creates an adapter with the given pictures.
     */
    private inner class DisplayImageSetAdapter(
        pictures: Set<CategorizedPicture>,
    ) : BaseAdapter() {

        var adapterPictures = pictures

        private var layoutInflater =
            parentActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater

        override fun getView(position: Int, view0: View?, viewGroup: ViewGroup?): View {
            val view =
                view0 ?: layoutInflater.inflate(R.layout.image_item, viewGroup!!, false)

            val imageView = view.findViewById<ImageView>(R.id.imageView_image_item)

            with(imageDisplayer) {
                lifecycleScope.launch {
                    adapterPictures.elementAt(position).displayOn(imageView as ImageView)
                }
            }

            return view
        }

        override fun getItem(position: Int): Any {
            return adapterPictures.elementAt(position)
        }

        override fun getItemId(position: Int): Long {
            return position.toLong()
        }

        override fun getCount(): Int {
            return adapterPictures.size
        }

        /**
         * updates the adapter and display the new pictures
         *
         * @param newPictures the new pictures to be displayed.
         */
        fun updatePictures(newPictures: Set<CategorizedPicture>) {
            adapterPictures = newPictures
            notifyDataSetChanged()
        }
    }

    /**
     * Listener to navigate to the display image fragment when a picture is clicked.
     */
    private fun setPictureItemListener() {
        binding.gridViewDisplayImageSet.setOnItemClickListener { _, _, i, _ ->
            val action =
                DisplayImageSetFragmentDirections.actionDisplayImageSetFragmentToDisplayImageFragment(
                    categorizedPicturesList.elementAt(i),
                    datasetId
                )
            findNavController().navigate(action)
        }
    }

    /**
     * Listener for the multiple selection of the pictures after a long click.
     */
    private fun setGridViewMultipleChoiceModeListener() {
        binding.gridViewDisplayImageSet.setMultiChoiceModeListener(object :
            AbsListView.MultiChoiceModeListener {

            val tenDp = TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_DIP, 10f,
                this@DisplayImageSetFragment.resources.displayMetrics
            )

            override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
                val inflater = mode!!.menuInflater
                inflater!!.inflate(R.menu.display_imageset_menu, menu)
                return true
            }

            override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
                return false
            }

            override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
                when (item!!.itemId) {
                    /**
                     * delete all the selected pictures.
                     */
                    R.id.delete_pictures ->
                        lifecycleScope.launch {
                            for (pic in categorizedPicturesSelectedList) {
                                categorizedPicturesList = categorizedPicturesList.minus(pic)
                                dBManagement.removePicture(pic)
                            }
                            categorizedPicturesSelectedList = emptySet()
                            numberOfSelectedPictures = 0
                            mode!!.finish()
                        }
                    /**
                     * Set the selected picture as representative of the category.
                     */
                    R.id.set_representative_picture ->
                        lifecycleScope.launch {
                            val pic = categorizedPicturesSelectedList.first()
                            dBManagement.putRepresentativePicture(
                                pic
                            )
                            categorizedPicturesList = categorizedPicturesList.minus(pic)
                            categorizedPicturesSelectedList = emptySet()
                            numberOfSelectedPictures = 0
                            mode!!.finish()
                        }
                    else -> {

                    }
                }
                return true
            }

            override fun onDestroyActionMode(mode: ActionMode?) {
                for (i in categorizedPicturesList.indices) {
                    binding.gridViewDisplayImageSet[i].alpha = 1F
                    binding.gridViewDisplayImageSet[i].elevation = tenDp
                }
                displayImageSetAdapter.updatePictures(categorizedPicturesList)
                displayImageSetAdapter.notifyDataSetChanged()
            }

            override fun onItemCheckedStateChanged(
                mode: ActionMode?,
                position: Int,
                id: Long,
                checked: Boolean
            ) {
                /**
                 * When a picture is selected, lower it's alpha value to clearly see that the image is selected.
                 */
                if (checked) {
                    numberOfSelectedPictures += 1
                    mode!!.title =
                        resources.getQuantityString(
                            R.plurals.DisplayImageSet_numberOfSelectedPicturesText,
                            numberOfSelectedPictures,
                            numberOfSelectedPictures
                        )
                    binding.gridViewDisplayImageSet[position].alpha = 0.35F
                    binding.gridViewDisplayImageSet[position].elevation = 0F
                    categorizedPicturesSelectedList =
                        categorizedPicturesSelectedList.plus(
                            categorizedPicturesList.elementAt(
                                position
                            )
                        )
                } else {
                    numberOfSelectedPictures -= 1
                    binding.gridViewDisplayImageSet[position].alpha = 1F
                    binding.gridViewDisplayImageSet[position].elevation = tenDp
                    mode!!.title =
                        resources.getQuantityString(
                            R.plurals.DisplayImageSet_numberOfSelectedPicturesText,
                            numberOfSelectedPictures,
                            numberOfSelectedPictures
                        )
                    categorizedPicturesSelectedList =
                        categorizedPicturesSelectedList.minus(
                            categorizedPicturesList.elementAt(
                                position
                            )
                        )
                }

                /**
                 * The star icon to set as representative picture is only displayed when exactly one
                 * picture is selected.
                 */
                mode.menu[1].isVisible = numberOfSelectedPictures == 1
            }

        })
    }

    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        inflater.inflate(R.menu.display_imageset_info_menu, menu)
        super.onCreateOptionsMenu(menu, inflater)
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        return FragmentOptionsUtil.displayInfoMenu(
            item = item,
            infoItemId = R.id.display_imageset_menu_info,
            title = getString(R.string.MetadataEditing_infoTitle),
            message = getString(R.string.DisplayImageSet_infoTitle),
            context = this.context
        )
    }
}