• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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