1 /*
2  * Copyright 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      https://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.intentresolver.widget
18 
19 import android.content.Context
20 import android.graphics.Canvas
21 import android.graphics.Color
22 import android.graphics.Paint
23 import android.util.AttributeSet
24 import android.util.TypedValue
25 import android.view.MotionEvent
26 import android.view.View
27 import android.widget.ImageView
28 import android.widget.LinearLayout
29 import com.android.intentresolver.R
30 
31 class ChooserTargetItemView(
32     context: Context,
33     attrs: AttributeSet?,
34     defStyleAttr: Int,
35     defStyleRes: Int,
36 ) : LinearLayout(context, attrs, defStyleAttr, defStyleRes) {
37     private val outlineRadius: Float
38     private val outlineWidth: Float
39     private val outlinePaint: Paint =
<lambda>null40         Paint(Paint.ANTI_ALIAS_FLAG).apply { style = Paint.Style.STROKE }
41     private val outlineInnerPaint: Paint =
<lambda>null42         Paint(Paint.ANTI_ALIAS_FLAG).apply { style = Paint.Style.STROKE }
43     private var iconView: ImageView? = null
44 
45     constructor(context: Context) : this(context, null)
46 
47     constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
48 
49     constructor(
50         context: Context,
51         attrs: AttributeSet?,
52         defStyleAttr: Int,
53     ) : this(context, attrs, defStyleAttr, 0)
54 
55     init {
56         val a = context.obtainStyledAttributes(attrs, R.styleable.ChooserTargetItemView)
57         val defaultWidth =
58             TypedValue.applyDimension(
59                 TypedValue.COMPLEX_UNIT_DIP,
60                 2f,
61                 context.resources.displayMetrics,
62             )
63         outlineRadius =
64             a.getDimension(R.styleable.ChooserTargetItemView_focusOutlineCornerRadius, 0f)
65         outlineWidth =
66             a.getDimension(R.styleable.ChooserTargetItemView_focusOutlineWidth, defaultWidth)
67 
68         outlinePaint.strokeWidth = outlineWidth
69         outlinePaint.color =
70             a.getColor(R.styleable.ChooserTargetItemView_focusOutlineColor, Color.TRANSPARENT)
71 
72         outlineInnerPaint.strokeWidth = outlineWidth
73         outlineInnerPaint.color =
74             a.getColor(R.styleable.ChooserTargetItemView_focusInnerOutlineColor, Color.TRANSPARENT)
75         a.recycle()
76     }
77 
onViewAddednull78     override fun onViewAdded(child: View) {
79         super.onViewAdded(child)
80         if (child is ImageView) {
81             iconView = child
82         }
83     }
84 
onViewRemovednull85     override fun onViewRemoved(child: View?) {
86         super.onViewRemoved(child)
87         if (child === iconView) {
88             iconView = null
89         }
90     }
91 
onHoverEventnull92     override fun onHoverEvent(event: MotionEvent): Boolean {
93         val iconView = iconView ?: return false
94         if (!isEnabled) return true
95         when (event.action) {
96             MotionEvent.ACTION_HOVER_ENTER -> {
97                 iconView.isHovered = true
98             }
99             MotionEvent.ACTION_HOVER_EXIT -> {
100                 iconView.isHovered = false
101             }
102         }
103         return true
104     }
105 
onInterceptHoverEventnull106     override fun onInterceptHoverEvent(event: MotionEvent?) = true
107 
108     override fun dispatchDraw(canvas: Canvas) {
109         super.dispatchDraw(canvas)
110         if (isFocused) {
111             drawFocusInnerOutline(canvas)
112             drawFocusOutline(canvas)
113         }
114     }
115 
drawFocusInnerOutlinenull116     private fun drawFocusInnerOutline(canvas: Canvas) {
117         val outlineOffset = outlineWidth + outlineWidth / 2
118         canvas.drawRoundRect(
119             outlineOffset,
120             outlineOffset,
121             maxOf(0f, width - outlineOffset),
122             maxOf(0f, height - outlineOffset),
123             outlineRadius - outlineWidth,
124             outlineRadius - outlineWidth,
125             outlineInnerPaint,
126         )
127     }
128 
drawFocusOutlinenull129     private fun drawFocusOutline(canvas: Canvas) {
130         val outlineOffset = outlineWidth / 2
131         canvas.drawRoundRect(
132             outlineOffset,
133             outlineOffset,
134             maxOf(0f, width - outlineOffset),
135             maxOf(0f, height - outlineOffset),
136             outlineRadius,
137             outlineRadius,
138             outlinePaint,
139         )
140     }
141 }
142