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