1 /* <lambda>null2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.communal.ui.compose 18 19 import android.content.ComponentName 20 import android.os.UserHandle 21 import androidx.compose.runtime.Composable 22 import androidx.compose.runtime.remember 23 import androidx.compose.runtime.toMutableStateList 24 import com.android.systemui.Flags.communalWidgetResizing 25 import com.android.systemui.communal.domain.model.CommunalContentModel 26 import com.android.systemui.communal.shared.model.CommunalContentSize 27 import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel 28 import com.android.systemui.communal.ui.viewmodel.DragHandle 29 import com.android.systemui.communal.ui.viewmodel.ResizeInfo 30 import com.android.systemui.communal.widgets.WidgetConfigurator 31 32 @Composable 33 fun rememberContentListState( 34 widgetConfigurator: WidgetConfigurator?, 35 communalContent: List<CommunalContentModel>, 36 viewModel: BaseCommunalViewModel, 37 ): ContentListState { 38 return remember(communalContent) { 39 ContentListState( 40 communalContent, 41 { componentName, user, rank -> 42 viewModel.onAddWidget(componentName, user, rank, widgetConfigurator) 43 }, 44 viewModel::onDeleteWidget, 45 viewModel::onReorderWidgets, 46 viewModel::onResizeWidget, 47 ) 48 } 49 } 50 51 /** 52 * Keeps the current state of the [CommunalContentModel] list being edited. [GridDragDropState] 53 * interacts with this class to update the order in the list. [onSaveList] should be called on 54 * dragging ends to persist the state in db for better performance. 55 */ 56 class ContentListState 57 internal constructor( 58 communalContent: List<CommunalContentModel>, 59 private val onAddWidget: (componentName: ComponentName, user: UserHandle, rank: Int) -> Unit, 60 private val onDeleteWidget: 61 (id: Int, key: String, componentName: ComponentName, rank: Int) -> Unit, 62 private val onReorderWidgets: (widgetIdToRankMap: Map<Int, Int>) -> Unit, 63 private val onResizeWidget: 64 ( 65 id: Int, 66 spanY: Int, 67 widgetIdToRankMap: Map<Int, Int>, 68 componentName: ComponentName, 69 rank: Int, 70 ) -> Unit, 71 ) { 72 var list = communalContent.toMutableStateList() 73 private set 74 75 /** Move item to a new position in the list. */ onMovenull76 fun onMove(fromIndex: Int, toIndex: Int) { 77 list.apply { add(toIndex, removeAt(fromIndex)) } 78 } 79 80 /** Swap the two items in the list with the given indices. */ swapItemsnull81 fun swapItems(index1: Int, index2: Int) { 82 list.apply { 83 val item1 = get(index1) 84 val item2 = get(index2) 85 set(index2, item1) 86 set(index1, item2) 87 } 88 } 89 90 /** Remove widget from the list and the database. */ onRemovenull91 fun onRemove(indexToRemove: Int) { 92 if (list[indexToRemove].isWidgetContent()) { 93 val widget = list[indexToRemove] as CommunalContentModel.WidgetContent 94 list.apply { removeAt(indexToRemove) } 95 onDeleteWidget(widget.appWidgetId, widget.key, widget.componentName, widget.rank) 96 } 97 } 98 99 /** Resize a widget, possibly re-ordering widgets if needed. */ resizenull100 fun resize(index: Int, resizeInfo: ResizeInfo) { 101 val item = list[index] 102 val currentSpan = item.size.span 103 val newSpan = currentSpan + resizeInfo.spans 104 // Only widgets can be resized 105 if ( 106 !communalWidgetResizing() || 107 currentSpan == newSpan || 108 item !is CommunalContentModel.WidgetContent.Widget 109 ) { 110 return 111 } 112 list[index] = item.copy(size = CommunalContentSize.toSize(newSpan)) 113 val prevItem = list.getOrNull(index - 1) 114 // Check if we have to update indices of items to accommodate the resize. 115 val widgetIdToRankMap: Map<Int, Int> = 116 if ( 117 resizeInfo.isExpanding && 118 resizeInfo.fromHandle == DragHandle.TOP && 119 prevItem is CommunalContentModel.WidgetContent.Widget 120 ) { 121 onMove(index - 1, index) 122 mapOf(prevItem.appWidgetId to index, item.appWidgetId to index - 1) 123 } else { 124 emptyMap() 125 } 126 val componentName = item.componentName 127 val rank = item.rank 128 onResizeWidget(item.appWidgetId, newSpan, widgetIdToRankMap, componentName, rank) 129 } 130 131 /** 132 * Persists the new order with all the movements happened during drag operations & the new 133 * widget drop (if applicable). 134 * 135 * @param newItemComponentName name of the new widget that was dropped into the list; null if no 136 * new widget was added. 137 * @param newItemUser user profile associated with the new widget that was dropped into the 138 * list; null if no new widget was added. 139 * @param newItemIndex index at which the a new widget was dropped into the list; null if no new 140 * widget was dropped. 141 */ onSaveListnull142 fun onSaveList( 143 newItemComponentName: ComponentName? = null, 144 newItemUser: UserHandle? = null, 145 newItemIndex: Int? = null, 146 ) { 147 // New widget added to the grid. Other widgets are shifted as needed at the database level. 148 if (newItemComponentName != null && newItemUser != null && newItemIndex != null) { 149 onAddWidget(newItemComponentName, newItemUser, /* rank= */ newItemIndex) 150 return 151 } 152 153 // No new widget, only reorder existing widgets. 154 val widgetIdToRankMap: Map<Int, Int> = 155 list 156 .mapIndexedNotNull { index, item -> 157 if (item is CommunalContentModel.WidgetContent) { 158 item.appWidgetId to index 159 } else { 160 null 161 } 162 } 163 .toMap() 164 onReorderWidgets(widgetIdToRankMap) 165 } 166 167 /** Returns true if the item at given index is editable. */ isItemEditablenull168 fun isItemEditable(index: Int) = list[index].isWidgetContent() 169 } 170