1 /*
<lambda>null2  * Copyright 2024 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 androidx.compose.foundation.text.contextmenu.builder
18 
19 import androidx.collection.mutableObjectListOf
20 import androidx.compose.foundation.text.contextmenu.data.PlatformIcon
21 import androidx.compose.foundation.text.contextmenu.data.TextContextMenuComponent
22 import androidx.compose.foundation.text.contextmenu.data.TextContextMenuData
23 import androidx.compose.foundation.text.contextmenu.data.TextContextMenuItem
24 import androidx.compose.foundation.text.contextmenu.data.TextContextMenuSeparator
25 import androidx.compose.foundation.text.contextmenu.data.TextContextMenuSession
26 import androidx.compose.foundation.text.contextmenu.modifier.addTextContextMenuComponents
27 
28 /**
29  * Scope for building a text context menu in
30  * [Modifier.addTextContextMenuComponents][addTextContextMenuComponents]. See member functions for
31  * how to add context menu components to this scope as part of the
32  * [Modifier.addTextContextMenuComponents][addTextContextMenuComponents] modifier.
33  */
34 // TODO(grantapher-cm-api-publicize) Make class public
35 internal class TextContextMenuBuilderScope internal constructor() {
36     private val components = mutableObjectListOf<TextContextMenuComponent>()
37     private val filters = mutableObjectListOf<(TextContextMenuComponent) -> Boolean>()
38 
39     /**
40      * Build the current state of this into a [TextContextMenuData]. This applies a few filters to
41      * the components:
42      * * No back-to-back separators.
43      * * No heading or tailing separators.
44      * * Applies [filters] to each component, excluding separators.
45      */
46     internal fun build(): TextContextMenuData {
47         val resultList = mutableObjectListOf<TextContextMenuComponent>()
48 
49         var headIsSeparator = true
50         var previous: TextContextMenuComponent? = null
51         components.forEach { current ->
52             // remove heading separators
53             if (headIsSeparator && current === TextContextMenuSeparator) return@forEach
54             headIsSeparator = false
55 
56             // remove back-to-back separators
57             if (current.isSeparator && previous.isSeparator) return@forEach
58 
59             // apply `filters` unless a component is a separator
60             if (!current.isSeparator && filters.any { filter -> !filter(current) }) return@forEach
61 
62             resultList += current
63             previous = current
64         }
65 
66         // remove the remaining trailing separator, if there is one.
67         if (resultList.lastOrNull().isSeparator) {
68             @Suppress("Range") // lastIndex will not be -1 because the list cannot be empty
69             resultList.removeAt(resultList.lastIndex)
70         }
71 
72         @Suppress("AsCollectionCall") // need to use asList as this enters a public api.
73         return TextContextMenuData(components = resultList.asList())
74     }
75 
76     /**
77      * Adds a [filter] to be applied to each component in [components] when the [build] method is
78      * called.
79      *
80      * @param filter A predicate that determines whether a [TextContextMenuComponent] should be
81      *   added to the final list of components. This filter will never receive a
82      *   [TextContextMenuSeparator] since they are excluded from filtering.
83      */
84     internal fun addFilter(filter: (TextContextMenuComponent) -> Boolean) {
85         filters += filter
86     }
87 
88     // TODO(grantapher-cm-api-publicize) add AddItemToTextContextMenuAndroid sample
89     /**
90      * Adds an item to the list of text context menu components.
91      *
92      * @param key A unique key that identifies this item. Used to identify context menu items in the
93      *   context menu. It is advisable to use a `data object` as a key here.
94      * @param label string to display as the text of the item.
95      * @param leadingIcon Icon that precedes the label in the context menu. Setting this to null
96      *   means that it will not be displayed.
97      * @param onClick Action to perform upon the item being clicked/pressed.
98      */
99     fun item(
100         key: Any,
101         label: String,
102         leadingIcon: PlatformIcon? = null,
103         onClick: TextContextMenuSession.() -> Unit,
104     ) {
105         components += TextContextMenuItem(key, label, leadingIcon, onClick)
106     }
107 
108     /**
109      * Adds a separator to the list of text context menu components. Successive separators will be
110      * combined into a single separator.
111      */
112     fun separator() {
113         components += TextContextMenuSeparator
114     }
115 }
116 
117 private val TextContextMenuComponent?.isSeparator: Boolean
118     get() = this === TextContextMenuSeparator
119