1 /*
<lambda>null2  * Copyright (C) 2022 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  *      http://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.Rect
21 import android.graphics.drawable.Drawable
22 import android.util.AttributeSet
23 import android.view.LayoutInflater
24 import android.view.View
25 import android.view.ViewGroup
26 import android.widget.TextView
27 import androidx.core.view.ViewCompat
28 import androidx.recyclerview.widget.LinearLayoutManager
29 import androidx.recyclerview.widget.RecyclerView
30 import com.android.intentresolver.R
31 
32 class ScrollableActionRow : RecyclerView, ActionRow {
33     constructor(context: Context) : this(context, null)
34     constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
35     constructor(
36         context: Context,
37         attrs: AttributeSet?,
38         defStyleAttr: Int
39     ) : super(context, attrs, defStyleAttr) {
40         layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
41         adapter = Adapter(context)
42 
43         addItemDecoration(
44             MarginDecoration(
45                 context.resources.getDimensionPixelSize(R.dimen.chooser_action_horizontal_margin),
46                 context.resources.getDimensionPixelSize(R.dimen.chooser_edge_margin_normal)
47             )
48         )
49     }
50 
51     private val actionsAdapter
52         get() = adapter as Adapter
53 
54     override fun setActions(actions: List<ActionRow.Action>) {
55         actionsAdapter.setActions(actions)
56     }
57 
58     override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
59         super.onLayout(changed, l, t, r, b)
60         setOverScrollMode(
61             if (areAllChildrenVisible) View.OVER_SCROLL_NEVER else View.OVER_SCROLL_ALWAYS
62         )
63     }
64 
65     private inner class Adapter(private val context: Context) : RecyclerView.Adapter<ViewHolder>() {
66         private val iconSize: Int =
67             context.resources.getDimensionPixelSize(R.dimen.chooser_action_view_icon_size)
68         private val itemLayout = R.layout.chooser_action_view
69         private var actions: List<ActionRow.Action> = emptyList()
70 
71         override fun onCreateViewHolder(parent: ViewGroup, type: Int): ViewHolder =
72             ViewHolder(
73                 LayoutInflater.from(context).inflate(itemLayout, null) as TextView,
74                 iconSize,
75             )
76 
77         override fun onBindViewHolder(holder: ViewHolder, position: Int) {
78             holder.bind(actions[position])
79         }
80 
81         override fun getItemCount() = actions.size
82 
83         override fun onViewRecycled(holder: ViewHolder) {
84             holder.unbind()
85         }
86 
87         override fun onFailedToRecycleView(holder: ViewHolder): Boolean {
88             holder.unbind()
89             return super.onFailedToRecycleView(holder)
90         }
91 
92         fun setActions(actions: List<ActionRow.Action>) {
93             this.actions = ArrayList(actions)
94             notifyDataSetChanged()
95         }
96     }
97 
98     private inner class ViewHolder(
99         private val view: TextView,
100         private val iconSize: Int,
101     ) : RecyclerView.ViewHolder(view) {
102 
103         fun bind(action: ActionRow.Action) {
104             action.icon?.let { icon ->
105                 icon.setBounds(0, 0, iconSize, iconSize)
106                 // some drawables (edit) does not gets tinted when set to the top of the text
107                 // with TextView#setCompoundDrawableRelative
108                 tintIcon(icon, view)
109                 view.setCompoundDrawablesRelative(icon, null, null, null)
110             }
111             view.text = action.label ?: ""
112             view.setOnClickListener { action.onClicked.run() }
113             view.id = action.id
114         }
115 
116         fun unbind() {
117             view.setOnClickListener(null)
118         }
119 
120         private fun tintIcon(drawable: Drawable, view: TextView) {
121             val tintList = view.compoundDrawableTintList ?: return
122             drawable.setTintList(tintList)
123             view.compoundDrawableTintMode?.let { drawable.setTintMode(it) }
124             view.compoundDrawableTintBlendMode?.let { drawable.setTintBlendMode(it) }
125         }
126     }
127 
128     private class MarginDecoration(private val innerMargin: Int, private val outerMargin: Int) :
129         ItemDecoration() {
130         override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: State) {
131             val index = parent.getChildAdapterPosition(view)
132             val startMargin = if (index == 0) outerMargin else innerMargin
133             val endMargin = if (index == state.itemCount - 1) outerMargin else innerMargin
134 
135             if (ViewCompat.getLayoutDirection(parent) == ViewCompat.LAYOUT_DIRECTION_RTL) {
136                 outRect.right = startMargin
137                 outRect.left = endMargin
138             } else {
139                 outRect.left = startMargin
140                 outRect.right = endMargin
141             }
142         }
143     }
144 }
145