1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
|
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.tabstray.browser
import android.graphics.Canvas
import android.graphics.drawable.Drawable
import androidx.appcompat.content.res.AppCompatResources
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.ItemTouchHelper.ACTION_STATE_IDLE
import androidx.recyclerview.widget.RecyclerView
import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.browser.tabstray.TabTouchCallback
import mozilla.components.browser.tabstray.TabsTray
import mozilla.components.support.ktx.android.content.getColorFromAttr
import mozilla.components.support.ktx.android.content.getDrawableWithTint
import mozilla.components.support.ktx.android.util.dpToPx
import org.mozilla.fenix.R
/**
* A callback for consumers to know when a [RecyclerView.ViewHolder] is about to be touched.
* Return false if the custom behaviour should be ignored.
*/
typealias OnViewHolderTouched = (RecyclerView.ViewHolder) -> Boolean
/**
* A callback for consumers to know when a [RecyclerView.ViewHolder] is about to be drawn.
* Return false if the custom drawing should be ignored.
*/
typealias OnViewHolderToDraw = (RecyclerView.ViewHolder) -> Boolean
/**
* An [ItemTouchHelper] for handling tab swiping to delete.
*
* @param interactionDelegate [TabsTray.Delegate] for handling all user interactions.
* @param onViewHolderTouched See [OnViewHolderTouched].
* @param onViewHolderDraw See [OnViewHolderToDraw].
* @param featureNameHolder Contains the identifying name of the feature.
* @param delegate The Callback which controls the behavior of this touch helper.
*/
class TabsTouchHelper(
interactionDelegate: TabsTray.Delegate,
onViewHolderTouched: OnViewHolderTouched = { true },
onViewHolderDraw: OnViewHolderToDraw = { true },
featureNameHolder: FeatureNameHolder,
delegate: Callback = TouchCallback(interactionDelegate, onViewHolderTouched, onViewHolderDraw, featureNameHolder),
) : ItemTouchHelper(delegate)
/**
* An [ItemTouchHelper.Callback] for drawing custom layouts on [RecyclerView.ViewHolder] interactions.
*
* @param delegate [TabsTray.Delegate] for handling all user interactions.
* @param onViewHolderTouched Invoked when a tab is about to be swiped. See [OnViewHolderTouched].
* @param onViewHolderDraw Invoked when a tab is drawn. See [OnViewHolderToDraw].
* @param featureNameHolder Contains the identifying name of the feature.
* @param onRemove A callback invoked when a tab is removed.
*/
class TouchCallback(
delegate: TabsTray.Delegate,
private val onViewHolderTouched: OnViewHolderTouched,
private val onViewHolderDraw: OnViewHolderToDraw,
featureNameHolder: FeatureNameHolder,
onRemove: (TabSessionState) -> Unit = { delegate.onTabClosed(it, featureNameHolder.featureName) },
) : TabTouchCallback(onRemove) {
override fun getMovementFlags(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
): Int {
if (!onViewHolderTouched.invoke(viewHolder)) {
return ItemTouchHelper.Callback.makeFlag(ACTION_STATE_IDLE, 0)
}
return super.getMovementFlags(recyclerView, viewHolder)
}
override fun onChildDraw(
c: Canvas,
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
dX: Float,
dY: Float,
actionState: Int,
isCurrentlyActive: Boolean,
) {
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
if (!onViewHolderDraw.invoke(viewHolder)) {
return
}
val icon = recyclerView.context.getDrawableWithTint(
R.drawable.ic_delete,
recyclerView.context.getColorFromAttr(R.attr.textCritical),
)!!
val background = AppCompatResources.getDrawable(
recyclerView.context,
R.drawable.swipe_delete_background,
)!!
val itemView = viewHolder.itemView
val iconLeft: Int
val iconRight: Int
val margin =
MARGIN.dpToPx(recyclerView.resources.displayMetrics)
val iconWidth = icon.intrinsicWidth
val iconHeight = icon.intrinsicHeight
val cellHeight = itemView.bottom - itemView.top
val iconTop = itemView.top + (cellHeight - iconHeight) / 2
val iconBottom = iconTop + iconHeight
when {
dX > 0 -> { // Swiping to the right
iconLeft = itemView.left + margin
iconRight = itemView.left + margin + iconWidth
background.setBounds(
itemView.left,
itemView.top,
(itemView.left + dX).toInt() + BACKGROUND_CORNER_OFFSET,
itemView.bottom,
)
icon.setBounds(iconLeft, iconTop, iconRight, iconBottom)
draw(background, icon, c)
}
dX < 0 -> { // Swiping to the left
iconLeft = itemView.right - margin - iconWidth
iconRight = itemView.right - margin
background.setBounds(
(itemView.right + dX).toInt() - BACKGROUND_CORNER_OFFSET,
itemView.top,
itemView.right,
itemView.bottom,
)
icon.setBounds(iconLeft, iconTop, iconRight, iconBottom)
draw(background, icon, c)
}
else -> { // View not swiped
background.setBounds(0, 0, 0, 0)
icon.setBounds(0, 0, 0, 0)
}
}
}
private fun draw(
background: Drawable,
icon: Drawable,
c: Canvas,
) {
background.draw(c)
icon.draw(c)
}
companion object {
const val BACKGROUND_CORNER_OFFSET = 40
const val MARGIN = 32
}
}
|