diff options
| author | 2023-11-17 18:10:33 +0000 | |
|---|---|---|
| committer | 2023-11-17 18:10:33 +0000 | |
| commit | fff1a90416497cb8a3cc5ab9e8bb956c71ae24f6 (patch) | |
| tree | 0a21f37f232bdcc242ef671f6a2c7766773816f3 | |
| parent | bfbd7ae91392acedc938041321f5a1d30eb7584d (diff) | |
| parent | 12537140a7d71f51dc7f1aa49857467b6e459eb6 (diff) | |
Merge changes Icc3721d9,I78357990 into main
* changes:
Move communal hub lazygrid logic to a separate function
Drag & drop to reorder and remove widget from glanceable hub
10 files changed, 484 insertions, 36 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..e8ecd3a66186 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,6 +56,7 @@ 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 @@ -67,31 +71,12 @@ fun CommunalHub( Box( modifier = modifier.fillMaxSize().background(Color.White), ) { - LazyHorizontalGrid( - modifier = modifier.height(Dimensions.GridHeight).align(Alignment.CenterStart), - 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) }, - ) { 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, - ), - ) - } - } + CommunalHubLazyGrid( + modifier = Modifier.height(Dimensions.GridHeight).align(Alignment.CenterStart), + communalContent = communalContent, + isEditMode = viewModel.isEditMode, + viewModel = viewModel, + ) if (viewModel.isEditMode && onOpenWidgetPicker != null) { IconButton(onClick = onOpenWidgetPicker) { Icon(Icons.Default.Add, stringResource(R.string.hub_mode_add_widget_button_text)) @@ -114,16 +99,80 @@ fun CommunalHub( } } +@OptIn(ExperimentalFoundationApi::class) +@Composable +private fun CommunalHubLazyGrid( + communalContent: List<CommunalContentModel>, + isEditMode: Boolean, + viewModel: BaseCommunalViewModel, + modifier: Modifier = Modifier, +) { + var gridModifier = modifier + val gridState = rememberLazyGridState() + var list = communalContent + var dragDropState: GridDragDropState? = null + if (isEditMode && viewModel is CommunalEditModeViewModel) { + val contentListState = rememberContentListState(communalContent, viewModel) + list = contentListState.list + dragDropState = rememberGridDragDropState(gridState, contentListState) + gridModifier = gridModifier.dragContainer(dragDropState) + } + LazyHorizontalGrid( + 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 = list.size, + key = { index -> list[index].key }, + span = { index -> GridItemSpan(list[index].size.span) }, + ) { index -> + val cardModifier = Modifier.fillMaxHeight().width(Dimensions.CardWidth) + val size = + SizeF( + Dimensions.CardWidth.value, + list[index].size.dp().value, + ) + if (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, + ) + } + } + } +} + @Composable 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 +183,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 +257,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) |