<repo_name>Loan555/Partent-Locking-CustomView<file_sep>/app/src/main/java/com/loan555/customview_canvas_animation/views/LockCustomView.kt
package com.loan555.customview_canvas_animation.views

import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import android.view.View
import androidx.core.content.ContextCompat
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.loan555.customview_canvas_animation.R

const val PARTITION_RATIO = 1 / 3f

class LockCustomView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
    private var myPassword = ""
    private val _password = MutableLiveData<String>().apply {
        value = myPassword
    }
    val password: LiveData<String> = _password

    private val countRow = 3
    private val countColumn = 3
    private var rectIndex = Pair(-1, -1)
    private var touching = false
    private var startPoint: Point? = null
    private var endPoint: Point? = null

    private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        style = Paint.Style.FILL
        strokeWidth = 12f
        color = Color.DKGRAY
    }
    private val highLightPaint = Paint().apply {
        color = ContextCompat.getColor(context!!, R.color.hilight)
        style = Paint.Style.FILL
        isAntiAlias = true
    }
    private lateinit var squares: Array<Array<Rect>>
    private lateinit var squaresData: Array<Array<String>>
    private val listPoint: ArrayList<Point> = ArrayList()

    override fun onDraw(canvas: Canvas?) {
        Log.d("aaa", "onDraw")
        super.onDraw(canvas)
        initializeTicTacToeSquares()
        if (touching) {
            drawHighlightDot(canvas)
        }
        squaresData.forEachIndexed { i, strings ->
            strings.forEachIndexed { j, s ->
                drawDot(canvas, squares[i][j])
                if (s != "")
                    drawTextInsideRectangle(canvas, squares[i][j], s)
            }
        }
        if (startPoint != null && endPoint != null)
            drawLine(canvas, startPoint!!, endPoint!!)
        drawAllLine(canvas)
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        Log.d("aaa", "onMeasure")
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        squaresData = Array(countRow) { Array(countColumn) { "" } }
    }

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        val x = event?.x
        val y = event?.y
        return when (event?.action) {
            MotionEvent.ACTION_DOWN -> {
                if (x != null && y != null) {
                    listPoint.clear()
                    myPassword = ""
                    rectIndex = getRectIndexesFor(x, y)
                    if (rectIndex.first != -1 && rectIndex.second != -1) {
                        touching = true
                        addKey(rectIndex.first, rectIndex.second)
                        postInvalidate()
                    }
                }
                true
            }
            MotionEvent.ACTION_MOVE -> {
                if (x != null && y != null) {
                    endPoint = Point(x.toInt(), y.toInt())
                    rectIndex = getRectIndexesFor(x, y)
                    if (rectIndex.first != -1 && rectIndex.second != -1) {
                        touching = true
                        addKey(rectIndex.first, rectIndex.second)
                    }
                    postInvalidate()
                }
                true
            }
            MotionEvent.ACTION_UP -> {
                touching = false
                if (myPassword.length < 4) {
                    squaresData = Array(countRow) { Array(countColumn) { "" } }
                    myPassword = ""
                    Log.e("aaa", "mat khau phai >= 4 ky tu")
                }
                listPoint.clear()
                _password.value = myPassword
                startPoint = null
                endPoint = null
                postInvalidate()
                true
            }
            MotionEvent.ACTION_CANCEL -> {
                true
            }
            else -> super.onTouchEvent(event)
        }
    }

    private fun drawAllLine(canvas: Canvas?) {
        if (listPoint.size >= 2) {
            for (i in 0..listPoint.size - 2) {
                drawLine(canvas, listPoint[i], listPoint[i + 1])
            }
        }
    }

    private fun addKey(i: Int, j: Int) {
        val newKey = (i * 3 + j + 1).toString()
        var count = false
        myPassword.forEach {
            if (it.toString() == newKey) {
                count = true
                return@forEach
            }
        }
        if (!count) {
            val rect = squares[rectIndex.first][rectIndex.second]
            val point = Point(rect.centerX(), rect.centerY())
            listPoint.add(point)
            startPoint = point
            squaresData[i][j] = newKey
            myPassword += squaresData[i][j]

            Log.d("aaa", "pass = $<PASSWORD>")
        }
    }

    private fun drawHighlightDot(canvas: Canvas?) {
        if (rectIndex.first != -1 && rectIndex.second != -1) {
            val rect = squares[rectIndex.first][rectIndex.second]
            canvas?.drawCircle(
                rect.exactCenterX(),
                rect.exactCenterY(),
                (rect.right - rect.left).toFloat(),
                highLightPaint
            )
        }
    }

    private fun getRectIndexesFor(x: Float, y: Float): Pair<Int, Int> {
        squares.forEachIndexed { i, arrayOfRects ->
            arrayOfRects.forEachIndexed { j, rect ->
                if (rect.contains(x.toInt(), y.toInt()))
                    return Pair(i, j)
            }
        }
        return Pair(-1, -1)// x, y do not lie in our view
    }

    private fun drawDot(canvas: Canvas?, rect: Rect) {
        val radio = (rect.right - rect.left) / 4f
        canvas?.drawCircle(
            rect.left.toFloat() + (radio * 2),
            rect.top.toFloat() + (2 * radio),
            radio,
            paint
        )
    }

    private fun drawLine(canvas: Canvas?, startPoint: Point, endPoint: Point) {
        canvas?.drawLine(
            startPoint.x.toFloat(),
            startPoint.y.toFloat(),
            endPoint.x.toFloat(),
            endPoint.y.toFloat(),
            paint
        )
    }

    private fun initializeTicTacToeSquares() {
        squares = Array(countRow) { Array(countColumn) { Rect() } }
        val xUnit = (width * PARTITION_RATIO).toInt() // one unit on x-axis
        val yUnit = (height * PARTITION_RATIO).toInt() // one unit on y-axis
        for (j in 0 until countRow) {
            for (i in 0 until countColumn) {
                val cx = (xUnit * (i + 0.5f)).toInt()
                val cy = (yUnit * (j + 0.5f)).toInt()
                val radio = (width * PARTITION_RATIO * 0.1).toInt()
                squares[j][i] =
                    Rect(
                        cx - radio, cy - radio, cx + radio, cy + radio
                    )
            }
        }
    }

    private fun drawTextInsideRectangle(canvas: Canvas?, rect: Rect, str: String) {
        val xOffset = paint.measureText(str) * 0.5f
        val yOffset = paint.fontMetrics.ascent * -0.4f
        val textX = (rect.exactCenterX()) - xOffset
        val textY = (rect.exactCenterY()) + yOffset
        canvas?.drawText(str, textX, textY, paint)
    }
}<file_sep>/settings.gradle
rootProject.name = "CustomView-Canvas-Animation"
include ':app'
<file_sep>/app/src/main/java/com/loan555/customview_canvas_animation/AppPreferences.kt
package com.loan555.customview_canvas_animation

import android.content.Context
import android.content.SharedPreferences
import androidx.core.content.edit

object AppPreferences {
    private const val NAME = "SpinKotlin"
    private const val MODE = Context.MODE_PRIVATE
    lateinit var preferences: SharedPreferences

    private val password = Pair("<PASSWORD>", null)

    fun init(context: Context) {
        preferences = context.getSharedPreferences(NAME, MODE)
    }

    var myPassword: String?
        get() = preferences.getString(password.first, password.second)
        set(value) = preferences.edit {
            this.putString(password.first, value)
        }
}<file_sep>/README.md
"# Partent-Locking-CustomView" 
<file_sep>/app/src/main/java/com/loan555/customview_canvas_animation/views/EmotionalFaceView.kt
package com.loan555.customview_canvas_animation.views

import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.util.Log
import android.view.View

const val myTag = "aaa"

class EmotionalFaceView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
    // Paint object for coloring and styling
    private val paint = Paint(Paint.ANTI_ALIAS_FLAG)

    // Some colors for the face background, eyes and mouth.
    private var faceColor = Color.YELLOW
    private var eyesColor = Color.BLACK
    private var mouthColor = Color.WHITE
    private var borderColor = Color.BLACK

    // Face border width in pixels
    private var borderWidth = 4.0f

    // View size in pixels
    var size = 320
    private val mouthPath = Path()

    override fun draw(canvas: Canvas?) {
        Log.d(myTag,"draw")
        super.draw(canvas)

        drawFaceBackground(canvas)
        drawEyes(canvas)
        drawMouth(canvas)
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        Log.d(myTag,"onMeasure")
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        setMeasuredDimension(size, size)
//        setMeasuredDimension(size,size)
    }

    private fun drawFaceBackground(canvas: Canvas?) {
        // 1
        paint.color = faceColor
        paint.style = Paint.Style.FILL
        // 2
        val radius = size / 2f

        // 3
        canvas?.drawCircle(size / 2f, size / 2f, radius, paint)

        // 4
        paint.color = borderColor
        paint.style = Paint.Style.STROKE
        paint.strokeWidth = borderWidth

        // 5
        canvas?.drawCircle(size / 2f, size / 2f, radius - borderWidth / 2f, paint)
    }

    private fun drawEyes(canvas: Canvas?) {
// 1
        paint.color = eyesColor
        paint.style = Paint.Style.FILL

// 2
        val leftEyeRect = RectF(size * 0.32f, size * 0.23f, size * 0.43f, size * 0.50f)

        canvas?.drawOval(leftEyeRect, paint)

// 3
        val rightEyeRect = RectF(size * 0.57f, size * 0.23f, size * 0.68f, size * 0.50f)

        canvas?.drawOval(rightEyeRect, paint)
    }

    private fun drawMouth(canvas: Canvas?) {
// 1 move to (x0,y0)
        mouthPath.moveTo(size * 0.22f, size * 0.7f)
// 2 vẽ một đường cong bắt đầu từ điểm (x0, y0) đi qua các điểm (x1, y1) và (x2, y2)
        mouthPath.quadTo(size * 0.50f, size * 0.70f, size * 0.78f, size * 0.70f)
// 3 vẽ 1 đường cong từ điểm (x2, y2) tới điẻm (x0, y0) đi qua điểm (x3, y3)
        mouthPath.quadTo(size * 0.50f, size * 0.90f, size * 0.22f, size * 0.70f)
// 4
        paint.color = mouthColor
        paint.style = Paint.Style.FILL
// 5
        canvas?.drawPath(mouthPath, paint)
    }
}<file_sep>/app/src/main/java/com/loan555/customview_canvas_animation/model/Dot.kt
package com.loan555.customview_canvas_animation.model

data class Dot(
    val rowIndex: Int = 0,
    val columnIndex: Int = 0,
    val leftPoint: Int = 0,
    val rightPoint: Int = 0,
    val topPoint: Int = 0,
    val bottomPoint: Int = 0,
    val key: String? = null
)
<file_sep>/app/src/main/java/com/loan555/customview_canvas_animation/AppViewModel.kt
package com.loan555.customview_canvas_animation

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel

class AppViewModel : ViewModel() {
    private val _pass = MutableLiveData<String>().apply {
        value = ""
    }
    private val _page = MutableLiveData<Int>().apply {
        value = 0
    }
    val page: LiveData<Int> = _page

    fun setPass(newPass: String) {
        _pass.value = newPass
    }

    fun checkPass(commitPass: String): Boolean {
        if (commitPass == _pass.value)
            return true
        return false
    }

    fun currentPage(number: Int) {
        _page.value = number
    }
}<file_sep>/app/src/main/java/com/loan555/customview_canvas_animation/ui/home/HomeFragment.kt
package com.loan555.customview_canvas_animation.ui.home

import android.animation.*
import android.graphics.drawable.AnimatedImageDrawable
import android.graphics.drawable.AnimatedVectorDrawable
import android.graphics.drawable.AnimationDrawable
import android.os.Build
import android.os.Bundle
import android.transition.Transition
import android.transition.TransitionManager
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.animation.Animation
import android.view.animation.AnimationSet
import android.view.animation.AnimationUtils
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProviders
import com.loan555.customview_canvas_animation.AppViewModel
import com.loan555.customview_canvas_animation.R
import com.loan555.customview_canvas_animation.databinding.FragmentHomeBinding
import java.lang.Exception

class HomeFragment : Fragment() {

    private lateinit var viewModel: AppViewModel
    private var _binding: FragmentHomeBinding? = null
    private lateinit var animationDrawable: AnimationDrawable

    // This property is only valid between onCreateView and
    // onDestroyView.
    private val binding get() = _binding!!

    private lateinit var animation: Animation

    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        viewModel = activity?.let {
            ViewModelProviders.of(it).get(AppViewModel::class.java)
        } ?: throw Exception("Activity is null")

        _binding = FragmentHomeBinding.inflate(inflater, container, false)

        binding.fadeIn.setOnClickListener {
            animation = AnimationUtils.loadAnimation(this.context, R.anim.fade_in)
            binding.img.startAnimation(animation)
        }
        binding.fadeOut.setOnClickListener {
            binding.img.startAnimation(AnimationUtils.loadAnimation(this.context, R.anim.fade_out))
        }
        binding.blink.setOnClickListener {
            binding.img.startAnimation(AnimationUtils.loadAnimation(this.context, R.anim.blink))
        }
        binding.zoomIn.setOnClickListener {
            binding.img.startAnimation(AnimationUtils.loadAnimation(this.context, R.anim.zoom_in))
        }
        binding.zoomOut.setOnClickListener {
            binding.img.startAnimation(AnimationUtils.loadAnimation(this.context, R.anim.zoom_out))
        }
        binding.rotate.setOnClickListener {
            binding.img.startAnimation(AnimationUtils.loadAnimation(this.context, R.anim.rotate))
        }
        binding.move.setOnClickListener {
            binding.img.startAnimation(AnimationUtils.loadAnimation(this.context, R.anim.move))
        }
        binding.slideUp.setOnClickListener {
            binding.img.startAnimation(AnimationUtils.loadAnimation(this.context, R.anim.slide_up))
        }
        binding.slideIn.setOnClickListener {
            binding.img.startAnimation(AnimationUtils.loadAnimation(this.context, R.anim.slide_in))
        }
        binding.slideDown.setOnClickListener {
            binding.img.startAnimation(
                AnimationUtils.loadAnimation(
                    this.context,
                    R.anim.slide_down
                )
            )
        }
        binding.slideOut.setOnClickListener {
            binding.img.startAnimation(AnimationUtils.loadAnimation(this.context, R.anim.slide_out))
        }
        binding.sequential.setOnClickListener {
            binding.img.startAnimation(AnimationUtils.loadAnimation(this.context, R.anim.squential))
        }
        binding.together.setOnClickListener {
            binding.img.startAnimation(AnimationUtils.loadAnimation(this.context, R.anim.together))
        }
        binding.valueAnimator.setOnClickListener {
            ValueAnimator.ofFloat(0f, 100f).apply {
                duration = 1000
                repeatCount = Animation.INFINITE
                start()
                addUpdateListener {
                    binding.img.translationX = it.animatedValue as Float
                }
            }
        }
        binding.objectAnimation.setOnClickListener {
            ObjectAnimator.ofFloat(binding.img, "translationX", -100f).apply {
                duration = 1000
                start()
            }
        }
        binding.animatorSet.setOnClickListener {
            val bouncer = AnimatorSet().apply {
                play(ObjectAnimator.ofFloat(binding.img, "translationX", 100f).apply {
                    duration = 1000
                }).before(ObjectAnimator.ofFloat(binding.img, "alpha", 1f, 0f).apply {
                    duration = 1000
                })
            }
            AnimatorSet().apply {
                play(bouncer).before(ObjectAnimator.ofFloat(binding.img, "alpha", 0f, 1f).apply {
                    duration = 1000
                })
                start()
            }
        }
        binding.animationLayoutChange.setOnClickListener {

            val animFadeOut = ObjectAnimator.ofFloat(binding.linearLayout, "scaleY", 1f, 0f).apply {
                duration = 1000
            }
            val animSlideUp =
                ObjectAnimator.ofFloat(binding.linearLayout, "scaleY", 0.0f, 1.0f).apply {
                    duration = 1000
                }
            AnimatorSet().apply {
                play(animFadeOut).before(animSlideUp)
                start()
                addListener(object : AnimatorListenerAdapter() {
                    override fun onAnimationEnd(animation: Animator?) {
                        binding.linearLayout.visibility = View.GONE
                    }
                })
            }
        }
        binding.img.apply {
            setBackgroundResource(R.drawable.rocket_image)
            animationDrawable = background as AnimationDrawable
        }
        binding.img.setOnClickListener { animationDrawable.start() }
        binding.vectorAnimation.apply {
            setBackgroundResource(R.drawable.animatorvectordrawable)
            val anim = background as AnimatedVectorDrawable
            setOnClickListener { anim.start() }
        }
        binding.resetPass.setOnClickListener {
            viewModel.currentPage(0)
        }

        return binding.root
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}<file_sep>/app/src/main/java/com/loan555/customview_canvas_animation/ui/login/LoginFragment.kt
package com.loan555.customview_canvas_animation.ui.login

import android.graphics.Color
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.lifecycle.ViewModelProviders
import com.loan555.customview_canvas_animation.AppPreferences
import com.loan555.customview_canvas_animation.AppViewModel
import com.loan555.customview_canvas_animation.databinding.FragmentLoginBinding
import java.lang.Exception

class LoginFragment : Fragment() {

    private lateinit var viewModel: AppViewModel
    private var _binding: FragmentLoginBinding? = null
    private val binding get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        viewModel = activity?.let {
            ViewModelProviders.of(it).get(AppViewModel::class.java)
        } ?: throw Exception("Activity is null")
        // Inflate the layout for this fragment
        _binding = FragmentLoginBinding.inflate(inflater, container, false)

        binding.lockCustomView.password.observe(viewLifecycleOwner, {
            if (it != "") {
                if (checkMyPass(it)) {
                    Toast.makeText(this.context, "Đăng nhập thành công", Toast.LENGTH_SHORT).show()
                    viewModel.currentPage(3)
                }else{
                    binding.message.setTextColor(Color.RED)
                    binding.message.text = "Mật khẩu không đúng"
                }
            }
        })

        return binding.root
    }

    private fun checkMyPass(pass: String?): Boolean {
        AppPreferences.init(this.requireContext())
        if (pass == AppPreferences.myPassword) {
            return true
        }
        return false
    }
}<file_sep>/app/src/main/java/com/loan555/customview_canvas_animation/ui/notifications/NotificationsFragment.kt
package com.loan555.customview_canvas_animation.ui.notifications

import android.graphics.Color
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProviders
import com.loan555.customview_canvas_animation.AppPreferences
import com.loan555.customview_canvas_animation.AppViewModel
import com.loan555.customview_canvas_animation.databinding.FragmentNotificationsBinding
import java.lang.Exception

class NotificationsFragment : Fragment() {

    private lateinit var viewModel: AppViewModel
    private var _binding: FragmentNotificationsBinding? = null

    // This property is only valid between onCreateView and
    // onDestroyView.
    private val binding get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        viewModel = activity?.let {
            ViewModelProviders.of(it).get(AppViewModel::class.java)
        } ?: throw Exception("Activity is null")

        _binding = FragmentNotificationsBinding.inflate(inflater, container, false)
        binding.lockCustomView.password.observe(viewLifecycleOwner, {
            if (it != "") {
                if (viewModel.checkPass(it)) {
                    Toast.makeText(this.context, "Đặt mật khẩu thành công", Toast.LENGTH_SHORT)
                        .show()
                    AppPreferences.init(this.requireContext())
                    AppPreferences.myPassword = it
                    viewModel.currentPage(2)
                    viewModel.setPass("")
                } else {
                    binding.message.setTextColor(Color.RED)
                    binding.message.text = "Vui lòng nhập đúng mật khẩu mới"
                }
            }
        })

        return binding.root
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}<file_sep>/app/src/main/java/com/loan555/customview_canvas_animation/MainActivity.kt
package com.loan555.customview_canvas_animation

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.fragment.app.commit
import androidx.fragment.app.replace
import androidx.lifecycle.ViewModelProvider
import com.loan555.customview_canvas_animation.ui.dashboard.DashboardFragment
import com.loan555.customview_canvas_animation.ui.home.HomeFragment
import com.loan555.customview_canvas_animation.ui.login.LoginFragment
import com.loan555.customview_canvas_animation.ui.notifications.NotificationsFragment

class MainActivity : AppCompatActivity() {
    private lateinit var viewModel: AppViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewModel = ViewModelProvider(this).get(AppViewModel::class.java)
        AppPreferences.init(this)
        val myPassword = AppPreferences.myPassword
        if (myPassword != null) {
            viewModel.currentPage(2)
        }
        viewModel.page.observe(this, {
            when (it) {
                0 -> {
                    supportFragmentManager.commit {
                        setCustomAnimations(
                            R.anim.slide_in,
                            R.anim.fade_out,
                            R.anim.slide_out,
                            R.anim.fade_in
                        )
                        setReorderingAllowed(true)
                        replace<DashboardFragment>(R.id.viewFragment)
                        addToBackStack("setPass")
                    }
                }
                1 -> {
                    supportFragmentManager.commit {
                        setCustomAnimations(
                            R.anim.slide_in,
                            R.anim.fade_out,
                            R.anim.slide_out,
                            R.anim.fade_in
                        )
                        setReorderingAllowed(true)
                        replace<NotificationsFragment>(R.id.viewFragment)
                        addToBackStack("commitPass")
                    }
                }
                2 -> {
                    supportFragmentManager.commit {
                        setCustomAnimations(
                            R.anim.slide_in,
                            R.anim.slide_out
                        )
                        setReorderingAllowed(true)
                        replace<LoginFragment>(R.id.viewFragment)
                        addToBackStack("animationDemo")
                    }
                }
                3 -> {
                    supportFragmentManager.commit {
                        setCustomAnimations(
                            R.anim.slide_in,
                            R.anim.slide_out
                        )
                        setReorderingAllowed(true)
                        replace<HomeFragment>(R.id.viewFragment)
                        addToBackStack("animationDemo")
                    }
                }
            }
        })

        setContentView(R.layout.activity_main)
    }

    override fun onBackPressed() {
        when (viewModel.page.value) {
            1 -> {
                supportFragmentManager.popBackStack()
            }
            2 -> {
                this.finish()
            }
            3 -> {
                this.finish()
            }
        }
    }
}