diff options
| author | 2023-11-15 15:57:11 +0000 | |
|---|---|---|
| committer | 2023-11-17 14:41:52 +0000 | |
| commit | 03f5b4dc70ee14cdbb52da4c1b44ee636a0b9cf8 (patch) | |
| tree | 3d2fe1bf46b2b91a6771987cc43380477c048224 | |
| parent | 2b221f88b01d04a4d314a3a79e2d81c4a9d36b1d (diff) | |
Drag & drop to reorder and remove widget from glanceable hub
Added the infrastructure to handle drag & drop in the glanceable
hub grid when in edit mode to reorder and remove widgets.
Defined an edit state for the content list to store the ongoing
changes during dragging. On drag ends, persisit the state to db.
Only widget card is draggable and can be the drop target.
Bug: b/310969547
Flag: ACONFIG com.android.systemui.communal_hub DEVELOPMENT
Test: atest CommunalWidgetDaoTest
Test: atest CommunalWidgetRepositoryImplTest
Test: atest CommunalInteractorTest
Change-Id: I78357990e1494d784e7ccc3787c6d61019a5dd8d
10 files changed, 459 insertions, 26 deletions
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt index 2ba1b77fb76e..f7d331e5e8d5 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt @@ -19,6 +19,8 @@ package com.android.systemui.communal.ui.compose import android.os.Bundle import android.util.SizeF import android.widget.FrameLayout +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -31,11 +33,13 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridItemSpan import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid +import androidx.compose.foundation.lazy.grid.rememberLazyGridState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Edit import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.runtime.Composable @@ -45,7 +49,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp @@ -53,10 +56,12 @@ import androidx.compose.ui.viewinterop.AndroidView import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.communal.shared.model.CommunalContentSize import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel +import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel import com.android.systemui.media.controls.ui.MediaHierarchyManager import com.android.systemui.media.controls.ui.MediaHostState import com.android.systemui.res.R +@OptIn(ExperimentalFoundationApi::class) @Composable fun CommunalHub( modifier: Modifier = Modifier, @@ -67,29 +72,56 @@ fun CommunalHub( Box( modifier = modifier.fillMaxSize().background(Color.White), ) { + var gridModifier = Modifier.height(Dimensions.GridHeight).align(Alignment.CenterStart) + val gridState = rememberLazyGridState() + var list = communalContent + var dragDropState: GridDragDropState? = null + if (viewModel.isEditMode && viewModel is CommunalEditModeViewModel) { + val contentListState = rememberContentListState(communalContent, viewModel) + list = contentListState.list + dragDropState = rememberGridDragDropState(gridState, contentListState) + gridModifier = gridModifier.dragContainer(dragDropState) + } LazyHorizontalGrid( - modifier = modifier.height(Dimensions.GridHeight).align(Alignment.CenterStart), + modifier = gridModifier, + state = gridState, rows = GridCells.Fixed(CommunalContentSize.FULL.span), contentPadding = PaddingValues(horizontal = Dimensions.Spacing), horizontalArrangement = Arrangement.spacedBy(Dimensions.Spacing), verticalArrangement = Arrangement.spacedBy(Dimensions.Spacing), ) { items( - count = communalContent.size, - key = { index -> communalContent[index].key }, - span = { index -> GridItemSpan(communalContent[index].size.span) }, + count = list.size, + key = { index -> list[index].key }, + span = { index -> GridItemSpan(list[index].size.span) }, ) { index -> - CommunalContent( - modifier = Modifier.fillMaxHeight().width(Dimensions.CardWidth), - model = communalContent[index], - viewModel = viewModel, - deleteOnClick = if (viewModel.isEditMode) viewModel::onDeleteWidget else null, - size = - SizeF( - Dimensions.CardWidth.value, - communalContent[index].size.dp().value, - ), - ) + val cardModifier = Modifier.fillMaxHeight().width(Dimensions.CardWidth) + val size = + SizeF( + Dimensions.CardWidth.value, + list[index].size.dp().value, + ) + if (viewModel.isEditMode && dragDropState != null) { + DraggableItem(dragDropState = dragDropState, enabled = true, index = index) { + isDragging -> + val elevation by animateDpAsState(if (isDragging) 4.dp else 1.dp) + CommunalContent( + modifier = cardModifier, + deleteOnClick = viewModel::onDeleteWidget, + elevation = elevation, + model = list[index], + viewModel = viewModel, + size = size, + ) + } + } else { + CommunalContent( + modifier = cardModifier, + model = list[index], + viewModel = viewModel, + size = size, + ) + } } } if (viewModel.isEditMode && onOpenWidgetPicker != null) { @@ -119,11 +151,13 @@ private fun CommunalContent( model: CommunalContentModel, viewModel: BaseCommunalViewModel, size: SizeF, - deleteOnClick: ((id: Int) -> Unit)?, modifier: Modifier = Modifier, + elevation: Dp = 0.dp, + deleteOnClick: ((id: Int) -> Unit)? = null, ) { when (model) { - is CommunalContentModel.Widget -> WidgetContent(model, size, deleteOnClick, modifier) + is CommunalContentModel.Widget -> + WidgetContent(model, size, elevation, deleteOnClick, modifier) is CommunalContentModel.Smartspace -> SmartspaceContent(model, modifier) is CommunalContentModel.Tutorial -> TutorialContent(modifier) is CommunalContentModel.Umo -> Umo(viewModel, modifier) @@ -134,22 +168,20 @@ private fun CommunalContent( private fun WidgetContent( model: CommunalContentModel.Widget, size: SizeF, + elevation: Dp, deleteOnClick: ((id: Int) -> Unit)?, modifier: Modifier = Modifier, ) { // TODO(b/309009246): update background color - Box( + Card( modifier = modifier.fillMaxSize().background(Color.White), + elevation = CardDefaults.cardElevation(draggedElevation = elevation), ) { if (deleteOnClick != null) { IconButton(onClick = { deleteOnClick(model.appWidgetId) }) { - Icon( - Icons.Default.Close, - LocalContext.current.getString(R.string.button_to_remove_widget) - ) + Icon(Icons.Default.Close, stringResource(R.string.button_to_remove_widget)) } } - AndroidView( modifier = modifier, factory = { context -> @@ -210,7 +242,7 @@ private fun CommunalContentSize.dp(): Dp { } } -private object Dimensions { +object Dimensions { val CardWidth = 464.dp val CardHeightFull = 630.dp val CardHeightHalf = 307.dp diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt new file mode 100644 index 000000000000..89c57658b474 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2023 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.communal.ui.compose + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import com.android.systemui.communal.domain.model.CommunalContentModel +import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel + +@Composable +fun rememberContentListState( + communalContent: List<CommunalContentModel>, + viewModel: CommunalEditModeViewModel, +): ContentListState { + return remember(communalContent) { + ContentListState( + communalContent, + viewModel::onDeleteWidget, + viewModel::onReorderWidgets, + ) + } +} + +/** + * Keeps the current state of the [CommunalContentModel] list being edited. [GridDragDropState] + * interacts with this class to update the order in the list. [onSaveList] should be called on + * dragging ends to persist the state in db for better performance. + */ +class ContentListState +internal constructor( + communalContent: List<CommunalContentModel>, + private val onDeleteWidget: (id: Int) -> Unit, + private val onReorderWidgets: (ids: List<Int>) -> Unit, +) { + var list by mutableStateOf(communalContent) + private set + + /** Move item to a new position in the list. */ + fun onMove(fromIndex: Int, toIndex: Int) { + list = list.toMutableList().apply { add(toIndex, removeAt(fromIndex)) } + } + + /** Remove widget from the list and the database. */ + fun onRemove(indexToRemove: Int) { + if (list[indexToRemove] is CommunalContentModel.Widget) { + val widget = list[indexToRemove] as CommunalContentModel.Widget + list = list.toMutableList().apply { removeAt(indexToRemove) } + onDeleteWidget(widget.appWidgetId) + } + } + + /** Persist the new order with all the movements happened during dragging. */ + fun onSaveList() { + val widgetIds: List<Int> = + list.filterIsInstance<CommunalContentModel.Widget>().map { it.appWidgetId } + onReorderWidgets(widgetIds) + } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt new file mode 100644 index 000000000000..6cfa2f233f46 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2023 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.communal.ui.compose + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress +import androidx.compose.foundation.gestures.scrollBy +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.lazy.grid.LazyGridItemInfo +import androidx.compose.foundation.lazy.grid.LazyGridItemScope +import androidx.compose.foundation.lazy.grid.LazyGridState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.toOffset +import androidx.compose.ui.unit.toSize +import androidx.compose.ui.zIndex +import com.android.systemui.communal.domain.model.CommunalContentModel +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.launch + +@Composable +fun rememberGridDragDropState( + gridState: LazyGridState, + contentListState: ContentListState +): GridDragDropState { + val scope = rememberCoroutineScope() + val state = + remember(gridState, contentListState) { + GridDragDropState(state = gridState, contentListState = contentListState, scope = scope) + } + LaunchedEffect(state) { + while (true) { + val diff = state.scrollChannel.receive() + gridState.scrollBy(diff) + } + } + return state +} + +/** + * Handles drag and drop cards in the glanceable hub. While dragging to move, other items that are + * affected will dynamically get positioned and the state is tracked by [ContentListState]. When + * dragging to remove, affected cards will be moved and [ContentListState.onRemove] is called to + * remove the dragged item. On dragging ends, call [ContentListState.onSaveList] to persist the + * change. + */ +class GridDragDropState +internal constructor( + private val state: LazyGridState, + private val contentListState: ContentListState, + private val scope: CoroutineScope, +) { + var draggingItemIndex by mutableStateOf<Int?>(null) + private set + + internal val scrollChannel = Channel<Float>() + + private var draggingItemDraggedDelta by mutableStateOf(Offset.Zero) + private var draggingItemInitialOffset by mutableStateOf(Offset.Zero) + internal val draggingItemOffset: Offset + get() = + draggingItemLayoutInfo?.let { item -> + draggingItemInitialOffset + draggingItemDraggedDelta - item.offset.toOffset() + } + ?: Offset.Zero + + private val draggingItemLayoutInfo: LazyGridItemInfo? + get() = state.layoutInfo.visibleItemsInfo.firstOrNull { it.index == draggingItemIndex } + + internal fun onDragStart(offset: Offset) { + state.layoutInfo.visibleItemsInfo + .firstOrNull { item -> + item.isEditable && + offset.x.toInt() in item.offset.x..item.offsetEnd.x && + offset.y.toInt() in item.offset.y..item.offsetEnd.y + } + ?.apply { + draggingItemIndex = index + draggingItemInitialOffset = this.offset.toOffset() + } + } + + internal fun onDragInterrupted() { + if (draggingItemIndex != null) { + // persist list editing changes on dragging ends + contentListState.onSaveList() + draggingItemIndex = null + } + draggingItemDraggedDelta = Offset.Zero + draggingItemInitialOffset = Offset.Zero + } + + internal fun onDrag(offset: Offset) { + draggingItemDraggedDelta += offset + + val draggingItem = draggingItemLayoutInfo ?: return + val startOffset = draggingItem.offset.toOffset() + draggingItemOffset + val endOffset = startOffset + draggingItem.size.toSize() + val middleOffset = startOffset + (endOffset - startOffset) / 2f + + val targetItem = + state.layoutInfo.visibleItemsInfo.find { item -> + item.isEditable && + middleOffset.x.toInt() in item.offset.x..item.offsetEnd.x && + middleOffset.y.toInt() in item.offset.y..item.offsetEnd.y && + draggingItem.index != item.index + } + + if (targetItem != null) { + val scrollToIndex = + if (targetItem.index == state.firstVisibleItemIndex) { + draggingItem.index + } else if (draggingItem.index == state.firstVisibleItemIndex) { + targetItem.index + } else { + null + } + if (scrollToIndex != null) { + scope.launch { + // this is needed to neutralize automatic keeping the first item first. + state.scrollToItem(scrollToIndex, state.firstVisibleItemScrollOffset) + contentListState.onMove(draggingItem.index, targetItem.index) + } + } else { + contentListState.onMove(draggingItem.index, targetItem.index) + } + draggingItemIndex = targetItem.index + } else { + val overscroll = checkForOverscroll(startOffset, endOffset) + if (overscroll != 0f) { + scrollChannel.trySend(overscroll) + } + val removeOffset = checkForRemove(startOffset) + if (removeOffset != 0f) { + draggingItemIndex?.let { + contentListState.onRemove(it) + draggingItemIndex = null + } + } + } + } + + private val LazyGridItemInfo.offsetEnd: IntOffset + get() = this.offset + this.size + + /** Whether the grid item can be dragged or be a drop target. Only widget card is editable. */ + private val LazyGridItemInfo.isEditable: Boolean + get() = contentListState.list[this.index] is CommunalContentModel.Widget + + /** Calculate the amount dragged out of bound on both sides. Returns 0f if not overscrolled */ + private fun checkForOverscroll(startOffset: Offset, endOffset: Offset): Float { + return when { + draggingItemDraggedDelta.x > 0 -> + (endOffset.x - state.layoutInfo.viewportEndOffset).coerceAtLeast(0f) + draggingItemDraggedDelta.x < 0 -> + (startOffset.x - state.layoutInfo.viewportStartOffset).coerceAtMost(0f) + else -> 0f + } + } + + // TODO(b/309968801): a temporary solution to decide whether to remove card when it's dragged up + // and out of grid. Once we have a taskbar, calculate the intersection of the dragged item with + // the Remove button. + private fun checkForRemove(startOffset: Offset): Float { + return if (draggingItemDraggedDelta.y < 0) + (startOffset.y + Dimensions.CardHeightHalf.value - state.layoutInfo.viewportStartOffset) + .coerceAtMost(0f) + else 0f + } +} + +private operator fun IntOffset.plus(size: IntSize): IntOffset { + return IntOffset(x + size.width, y + size.height) +} + +private operator fun Offset.plus(size: Size): Offset { + return Offset(x + size.width, y + size.height) +} + +fun Modifier.dragContainer(dragDropState: GridDragDropState): Modifier { + return pointerInput(dragDropState) { + detectDragGesturesAfterLongPress( + onDrag = { change, offset -> + change.consume() + dragDropState.onDrag(offset = offset) + }, + onDragStart = { offset -> dragDropState.onDragStart(offset) }, + onDragEnd = { dragDropState.onDragInterrupted() }, + onDragCancel = { dragDropState.onDragInterrupted() } + ) + } +} + +/** Wrap LazyGrid item with additional modifier needed for drag and drop. */ +@ExperimentalFoundationApi +@Composable +fun LazyGridItemScope.DraggableItem( + dragDropState: GridDragDropState, + index: Int, + enabled: Boolean, + modifier: Modifier = Modifier, + content: @Composable (isDragging: Boolean) -> Unit +) { + if (!enabled) { + return Box(modifier = modifier) { content(false) } + } + val dragging = index == dragDropState.draggingItemIndex + val draggingModifier = + if (dragging) { + Modifier.zIndex(1f).graphicsLayer { + translationX = dragDropState.draggingItemOffset.x + translationY = dragDropState.draggingItemOffset.y + } + } else { + Modifier.animateItemPlacement() + } + Box(modifier = modifier.then(draggingModifier), propagateMinConstraints = true) { + content(dragging) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt index e50850d9cbbc..a12db6f8f346 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt @@ -91,7 +91,8 @@ constructor( interface CommunalWidgetDao { @Query( "SELECT * FROM communal_widget_table JOIN communal_item_rank_table " + - "ON communal_item_rank_table.uid = communal_widget_table.item_id" + "ON communal_item_rank_table.uid = communal_widget_table.item_id " + + "ORDER BY communal_item_rank_table.rank DESC" ) fun getWidgets(): Flow<Map<CommunalItemRank, CommunalWidgetItem>> @@ -112,6 +113,17 @@ interface CommunalWidgetDao { @Query("INSERT INTO communal_item_rank_table(rank) VALUES(:rank)") fun insertItemRank(rank: Int): Long + @Query("UPDATE communal_item_rank_table SET rank = :order WHERE uid = :itemUid") + fun updateItemRank(itemUid: Long, order: Int) + + @Transaction + fun updateWidgetOrder(ids: List<Int>) { + ids.forEachIndexed { index, it -> + val widget = getWidgetByIdNow(it) + updateItemRank(widget.itemId, ids.size - index) + } + } + @Transaction fun addWidget(widgetId: Int, provider: ComponentName, priority: Int): Long { return insertWidget( diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt index f7fee96c83c2..ded5581a3034 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt @@ -61,6 +61,9 @@ interface CommunalWidgetRepository { /** Delete a widget by id from app widget service and the database. */ fun deleteWidget(widgetId: Int) {} + + /** Update the order of widgets in the database. */ + fun updateWidgetOrder(ids: List<Int>) {} } @OptIn(ExperimentalCoroutinesApi::class) @@ -165,6 +168,15 @@ constructor( } } + override fun updateWidgetOrder(ids: List<Int>) { + applicationScope.launch(bgDispatcher) { + communalWidgetDao.updateWidgetOrder(ids) + logger.i({ "Updated the order of widget list with ids: $str1." }) { + str1 = ids.toString() + } + } + } + private fun mapToContentModel( entry: Map.Entry<CommunalItemRank, CommunalWidgetItem> ): CommunalWidgetContentModel { diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt index 927bf024215d..fd7f641c2e61 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt @@ -84,6 +84,9 @@ constructor( /** Delete a widget by id. */ fun deleteWidget(id: Int) = widgetRepository.deleteWidget(id) + /** Reorder widgets. The order in the list will be their display order in the hub. */ + fun updateWidgetOrder(ids: List<Int>) = widgetRepository.updateWidgetOrder(ids) + /** A list of widget content to be displayed in the communal hub. */ val widgetContent: Flow<List<CommunalContentModel.Widget>> = widgetRepository.communalWidgets.map { widgets -> diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt index 98f3594801f3..b4ab5fbfcc1a 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt @@ -43,6 +43,9 @@ abstract class BaseCommunalViewModel( /** Called as the UI requests deleting a widget. */ open fun onDeleteWidget(id: Int) {} + /** Called as the UI requests reordering widgets. */ + open fun onReorderWidgets(ids: List<Int>) {} + /** Called as the UI requests opening the widget editor. */ open fun onOpenWidgetEditor() {} } diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt index 14d9b2ca80f0..111f8b4ca48f 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt @@ -41,4 +41,6 @@ constructor( communalInteractor.widgetContent override fun onDeleteWidget(id: Int) = communalInteractor.deleteWidget(id) + + override fun onReorderWidgets(ids: List<Int>) = communalInteractor.updateWidgetOrder(ids) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt index 14ec4d44ab8c..16b2ed633f11 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt @@ -124,6 +124,39 @@ class CommunalWidgetDaoTest : SysuiTestCase() { assertThat(widgets()).containsExactly(communalItemRankEntry2, communalWidgetItemEntry2) } + @Test + fun reorderWidget_emitsWidgetsInNewOrder() = + testScope.runTest { + val widgetsToAdd = listOf(widgetInfo1, widgetInfo2) + val widgets = collectLastValue(communalWidgetDao.getWidgets()) + + widgetsToAdd.forEach { + val (widgetId, provider, priority) = it + communalWidgetDao.addWidget( + widgetId = widgetId, + provider = provider, + priority = priority, + ) + } + assertThat(widgets()) + .containsExactly( + communalItemRankEntry1, + communalWidgetItemEntry1, + communalItemRankEntry2, + communalWidgetItemEntry2 + ) + + val widgetIdsInNewOrder = listOf(widgetInfo2.widgetId, widgetInfo1.widgetId) + communalWidgetDao.updateWidgetOrder(widgetIdsInNewOrder) + assertThat(widgets()) + .containsExactly( + communalItemRankEntry2, + communalWidgetItemEntry2, + communalItemRankEntry1, + communalWidgetItemEntry1 + ) + } + data class FakeWidgetMetadata( val widgetId: Int, val provider: ComponentName, diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt index 28fae819de98..182712a13174 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt @@ -202,6 +202,20 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { } @Test + fun reorderWidgets_queryDb() = + testScope.runTest { + userUnlocked(true) + val repository = initCommunalWidgetRepository() + runCurrent() + + val ids = listOf(104, 103, 101) + repository.updateWidgetOrder(ids) + runCurrent() + + verify(communalWidgetDao).updateWidgetOrder(ids) + } + + @Test fun broadcastReceiver_communalDisabled_doNotRegisterUserUnlockedBroadcastReceiver() = testScope.runTest { communalEnabled(false) |