@@ -12,7 +12,6 @@ private const val SWAP_HYSTERESIS_PX = 8f
1212
1313internal data class ReorderVisibleItem (
1414 val key : String ,
15- val index : Int ,
1615 val offset : Int ,
1716 val size : Int ,
1817)
@@ -47,16 +46,6 @@ internal class NotificationActionsReorderController(
4746 initialActions : List <MessageNotificationAction >,
4847 initialCutoff : Int ,
4948 ) {
50- val currentActions = renderedItemsState
51- .filterIsInstance<NotificationListItem .Action >()
52- .map { it.action }
53- val currentCutoff = cutoffIndexState
54-
55- val normalizedIncomingCutoff = initialCutoff.coerceIn(0 , initialActions.size)
56- if (currentActions == initialActions && currentCutoff == normalizedIncomingCutoff) {
57- return
58- }
59-
6049 renderedItemsState.clear()
6150 renderedItemsState.addAll(buildRenderedItems(actions = initialActions, cutoff = initialCutoff))
6251 cutoffIndexState = renderedItemsState.indexOfFirst { it is NotificationListItem .Cutoff }
@@ -79,35 +68,86 @@ internal class NotificationActionsReorderController(
7968 val fromIndex = renderedItemsState.indexOfFirst { it.key == key }
8069 if (fromIndex == - 1 ) return
8170
82- if (deltaY > 0f ) {
83- val nextItemKey = renderedItemsState.getOrNull(fromIndex + 1 )?.key ? : return
84- val nextItem = visibleItems.firstOrNull { it.key == nextItemKey } ? : return
85-
86- val draggedBottom = draggedItem.offset + draggedItem.size + draggedOffsetY
87- val nextThreshold = nextItem.offset + (nextItem.size / 2f )
88- if (draggedBottom >= nextThreshold + SWAP_HYSTERESIS_PX ) {
89- if (moveRenderedItem(from = fromIndex, to = fromIndex + 1 )) {
90- dragDidMove = true
91- draggedOffsetY - = nextItem.size.toFloat()
92- }
93- }
94- } else if (deltaY < 0f ) {
95- val upwardSwap = calculateUpwardSwap(
71+ val swapDecision = findSwapDecision(
72+ deltaY = deltaY,
73+ fromIndex = fromIndex,
74+ draggedItem = draggedItem,
75+ visibleItems = visibleItems,
76+ ) ? : return
77+
78+ if (moveRenderedItem(from = fromIndex, to = swapDecision.toIndex)) {
79+ dragDidMove = true
80+ draggedOffsetY + = swapDecision.offsetCompensation
81+ }
82+ }
83+
84+ private data class SwapDecision (
85+ val toIndex : Int ,
86+ val offsetCompensation : Float ,
87+ )
88+
89+ private fun findSwapDecision (
90+ deltaY : Float ,
91+ fromIndex : Int ,
92+ draggedItem : ReorderVisibleItem ,
93+ visibleItems : List <ReorderVisibleItem >,
94+ ): SwapDecision ? {
95+ return when {
96+ deltaY > 0f -> findDownwardSwapDecision(
9697 fromIndex = fromIndex,
98+ draggedItem = draggedItem,
9799 visibleItems = visibleItems,
98- ) ? : return
99- if (draggedItem.offset + draggedOffsetY <= upwardSwap.threshold - SWAP_HYSTERESIS_PX ) {
100- if (moveRenderedItem(from = fromIndex, to = fromIndex - 1 )) {
101- dragDidMove = true
102- draggedOffsetY + = upwardSwap.offsetCompensation.toFloat()
103- }
104- }
100+ )
101+
102+ deltaY < 0f -> findUpwardSwapDecision(
103+ fromIndex = fromIndex,
104+ draggedItem = draggedItem,
105+ visibleItems = visibleItems,
106+ )
107+
108+ else -> null
105109 }
106110 }
107111
112+ private fun findDownwardSwapDecision (
113+ fromIndex : Int ,
114+ draggedItem : ReorderVisibleItem ,
115+ visibleItems : List <ReorderVisibleItem >,
116+ ): SwapDecision ? {
117+ val nextItemKey = renderedItemsState.getOrNull(fromIndex + 1 )?.key ? : return null
118+ val nextItem = visibleItems.firstOrNull { it.key == nextItemKey } ? : return null
119+
120+ val draggedBottom = draggedItem.offset + draggedItem.size + draggedOffsetY
121+ val nextThreshold = nextItem.offset + (nextItem.size / 2f )
122+ if (draggedBottom < nextThreshold + SWAP_HYSTERESIS_PX ) return null
123+
124+ return SwapDecision (
125+ toIndex = fromIndex + 1 ,
126+ offsetCompensation = - nextItem.size.toFloat(),
127+ )
128+ }
129+
130+ private fun findUpwardSwapDecision (
131+ fromIndex : Int ,
132+ draggedItem : ReorderVisibleItem ,
133+ visibleItems : List <ReorderVisibleItem >,
134+ ): SwapDecision ? {
135+ val upwardSwap = calculateUpwardSwap(
136+ fromIndex = fromIndex,
137+ visibleItems = visibleItems,
138+ ) ? : return null
139+ val draggedTop = draggedItem.offset + draggedOffsetY
140+ if (draggedTop > upwardSwap.threshold - SWAP_HYSTERESIS_PX ) return null
141+
142+ return SwapDecision (
143+ toIndex = fromIndex - 1 ,
144+ offsetCompensation = upwardSwap.offsetCompensation,
145+ )
146+ }
147+
108148 private data class UpwardSwapConfig (
109149 val threshold : Float ,
110- val offsetCompensation : Int ,
150+ val offsetCompensation : Float ,
111151 )
112152
113153 private fun calculateUpwardSwap (
@@ -117,23 +157,22 @@ internal class NotificationActionsReorderController(
117157 val previousItemKey = renderedItemsState.getOrNull(fromIndex - 1 )?.key ? : return null
118158 val previousItem = visibleItems.firstOrNull { it.key == previousItemKey } ? : return null
119159
120- val maxPos = NOTIFICATION_PREFERENCE_MAX_MESSAGE_ACTIONS_SHOWN
121- .coerceAtMost(renderedItemsState.lastIndex)
160+ val maxPos = maxAllowedCutoffIndex()
122161 val crossingUpThroughFullCutoff = previousItemKey == NotificationListItem .Cutoff .key &&
123162 cutoffIndexState >= maxPos
124163
125164 if (! crossingUpThroughFullCutoff) {
126165 return UpwardSwapConfig (
127166 threshold = previousItem.offset + (previousItem.size / 2f ),
128- offsetCompensation = previousItem.size,
167+ offsetCompensation = previousItem.size.toFloat() ,
129168 )
130169 }
131170
132171 val lastAboveKey = renderedItemsState.getOrNull(fromIndex - 2 )?.key ? : return null
133172 val lastAboveItem = visibleItems.firstOrNull { it.key == lastAboveKey } ? : return null
134173 return UpwardSwapConfig (
135174 threshold = lastAboveItem.offset + (lastAboveItem.size / 2f ),
136- offsetCompensation = previousItem.size + lastAboveItem.size,
175+ offsetCompensation = ( previousItem.size + lastAboveItem.size).toFloat() ,
137176 )
138177 }
139178
@@ -165,16 +204,22 @@ internal class NotificationActionsReorderController(
165204 if (from == - 1 ) return false
166205
167206 val to = from + delta
168- return from in renderedItemsState.indices && to in renderedItemsState.indices && from ! = to
207+ return isMoveAllowed( from = from, to = to)
169208 }
170209
171- private fun moveRenderedItem (from : Int , to : Int ): Boolean {
210+ private fun isMoveAllowed (from : Int , to : Int ): Boolean {
172211 if (from !in renderedItemsState.indices || to !in renderedItemsState.indices || from == to) return false
173- val maxPos = NOTIFICATION_PREFERENCE_MAX_MESSAGE_ACTIONS_SHOWN
174- .coerceAtMost(renderedItemsState.lastIndex )
212+
213+ val maxPos = maxAllowedCutoffIndex( )
175214
176215 // Don't allow the divider to be dragged to more than the max position
177- if (from == cutoffIndexState && to > maxPos) return false
216+ return ! (from == cutoffIndexState && to > maxPos)
217+ }
218+
219+ private fun moveRenderedItem (from : Int , to : Int ): Boolean {
220+ if (! isMoveAllowed(from = from, to = to)) return false
221+
222+ val maxPos = maxAllowedCutoffIndex()
178223
179224 val previousCutoff = cutoffIndexState
180225 val crossedCutoffUpward = previousCutoff in to.. < from
@@ -201,6 +246,11 @@ internal class NotificationActionsReorderController(
201246 return true
202247 }
203248
249+ private fun maxAllowedCutoffIndex (): Int {
250+ return NOTIFICATION_PREFERENCE_MAX_MESSAGE_ACTIONS_SHOWN
251+ .coerceAtMost(renderedItemsState.lastIndex)
252+ }
253+
204254 private fun buildRenderedItems (
205255 actions : List <MessageNotificationAction >,
206256 cutoff : Int ,
0 commit comments