1818
1919package org.schabi.newpipe.ui.components.menu
2020
21+ import android.util.Log
2122import androidx.annotation.StringRes
2223import androidx.compose.foundation.BorderStroke
2324import androidx.compose.foundation.border
@@ -51,6 +52,7 @@ import androidx.compose.runtime.getValue
5152import androidx.compose.runtime.mutableIntStateOf
5253import androidx.compose.runtime.mutableStateOf
5354import androidx.compose.runtime.remember
55+ import androidx.compose.runtime.rememberCoroutineScope
5456import androidx.compose.runtime.setValue
5557import androidx.compose.runtime.toMutableStateList
5658import androidx.compose.ui.Alignment
@@ -75,16 +77,19 @@ import androidx.compose.ui.unit.IntOffset
7577import androidx.compose.ui.unit.IntSize
7678import androidx.compose.ui.unit.dp
7779import androidx.compose.ui.unit.toSize
80+ import kotlinx.coroutines.launch
7881import org.schabi.newpipe.R
7982import org.schabi.newpipe.ui.components.menu.LongPressAction.Type.Companion.DefaultEnabledActions
8083import org.schabi.newpipe.ui.detectDragGestures
8184import org.schabi.newpipe.ui.theme.AppTheme
8285import org.schabi.newpipe.util.text.FixedHeightCenteredText
86+ import kotlin.math.abs
8387import kotlin.math.floor
8488import kotlin.math.min
8589
90+ const val TAG = " LongPressMenuEditor"
91+
8692// TODO padding doesn't seem to work as expected when the list becomes scrollable?
87- // TODO does Android TV auto-scroll to the selected item when the list becomes scrollable?
8893@Composable
8994fun LongPressMenuEditor (modifier : Modifier = Modifier ) {
9095 // We get the current arrangement once and do not observe on purpose
@@ -115,6 +120,7 @@ fun LongPressMenuEditor(modifier: Modifier = Modifier) {
115120 }.toList().toMutableStateList()
116121 }
117122
123+ val coroutineScope = rememberCoroutineScope()
118124 val gridState = rememberLazyGridState()
119125 var activeDragItem by remember { mutableStateOf<ItemInList ?>(null ) }
120126 var activeDragPosition by remember { mutableStateOf(IntOffset .Zero ) }
@@ -173,7 +179,7 @@ fun LongPressMenuEditor(modifier: Modifier = Modifier) {
173179 // make sure it is not possible to move items in between a *Caption and a HeaderBox
174180 if (! items[i].isDraggable) i + = 1
175181 if (i < items.size && items[i] == ItemInList .HeaderBox ) i + = 1
176- if (i > rawItem.index && prevDragMarkerIndex < rawItem.index ) i - = 1
182+ if (rawItem.index in ( prevDragMarkerIndex + 1 ) .. < i ) i - = 1
177183 i
178184 }
179185
@@ -228,9 +234,11 @@ fun LongPressMenuEditor(modifier: Modifier = Modifier) {
228234 }
229235 }
230236
237+ // test scrolling on Android TV by adding `.padding(horizontal = 350.dp)` here
231238 BoxWithConstraints (modifier) {
232239 // otherwise we wouldn't know the amount of columns to handle the Up/Down key events
233240 val columns = maxOf(1 , floor(this .maxWidth / MinButtonWidth ).toInt())
241+
234242 LazyVerticalGrid (
235243 modifier = Modifier
236244 .safeDrawingPadding()
@@ -239,14 +247,14 @@ fun LongPressMenuEditor(modifier: Modifier = Modifier) {
239247 handleDragGestureChange = ::handleDragGestureChange,
240248 endDragGesture = ::completeDragGestureAndCleanUp,
241249 )
250+ // this huge .focusTarget().onKeyEvent() block just handles DPAD on Android TVs
242251 .focusTarget()
243252 .onKeyEvent { event ->
244253 if (event.type != KeyEventType .KeyDown ) {
245254 if (event.type == KeyEventType .KeyUp &&
246255 event.key == Key .DirectionDown &&
247256 currentlyFocusedItem < 0
248257 ) {
249- //
250258 currentlyFocusedItem = 0
251259 }
252260 return @onKeyEvent false
@@ -259,6 +267,7 @@ fun LongPressMenuEditor(modifier: Modifier = Modifier) {
259267 } else if (items[focusedItem].columnSpan == null ) {
260268 focusedItem - = 1
261269 } else {
270+ // go to the previous line
262271 var remaining = columns
263272 while (true ) {
264273 focusedItem - = 1
@@ -276,9 +285,10 @@ fun LongPressMenuEditor(modifier: Modifier = Modifier) {
276285 Key .DirectionDown -> {
277286 if (focusedItem >= items.size - 1 ) {
278287 return @onKeyEvent false
279- } else if (items[focusedItem].columnSpan == null ) {
288+ } else if (focusedItem < 0 || items[focusedItem].columnSpan == null ) {
280289 focusedItem + = 1
281290 } else {
291+ // go to the next line
282292 var remaining = columns
283293 while (true ) {
284294 focusedItem + = 1
@@ -332,13 +342,36 @@ fun LongPressMenuEditor(modifier: Modifier = Modifier) {
332342 // then there would be no indication of the current cursor position at all.
333343 completeDragGestureAndCleanUp()
334344 return @onKeyEvent false
345+ } else if (focusedItem >= items.size) {
346+ Log .w(TAG , " Invalid focusedItem $focusedItem : >= items size ${items.size} " )
347+ }
348+
349+ val rawItem = gridState.layoutInfo.visibleItemsInfo
350+ .minByOrNull { abs(it.index - focusedItem) }
351+ ? : return @onKeyEvent false // no item is visible at all, impossible case
352+
353+ // If the item we are going to focus is not visible or is close to the boundary,
354+ // scroll to it. Note that this will cause the "drag item" to appear misplaced,
355+ // since the drag item's position is set to the position of the focused item
356+ // before scrolling. However, it's not worth overcomplicating the logic just for
357+ // correcting the position of a drag hint on Android TVs.
358+ val h = rawItem.size.height
359+ if (rawItem.index != focusedItem ||
360+ rawItem.offset.y <= gridState.layoutInfo.viewportStartOffset + 0.8 * h ||
361+ rawItem.offset.y + 1.8 * h >= gridState.layoutInfo.viewportEndOffset
362+ ) {
363+ coroutineScope.launch {
364+ gridState.scrollToItem(focusedItem, - (0.8 * h).toInt())
365+ }
335366 }
336367
337368 val dragItem = activeDragItem
338369 if (dragItem != null ) {
339- val rawItem = gridState.layoutInfo.visibleItemsInfo
340- .firstOrNull { it.index == focusedItem }
341- ? : return @onKeyEvent false
370+ // This will mostly bring the drag item to the right position, but will
371+ // misplace it if the view just scrolled (see above), or if the DragMarker's
372+ // position is moved past HiddenCaption by handleDragGestureChange() below.
373+ // However, it's not worth overcomplicating the logic just for correcting
374+ // the position of a drag hint on Android TVs.
342375 activeDragPosition = rawItem.offset
343376 handleDragGestureChange(dragItem, rawItem)
344377 }
0 commit comments