meili-epfl/Meili

View on GitHub
app/src/main/java/com/github/epfl/meili/photo/PhotoEditActivity.kt

Summary

Maintainability
B
4 hrs
Test Coverage
B
85%
package com.github.epfl.meili.photo

import android.Manifest
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
import android.annotation.SuppressLint
import android.content.Intent
import android.content.pm.PackageManager.PERMISSION_GRANTED
import android.net.Uri
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.TableRow
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import com.github.epfl.meili.BuildConfig
import com.github.epfl.meili.R
import com.github.epfl.meili.databinding.ActivityPhotoEditBinding
import ja.burhanrashid52.photoeditor.OnPhotoEditorListener
import ja.burhanrashid52.photoeditor.PhotoEditor
import ja.burhanrashid52.photoeditor.PhotoEditor.OnSaveListener
import ja.burhanrashid52.photoeditor.PhotoFilter
import ja.burhanrashid52.photoeditor.ViewType
import java.io.File


/**
 * An activity which is launched after a photo has been taken by the camera activity. It lets the user edit the photo by drawing, adding filters, etc.
 */
class PhotoEditActivity : AppCompatActivity() {
    private lateinit var binding: ActivityPhotoEditBinding
    private lateinit var uri: Uri
    private lateinit var photoEditor: PhotoEditor

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

        // display image which was received from camera
        uri = intent.getParcelableExtra(CameraActivity.URI_KEY)!!
        binding.photoEditorView.source.setImageURI(uri)

        photoEditor = PhotoEditor.Builder(this, binding.photoEditorView).build()

        binding.undo.setOnClickListener { photoEditor.undo() }
        binding.redo.setOnClickListener { photoEditor.redo() }

        // drawing on photo
        binding.colorSlider.setOnColorChangeListener { _, _, _ -> photoEditor.brushColor = binding.colorSlider.color }

        setupFilters()
        setupText()

        // photo will get stored on phone once it is done being edited
        if (!isPermissionGranted()) {
            getStoragePermission()
        }
        setFabListener()

        // Handle emojis
        binding.emojis.setOnClickListener { binding.emojiContainer.visibility = View.VISIBLE }
        // Show scrollable emoji table
        makeEmojiTable()

        // default
        stopDrawing()
    }

    private fun setupFilters() {
        binding.bw.setOnClickListener { photoEditor.setFilterEffect(PhotoFilter.NONE) }
        binding.sepia.setOnClickListener { photoEditor.setFilterEffect(PhotoFilter.SEPIA) }
        binding.sharpen.setOnClickListener { photoEditor.setFilterEffect(PhotoFilter.SHARPEN) }
        binding.fishEye.setOnClickListener { photoEditor.setFilterEffect(PhotoFilter.FISH_EYE) }
        binding.saturate.setOnClickListener { photoEditor.setFilterEffect(PhotoFilter.SATURATE) }
    }

    private fun setupText() {
        photoEditor.setOnPhotoEditorListener(object : OnPhotoEditorListener {
            override fun onEditTextChangeListener(rootView: View, text: String, colorCode: Int) {
                photoEditor.editText(rootView, binding.enterText.text.toString(), binding.colorSlider.color)
            }
            override fun onAddViewListener(p0: ViewType?, p1: Int) {}
            override fun onRemoveViewListener(p0: ViewType?, p1: Int) {}
            override fun onStartViewChangeListener(p0: ViewType?) {}
            override fun onStopViewChangeListener(p0: ViewType?) {}
        })
        binding.addText.setOnClickListener {
            photoEditor.addText(binding.enterText.text.toString(), binding.colorSlider.color)
        }
    }

    fun onPhotoEditClick(view: View) {
        when (view) {
            binding.filters -> toggleFilters()
            binding.textButton -> toggleText()
            binding.paintModeButton -> toggleDrawing()
        }
    }

    private fun toggleDrawing() {
        if (!photoEditor.brushDrawableMode) {
            stopFilters()
            stopText()
            startDrawing()
        } else {
            stopDrawing()
        }
    }

    private fun toggleText() {
        if (!binding.enterText.isVisible) {
            stopDrawing()
            stopFilters()
            startText()
        } else {
            stopText()
        }
    }

    private fun toggleFilters() {
        if (!binding.filtersContainer.isVisible) {
            stopDrawing()
            stopText()
            startFilters()
        } else {
            stopFilters()
        }
    }

    override fun onRequestPermissionsResult(
            requestCode: Int,
            permissions: Array<String>,
            grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (isPermissionGranted()) {
            setFabListener()
        }
    }

    /*
     * When fab is clicked, send edited photo back to camera, which will then send it back to activity which launched camera
     */
    @SuppressLint("MissingPermission")
    private fun setFabListener() {
        binding.finishEdit.setOnClickListener {
            uri.path?.let { it1 ->
                photoEditor.saveAsFile(it1, object : OnSaveListener {
                    override fun onSuccess(imagePath: String) {
                        val intent = Intent()
                        intent.data = Uri.fromFile(File(imagePath))
                        setResult(RESULT_OK, intent)
                        finish()
                    }

                    override fun onFailure(exception: Exception) {
                        Log.e("PhotoEditor", "Failed to save Image")
                    }
                })
            }
        }
    }

    private fun getStoragePermission() {
        if (isPermissionGranted() && BuildConfig.DEBUG) {
            error("Failed assertion")
        }
        ActivityCompat.requestPermissions(
                this, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
                REQUEST_CODE
        )
    }

    private fun isPermissionGranted(): Boolean {
        return ContextCompat.checkSelfPermission(
                applicationContext,
                WRITE_EXTERNAL_STORAGE
        ) == PERMISSION_GRANTED
    }

    private fun startText() {
        binding.enterText.visibility = View.VISIBLE
        binding.addText.visibility = View.VISIBLE
        binding.enterText.setBackgroundColor(getColor(R.color.quantum_bluegrey100))
        binding.textButton.setBackgroundColor(getColor(R.color.quantum_bluegrey100))
        binding.colorSlider.visibility = View.VISIBLE
    }

    private fun startDrawing() {
        photoEditor.setBrushDrawingMode(true)
        binding.paintModeButton.setBackgroundColor(getColor(R.color.quantum_bluegrey100))
        binding.colorSlider.visibility = View.VISIBLE
    }

    private fun startFilters() {
        binding.filters.setBackgroundColor(getColor(R.color.quantum_bluegrey100))
        binding.filtersContainer.visibility = View.VISIBLE
    }

    private fun stopText() {
        binding.colorSlider.visibility = View.GONE
        binding.enterText.visibility = View.GONE
        binding.addText.visibility = View.GONE
        binding.textButton.setBackgroundColor(0)
    }

    private fun stopDrawing() {
        photoEditor.setBrushDrawingMode(false)
        binding.paintModeButton.setBackgroundColor(0)
        binding.colorSlider.visibility = View.GONE
    }

    private fun stopFilters() {
        binding.filters.setBackgroundColor(0)
        binding.filtersContainer.visibility = View.GONE
    }

    // Creates all textView in the emoji table
    private fun makeEmojiTable() {
        val emojis = PhotoEditor.getEmojis(this) // list of emojis (emojis are strings)
        var curRow = TableRow(this)

        // Make a new textView for each emoji and arrange in grid
        for (i in 0 until emojis.size) {
            // Make new row every 6 emojis
            if (i % NUM_EMOJIS_PER_ROW == 0 && i != 0) {
                binding.emojiTable.addView(curRow)
                curRow = TableRow(this)
            }

            // Make textView
            val textView = TextView(this)
            textView.textSize = EMOJI_SIZE
            textView.text = emojis[i]
            textView.setOnClickListener { addEmoji(emojis[i]) } // When emoji is clicked, add itself to the photoEditor

            // Add emoji to current row
            curRow.addView(textView)
        }
    }

    // Add the passed emoji to the photoEditor, and hide emoji table
    private fun addEmoji(emoji: String) {
        binding.emojiContainer.visibility = View.GONE
        photoEditor.addEmoji(emoji)
    }

    companion object {
        private const val REQUEST_CODE: Int = 1
        private const val NUM_EMOJIS_PER_ROW = 6
        private const val EMOJI_SIZE = 50f
    }
}