Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ef1697e5 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Implement resizable tiles prototype" into main

parents d9c1f7a6 25e03a2c
Loading
Loading
Loading
Loading
+131 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.qs.panels.ui.compose.selection

import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith

@SmallTest
@RunWith(AndroidJUnit4::class)
class MutableSelectionStateTest : SysuiTestCase() {
    private val underTest = MutableSelectionState()

    @Test
    fun selectTile_isCorrectlySelected() {
        assertThat(underTest.isSelected(TEST_SPEC)).isFalse()

        underTest.select(TEST_SPEC)
        assertThat(underTest.isSelected(TEST_SPEC)).isTrue()

        underTest.unSelect()
        assertThat(underTest.isSelected(TEST_SPEC)).isFalse()

        val newSpec = TileSpec.create("newSpec")
        underTest.select(TEST_SPEC)
        underTest.select(newSpec)
        assertThat(underTest.isSelected(TEST_SPEC)).isFalse()
        assertThat(underTest.isSelected(newSpec)).isTrue()
    }

    @Test
    fun startResize_createsResizingState() {
        assertThat(underTest.resizingState).isNull()

        // Resizing starts but no tile is selected
        underTest.onResizingDragStart(TileWidths(0, 0, 1)) {}
        assertThat(underTest.resizingState).isNull()

        // Resizing starts with a selected tile
        underTest.select(TEST_SPEC)
        underTest.onResizingDragStart(TileWidths(0, 0, 1)) {}

        assertThat(underTest.resizingState).isNotNull()
    }

    @Test
    fun endResize_clearsResizingState() {
        val spec = TileSpec.create("testSpec")

        // Resizing starts with a selected tile
        underTest.select(spec)
        underTest.onResizingDragStart(TileWidths(base = 0, min = 0, max = 10)) {}
        assertThat(underTest.resizingState).isNotNull()

        underTest.onResizingDragEnd()
        assertThat(underTest.resizingState).isNull()
    }

    @Test
    fun unselect_clearsResizingState() {
        // Resizing starts with a selected tile
        underTest.select(TEST_SPEC)
        underTest.onResizingDragStart(TileWidths(base = 0, min = 0, max = 10)) {}
        assertThat(underTest.resizingState).isNotNull()

        underTest.unSelect()
        assertThat(underTest.resizingState).isNull()
    }

    @Test
    fun onResizingDrag_updatesResizingState() {
        // Resizing starts with a selected tile
        underTest.select(TEST_SPEC)
        underTest.onResizingDragStart(TileWidths(base = 0, min = 0, max = 10)) {}
        assertThat(underTest.resizingState).isNotNull()

        underTest.onResizingDrag(5f)
        assertThat(underTest.resizingState?.width).isEqualTo(5)

        underTest.onResizingDrag(2f)
        assertThat(underTest.resizingState?.width).isEqualTo(7)

        underTest.onResizingDrag(-6f)
        assertThat(underTest.resizingState?.width).isEqualTo(1)
    }

    @Test
    fun onResizingDrag_receivesResizeCallback() {
        var resized = false
        val onResize: () -> Unit = { resized = !resized }

        // Resizing starts with a selected tile
        underTest.select(TEST_SPEC)
        underTest.onResizingDragStart(TileWidths(base = 0, min = 0, max = 10), onResize)
        assertThat(underTest.resizingState).isNotNull()

        // Drag under the threshold
        underTest.onResizingDrag(1f)
        assertThat(resized).isFalse()

        // Drag over the threshold
        underTest.onResizingDrag(5f)
        assertThat(resized).isTrue()

        // Drag back under the threshold
        underTest.onResizingDrag(-5f)
        assertThat(resized).isFalse()
    }

    companion object {
        private val TEST_SPEC = TileSpec.create("testSpec")
    }
}
+62 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.qs.panels.ui.compose.selection

import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith

@SmallTest
@RunWith(AndroidJUnit4::class)
class ResizingStateTest : SysuiTestCase() {

    @Test
    fun drag_updatesStateCorrectly() {
        var resized = false
        val underTest =
            ResizingState(TileWidths(base = 0, min = 0, max = 10)) { resized = !resized }

        assertThat(underTest.width).isEqualTo(0)

        underTest.onDrag(2f)
        assertThat(underTest.width).isEqualTo(2)

        underTest.onDrag(1f)
        assertThat(underTest.width).isEqualTo(3)
        assertThat(resized).isTrue()

        underTest.onDrag(-1f)
        assertThat(underTest.width).isEqualTo(2)
        assertThat(resized).isFalse()
    }

    @Test
    fun dragOutOfBounds_isClampedCorrectly() {
        val underTest = ResizingState(TileWidths(base = 0, min = 0, max = 10)) {}

        assertThat(underTest.width).isEqualTo(0)

        underTest.onDrag(100f)
        assertThat(underTest.width).isEqualTo(10)

        underTest.onDrag(-200f)
        assertThat(underTest.width).isEqualTo(0)
    }
}
+32 −29
Original line number Diff line number Diff line
@@ -17,9 +17,10 @@
package com.android.systemui.qs.panels.ui.compose

import android.content.ClipData
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.draganddrop.dragAndDropSource
import androidx.compose.foundation.draganddrop.dragAndDropTarget
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress
import androidx.compose.foundation.lazy.grid.LazyGridItemInfo
import androidx.compose.foundation.lazy.grid.LazyGridState
import androidx.compose.runtime.Composable
@@ -104,11 +105,10 @@ fun Modifier.dragAndDropRemoveZone(
@Composable
fun Modifier.dragAndDropTileList(
    gridState: LazyGridState,
    contentOffset: Offset,
    contentOffset: () -> Offset,
    dragAndDropState: DragAndDropState,
    onDrop: () -> Unit,
    onDrop: (TileSpec) -> Unit,
): Modifier {
    val currentContentOffset by rememberUpdatedState(contentOffset)
    val target =
        remember(dragAndDropState) {
            object : DragAndDropTarget {
@@ -118,7 +118,7 @@ fun Modifier.dragAndDropTileList(

                override fun onMoved(event: DragAndDropEvent) {
                    // Drag offset relative to the list's top left corner
                    val relativeDragOffset = event.dragOffsetRelativeTo(currentContentOffset)
                    val relativeDragOffset = event.dragOffsetRelativeTo(contentOffset())
                    val targetItem =
                        gridState.layoutInfo.visibleItemsInfo.firstOrNull { item ->
                            // Check if the drag is on this item
@@ -132,7 +132,7 @@ fun Modifier.dragAndDropTileList(

                override fun onDrop(event: DragAndDropEvent): Boolean {
                    return dragAndDropState.draggedCell?.let {
                        onDrop()
                        onDrop(it.tile.tileSpec)
                        dragAndDropState.onDrop()
                        true
                    } ?: false
@@ -158,24 +158,26 @@ private fun insertAfter(item: LazyGridItemInfo, offset: Offset): Boolean {
    return item.span != 1 && offset.x > itemCenter.x
}

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun Modifier.dragAndDropTileSource(
    sizedTile: SizedTile<EditTileViewModel>,
    dragAndDropState: DragAndDropState,
    onTap: (TileSpec) -> Unit,
    onDoubleTap: (TileSpec) -> Unit = {},
    onDragStart: () -> Unit,
): Modifier {
    val state by rememberUpdatedState(dragAndDropState)
    return dragAndDropSource {
        detectTapGestures(
            onTap = { onTap(sizedTile.tile.tileSpec) },
            onDoubleTap = { onDoubleTap(sizedTile.tile.tileSpec) },
            onLongPress = {
                state.onStarted(sizedTile)

                // The tilespec from the ClipData transferred isn't actually needed as we're moving
                // a tile within the same application. We're using a custom MIME type to limit the
                // drag event to QS.
    val dragState by rememberUpdatedState(dragAndDropState)
    @Suppress("DEPRECATION") // b/368361871
    return dragAndDropSource(
        block = {
            detectDragGesturesAfterLongPress(
                onDrag = { _, _ -> },
                onDragStart = {
                    dragState.onStarted(sizedTile)
                    onDragStart()

                    // The tilespec from the ClipData transferred isn't actually needed as we're
                    // moving a tile within the same application. We're using a custom MIME type to
                    // limit the drag event to QS.
                    startTransfer(
                        DragAndDropTransferData(
                            ClipData(
@@ -188,6 +190,7 @@ fun Modifier.dragAndDropTileSource(
                },
            )
        }
    )
}

private object QsDragAndDrop {
+4 −5
Original line number Diff line number Diff line
@@ -42,10 +42,8 @@ fun rememberEditListState(
}

/** Holds the temporary state of the tile list during a drag movement where we move tiles around. */
class EditTileListState(
    tiles: List<SizedTile<EditTileViewModel>>,
    private val columns: Int,
) : DragAndDropState {
class EditTileListState(tiles: List<SizedTile<EditTileViewModel>>, private val columns: Int) :
    DragAndDropState {
    private val _draggedCell = mutableStateOf<SizedTile<EditTileViewModel>?>(null)
    override val draggedCell
        get() = _draggedCell.value
@@ -91,7 +89,8 @@ class EditTileListState(
            regenerateGrid(includeSpacers = true)
            _tiles.add(insertionIndex.coerceIn(0, _tiles.size), cell)
        } else {
            // Add the tile with a temporary row which will get reassigned when regenerating spacers
            // Add the tile with a temporary row which will get reassigned when
            // regenerating spacers
            _tiles.add(insertionIndex.coerceIn(0, _tiles.size), TileGridCell(draggedTile, 0))
        }

+3 −2
Original line number Diff line number Diff line
@@ -127,13 +127,14 @@ fun LargeTileContent(
}

@Composable
private fun LargeTileLabels(
fun LargeTileLabels(
    label: String,
    secondaryLabel: String?,
    colors: TileColors,
    modifier: Modifier = Modifier,
    accessibilityUiState: AccessibilityUiState? = null,
) {
    Column(verticalArrangement = Arrangement.Center, modifier = Modifier.fillMaxHeight()) {
    Column(verticalArrangement = Arrangement.Center, modifier = modifier.fillMaxHeight()) {
        Text(label, color = colors.label, modifier = Modifier.tileMarquee())
        if (!TextUtils.isEmpty(secondaryLabel)) {
            Text(
Loading