1 /*
<lambda>null2  * Copyright 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 @file:Suppress("DEPRECATION")
18 
19 package androidx.compose.foundation.layout
20 
21 import androidx.collection.IntIntPair
22 import androidx.compose.foundation.layout.ContextualFlowRowOverflow.Companion.Clip
23 import androidx.compose.foundation.layout.ContextualFlowRowOverflow.Companion.Visible
24 import androidx.compose.foundation.layout.FlowColumnOverflow.Companion.Clip
25 import androidx.compose.foundation.layout.FlowColumnOverflow.Companion.Visible
26 import androidx.compose.foundation.layout.FlowColumnOverflow.Companion.expandIndicator
27 import androidx.compose.foundation.layout.FlowColumnOverflow.Companion.expandOrCollapseIndicator
28 import androidx.compose.foundation.layout.FlowRowOverflow.Companion.Clip
29 import androidx.compose.foundation.layout.FlowRowOverflow.Companion.expandIndicator
30 import androidx.compose.foundation.layout.FlowRowOverflow.Companion.expandOrCollapseIndicator
31 import androidx.compose.runtime.Composable
32 import androidx.compose.runtime.remember
33 import androidx.compose.ui.layout.IntrinsicMeasurable
34 import androidx.compose.ui.layout.Measurable
35 import androidx.compose.ui.layout.Placeable
36 import androidx.compose.ui.platform.LocalDensity
37 import androidx.compose.ui.unit.Constraints
38 import androidx.compose.ui.unit.Dp
39 import androidx.compose.ui.unit.dp
40 
41 /**
42  * Overflow Handling Options
43  *
44  * This enumeration defines the available options for handling content that exceeds the boundaries
45  * of its container for [FlowRow].
46  *
47  * Options:
48  * - [Visible]: The overflowing content remains visible outside its container. This can lead to
49  *   overlapping with other elements. Use this option when it's crucial to display all content,
50  *   regardless of the container's size.
51  * - [Clip]: The overflowing content is clipped and not visible beyond the boundary of its
52  *   container. Ideal for maintaining a clean and uncluttered UI, where overlapping elements are
53  *   undesirable.
54  * - [expandIndicator]: Behaves similar to [Clip], however shows an indicator or button indicating
55  *   that more items can be loaded.
56  * - [expandOrCollapseIndicator]: Extends the [expandIndicator] functionality by adding a 'Collapse'
57  *   option. After expanding the content, users can choose to collapse it back to the summary view.
58  */
59 @Deprecated("FlowLayout overflow is no longer maintained")
60 @ExperimentalLayoutApi
61 class FlowRowOverflow
62 private constructor(
63     type: OverflowType,
64     minLinesToShowCollapse: Int = 0,
65     minCrossAxisSizeToShowCollapse: Int = 0,
66     seeMoreGetter: ((state: FlowLayoutOverflowState) -> @Composable () -> Unit)? = null,
67     collapseGetter: ((state: FlowLayoutOverflowState) -> @Composable () -> Unit)? = null
68 ) :
69     FlowLayoutOverflow(
70         type,
71         minLinesToShowCollapse,
72         minCrossAxisSizeToShowCollapse,
73         seeMoreGetter,
74         collapseGetter
75     ) {
76 
77     companion object {
78         /** Display all content, even if there is not enough space in the specified bounds. */
79         @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
80         @ExperimentalLayoutApi
81         @get:ExperimentalLayoutApi
82         val Visible = FlowRowOverflow(OverflowType.Visible)
83 
84         /** Clip the overflowing content to fix its container. */
85         @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
86         @ExperimentalLayoutApi
87         @get:ExperimentalLayoutApi
88         val Clip = FlowRowOverflow(OverflowType.Clip)
89 
90         /**
91          * Registers an "expand indicator" composable for handling overflow in a [FlowRow].
92          *
93          * This function allows the creation of a custom composable that can be displayed when there
94          * are more items in the [FlowRow] than can be displayed in the available space. The
95          * "expandable indicator" composable can be tailored to show a summary, a button, or any
96          * other composable element that indicates the presence of additional items.
97          *
98          * @sample androidx.compose.foundation.layout.samples.SimpleFlowRowMaxLinesWithSeeMore
99          * @param content composable that visually indicates more items can be loaded.
100          */
101         @ExperimentalLayoutApi
102         fun expandIndicator(content: @Composable FlowRowOverflowScope.() -> Unit): FlowRowOverflow {
103             val seeMoreGetter = { state: FlowLayoutOverflowState ->
104                 @Composable {
105                     val scope = FlowRowOverflowScopeImpl(state)
106                     scope.content()
107                 }
108             }
109             return FlowRowOverflow(OverflowType.ExpandIndicator, seeMoreGetter = seeMoreGetter)
110         }
111 
112         /**
113          * Registers an "expand or collapse indicator" composable for handling overflow in a
114          * [FlowRow].
115          *
116          * This function is designed to switch between two states: a "Expandable" state when there
117          * are more items to show, and a "Collapsable" state when all items are being displayed and
118          * can be collapsed.
119          *
120          * Prior to layout, the function evaluates both composables indicators to determine their
121          * maximum intrinsic size. Depending on the space available and the number of items, either
122          * the [expandIndicator] or the [collapseIndicator] is rendered.
123          *
124          * @sample androidx.compose.foundation.layout.samples.SimpleFlowRowMaxLinesDynamicSeeMore
125          * @param minRowsToShowCollapse Specifies the minimum number of rows that should be visible
126          *   before showing the collapse option. This parameter is useful when the number of rows is
127          *   too small to be reduced further.
128          * @param minHeightToShowCollapse Specifies the minimum height at which the collapse option
129          *   should be displayed. This parameter is useful for preventing the collapse option from
130          *   appearing when the height is too narrow.
131          * @param expandIndicator composable that visually indicates more items can be loaded.
132          * @param collapseIndicator composable that visually indicates less items can be loaded.
133          */
134         @ExperimentalLayoutApi
135         @Composable
136         fun expandOrCollapseIndicator(
137             expandIndicator: @Composable FlowRowOverflowScope.() -> Unit,
138             collapseIndicator: @Composable FlowRowOverflowScope.() -> Unit,
139             minRowsToShowCollapse: Int = 1,
140             minHeightToShowCollapse: Dp = 0.dp,
141         ): FlowRowOverflow {
142             val minHeightToShowCollapsePx =
143                 with(LocalDensity.current) { minHeightToShowCollapse.roundToPx() }
144             return remember(
145                 minRowsToShowCollapse,
146                 minHeightToShowCollapsePx,
147                 expandIndicator,
148                 collapseIndicator
149             ) {
150                 val seeMoreGetter = { state: FlowLayoutOverflowState ->
151                     @Composable {
152                         val scope = FlowRowOverflowScopeImpl(state)
153                         scope.expandIndicator()
154                     }
155                 }
156 
157                 val collapseGetter = { state: FlowLayoutOverflowState ->
158                     @Composable {
159                         val scope = FlowRowOverflowScopeImpl(state)
160                         scope.collapseIndicator()
161                     }
162                 }
163 
164                 FlowRowOverflow(
165                     OverflowType.ExpandOrCollapseIndicator,
166                     minLinesToShowCollapse = minRowsToShowCollapse,
167                     minCrossAxisSizeToShowCollapse = minHeightToShowCollapsePx,
168                     seeMoreGetter = seeMoreGetter,
169                     collapseGetter = collapseGetter
170                 )
171             }
172         }
173     }
174 }
175 
176 /**
177  * Overflow Handling Options
178  *
179  * This enumeration defines the available options for handling content that exceeds the boundaries
180  * of its container for [FlowColumn] and [ContextualFlowColumn].
181  *
182  * Options:
183  * - [Visible]: The overflowing content remains visible outside its container. This can lead to
184  *   overlapping with other elements. Use this option when it's crucial to display all content,
185  *   regardless of the container's size.
186  * - [Clip]: The overflowing content is clipped and not visible beyond the boundary of its
187  *   container. Ideal for maintaining a clean and uncluttered UI, where overlapping elements are
188  *   undesirable.
189  * - [expandIndicator]: Behaves similar to [Clip], however shows an indicator or button indicating
190  *   that more items can be loaded.
191  * - [expandOrCollapseIndicator]: Extends the [expandIndicator] functionality by adding a 'Collapse'
192  *   option. After expanding the content, users can choose to collapse it back to the summary view.
193  */
194 @Deprecated("FlowLayout overflow is no longer maintained")
195 @ExperimentalLayoutApi
196 class FlowColumnOverflow
197 private constructor(
198     type: OverflowType,
199     minLinesToShowCollapse: Int = 0,
200     minCrossAxisSizeToShowCollapse: Int = 0,
201     seeMoreGetter: ((state: FlowLayoutOverflowState) -> @Composable () -> Unit)? = null,
202     collapseGetter: ((state: FlowLayoutOverflowState) -> @Composable () -> Unit)? = null
203 ) :
204     FlowLayoutOverflow(
205         type,
206         minLinesToShowCollapse,
207         minCrossAxisSizeToShowCollapse,
208         seeMoreGetter,
209         collapseGetter
210     ) {
211     @Deprecated("FlowLayout overflow is no longer maintained")
212     @ExperimentalLayoutApi
213     companion object {
214         /** Display all content, even if there is not enough space in the specified bounds. */
215         @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
216         @ExperimentalLayoutApi
217         @get:ExperimentalLayoutApi
218         val Visible = FlowColumnOverflow(FlowLayoutOverflow.OverflowType.Visible)
219 
220         /** Clip the overflowing content to fix its container. */
221         @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
222         @ExperimentalLayoutApi
223         @get:ExperimentalLayoutApi
224         val Clip = FlowColumnOverflow(FlowLayoutOverflow.OverflowType.Clip)
225 
226         /**
227          * Registers an "expand indicator" composable for handling overflow in a [FlowColumn].
228          *
229          * This function allows the creation of a custom composable that can be displayed when there
230          * are more items in the [FlowColumn] than can be displayed in the available space. The
231          * "expandable indicator" composable can be tailored to show a summary, a button, or any
232          * other composable element that indicates the presence of additional items.
233          *
234          * @sample androidx.compose.foundation.layout.samples.SimpleFlowColumnMaxLinesWithSeeMore
235          * @param content composable that visually indicates more items can be loaded.
236          */
237         @ExperimentalLayoutApi
expandIndicatornull238         fun expandIndicator(
239             content: @Composable FlowColumnOverflowScope.() -> Unit
240         ): FlowColumnOverflow {
241             val seeMoreGetter = { state: FlowLayoutOverflowState ->
242                 @Composable {
243                     val scope = FlowColumnOverflowScopeImpl(state)
244                     scope.content()
245                 }
246             }
247             return FlowColumnOverflow(OverflowType.ExpandIndicator, seeMoreGetter = seeMoreGetter)
248         }
249 
250         /**
251          * Registers an "expand or collapse indicator" composable for handling overflow in a
252          * [FlowColumn].
253          *
254          * This function is designed to switch between two states: a "Expandable" state when there
255          * are more items to show, and a "Collapsable" state when all items are being displayed and
256          * can be collapsed.
257          *
258          * Prior to layout, the function evaluates both composables indicators to determine their
259          * maximum intrinsic size. Depending on the space available and the number of items, either
260          * the [expandIndicator] or the [collapseIndicator] is rendered.
261          *
262          * @sample androidx.compose.foundation.layout.samples.SimpleFlowColumnMaxLinesDynamicSeeMore
263          * @param minColumnsToShowCollapse Specifies the minimum number of columns that should be
264          *   visible before showing the collapse option. This parameter is useful when the number of
265          *   columns is too small to be reduced further.
266          * @param minWidthToShowCollapse Specifies the minimum width at which the collapse option
267          *   should be displayed. This parameter is useful for preventing the collapse option from
268          *   appearing when the width is too narrow.
269          * @param expandIndicator composable that visually indicates more items can be loaded.
270          * @param collapseIndicator composable that visually indicates less items can be loaded.
271          */
272         @ExperimentalLayoutApi
273         @Composable
expandOrCollapseIndicatornull274         fun expandOrCollapseIndicator(
275             expandIndicator: @Composable FlowColumnOverflowScope.() -> Unit,
276             collapseIndicator: @Composable FlowColumnOverflowScope.() -> Unit,
277             minColumnsToShowCollapse: Int = 1,
278             minWidthToShowCollapse: Dp = 0.dp,
279         ): FlowColumnOverflow {
280             val minWidthToShowCollapsePx =
281                 with(LocalDensity.current) { minWidthToShowCollapse.roundToPx() }
282             return remember(
283                 minColumnsToShowCollapse,
284                 minWidthToShowCollapsePx,
285                 expandIndicator,
286                 collapseIndicator
287             ) {
288                 val seeMoreGetter = { state: FlowLayoutOverflowState ->
289                     @Composable {
290                         val scope = FlowColumnOverflowScopeImpl(state)
291                         scope.expandIndicator()
292                     }
293                 }
294 
295                 val collapseGetter = { state: FlowLayoutOverflowState ->
296                     @Composable {
297                         val scope = FlowColumnOverflowScopeImpl(state)
298                         scope.collapseIndicator()
299                     }
300                 }
301 
302                 FlowColumnOverflow(
303                     OverflowType.ExpandOrCollapseIndicator,
304                     minLinesToShowCollapse = minColumnsToShowCollapse,
305                     minCrossAxisSizeToShowCollapse = minWidthToShowCollapsePx,
306                     seeMoreGetter = seeMoreGetter,
307                     collapseGetter = collapseGetter
308                 )
309             }
310         }
311     }
312 }
313 
314 /**
315  * Overflow Handling Options
316  *
317  * This enumeration defines the available options for handling content that exceeds the boundaries
318  * of its container for [ContextualFlowRow].
319  *
320  * Options:
321  * - [Visible]: The overflowing content remains visible outside its container. This can lead to
322  *   overlapping with other elements. Use this option when it's crucial to display all content,
323  *   regardless of the container's size.
324  * - [Clip]: The overflowing content is clipped and not visible beyond the boundary of its
325  *   container. Ideal for maintaining a clean and uncluttered UI, where overlapping elements are
326  *   undesirable.
327  * - [expandIndicator]: Behaves similar to [Clip], however shows an indicator or button indicating
328  *   that more items can be loaded.
329  * - [expandOrCollapseIndicator]: Extends the [expandIndicator] functionality by adding a 'Collapse'
330  *   option. After expanding the content, users can choose to collapse it back to the summary view.
331  */
332 @Deprecated("ContextualFlowLayouts are no longer maintained")
333 @ExperimentalLayoutApi
334 class ContextualFlowRowOverflow
335 private constructor(
336     type: OverflowType,
337     minLinesToShowCollapse: Int = 0,
338     minCrossAxisSizeToShowCollapse: Int = 0,
339     seeMoreGetter: ((state: FlowLayoutOverflowState) -> @Composable () -> Unit)? = null,
340     collapseGetter: ((state: FlowLayoutOverflowState) -> @Composable () -> Unit)? = null
341 ) :
342     FlowLayoutOverflow(
343         type,
344         minLinesToShowCollapse,
345         minCrossAxisSizeToShowCollapse,
346         seeMoreGetter,
347         collapseGetter
348     ) {
349 
350     @Deprecated("FlowLayout overflow is no longer maintained")
351     @ExperimentalLayoutApi
352     companion object {
353         /** Display all content, even if there is not enough space in the specified bounds. */
354         @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
355         @ExperimentalLayoutApi
356         @get:ExperimentalLayoutApi
357         val Visible = ContextualFlowRowOverflow(FlowLayoutOverflow.OverflowType.Visible)
358 
359         /** Clip the overflowing content to fix its container. */
360         @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
361         @ExperimentalLayoutApi
362         @get:ExperimentalLayoutApi
363         val Clip = ContextualFlowRowOverflow(FlowLayoutOverflow.OverflowType.Clip)
364 
365         /**
366          * Registers an "expand indicator" composable for handling overflow in a
367          * [ContextualFlowRow].
368          *
369          * This function allows the creation of a custom composable that can be displayed when there
370          * are more items in the [ContextualFlowRow] than can be displayed in the available space.
371          * The "expandable indicator" composable can be tailored to show a summary, a button, or any
372          * other composable element that indicates the presence of additional items.
373          *
374          * @param content composable that visually indicates more items can be loaded.
375          */
376         @ExperimentalLayoutApi
expandIndicatornull377         fun expandIndicator(
378             content: @Composable ContextualFlowRowOverflowScope.() -> Unit
379         ): ContextualFlowRowOverflow {
380             val seeMoreGetter = { state: FlowLayoutOverflowState ->
381                 @Composable {
382                     val scope = ContextualFlowRowOverflowScopeImpl(state)
383                     scope.content()
384                 }
385             }
386             return ContextualFlowRowOverflow(
387                 OverflowType.ExpandIndicator,
388                 seeMoreGetter = seeMoreGetter
389             )
390         }
391 
392         /**
393          * Registers an "expand or collapse indicator" composable for handling overflow in a
394          * [ContextualFlowRow].
395          *
396          * This function is designed to switch between two states: a "Expandable" state when there
397          * are more items to show, and a "Collapsable" state when all items are being displayed and
398          * can be collapsed.
399          *
400          * Prior to layout, the function evaluates both composables indicators to determine their
401          * maximum intrinsic size. Depending on the space available and the number of items, either
402          * the [expandIndicator] or the [collapseIndicator] is rendered.
403          *
404          * @sample androidx.compose.foundation.layout.samples.ContextualFlowRowMaxLineDynamicSeeMore
405          * @param minRowsToShowCollapse Specifies the minimum number of rows that should be visible
406          *   before showing the collapse option. This parameter is useful when the number of rows is
407          *   too small to be reduced further.
408          * @param minHeightToShowCollapse Specifies the minimum height at which the collapse option
409          *   should be displayed. This parameter is useful for preventing the collapse option from
410          *   appearing when the height is too narrow.
411          * @param expandIndicator composable that visually indicates more items can be loaded.
412          * @param collapseIndicator composable that visually indicates less items can be loaded.
413          */
414         @ExperimentalLayoutApi
415         @Composable
expandOrCollapseIndicatornull416         fun expandOrCollapseIndicator(
417             expandIndicator: @Composable ContextualFlowRowOverflowScope.() -> Unit,
418             collapseIndicator: @Composable ContextualFlowRowOverflowScope.() -> Unit,
419             minRowsToShowCollapse: Int = 1,
420             minHeightToShowCollapse: Dp = 0.dp,
421         ): ContextualFlowRowOverflow {
422             val minHeightToShowCollapsePx =
423                 with(LocalDensity.current) { minHeightToShowCollapse.roundToPx() }
424             return remember(
425                 minRowsToShowCollapse,
426                 minHeightToShowCollapsePx,
427                 expandIndicator,
428                 collapseIndicator
429             ) {
430                 val seeMoreGetter = { state: FlowLayoutOverflowState ->
431                     @Composable {
432                         val scope = ContextualFlowRowOverflowScopeImpl(state)
433                         scope.expandIndicator()
434                     }
435                 }
436 
437                 val collapseGetter = { state: FlowLayoutOverflowState ->
438                     @Composable {
439                         val scope = ContextualFlowRowOverflowScopeImpl(state)
440                         scope.collapseIndicator()
441                     }
442                 }
443 
444                 ContextualFlowRowOverflow(
445                     OverflowType.ExpandOrCollapseIndicator,
446                     minLinesToShowCollapse = minRowsToShowCollapse,
447                     minCrossAxisSizeToShowCollapse = minHeightToShowCollapsePx,
448                     seeMoreGetter = seeMoreGetter,
449                     collapseGetter = collapseGetter
450                 )
451             }
452         }
453     }
454 }
455 
456 /**
457  * Overflow Handling Options
458  *
459  * This enumeration defines the available options for handling content that exceeds the boundaries
460  * of its container for [ContextualFlowColumn].
461  *
462  * Options:
463  * - [Visible]: The overflowing content remains visible outside its container. This can lead to
464  *   overlapping with other elements. Use this option when it's crucial to display all content,
465  *   regardless of the container's size.
466  * - [Clip]: The overflowing content is clipped and not visible beyond the boundary of its
467  *   container. Ideal for maintaining a clean and uncluttered UI, where overlapping elements are
468  *   undesirable.
469  * - [expandIndicator]: Behaves similar to [Clip], however shows an indicator or button indicating
470  *   that more items can be loaded.
471  * - [expandOrCollapseIndicator]: Extends the [expandIndicator] functionality by adding a 'Collapse'
472  *   option. After expanding the content, users can choose to collapse it back to the summary view.
473  */
474 @Deprecated("ContextualFlowLayouts are no longer maintained")
475 @ExperimentalLayoutApi
476 class ContextualFlowColumnOverflow
477 private constructor(
478     type: OverflowType,
479     minLinesToShowCollapse: Int = 0,
480     minCrossAxisSizeToShowCollapse: Int = 0,
481     seeMoreGetter: ((state: FlowLayoutOverflowState) -> @Composable () -> Unit)? = null,
482     collapseGetter: ((state: FlowLayoutOverflowState) -> @Composable () -> Unit)? = null
483 ) :
484     FlowLayoutOverflow(
485         type,
486         minLinesToShowCollapse,
487         minCrossAxisSizeToShowCollapse,
488         seeMoreGetter,
489         collapseGetter
490     ) {
491 
492     @Deprecated("ContextualFlowLayouts are no longer maintained")
493     @ExperimentalLayoutApi
494     companion object {
495         /** Display all content, even if there is not enough space in the specified bounds. */
496         @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
497         @ExperimentalLayoutApi
498         @get:ExperimentalLayoutApi
499         val Visible = ContextualFlowColumnOverflow(FlowLayoutOverflow.OverflowType.Visible)
500 
501         /** Clip the overflowing content to fix its container. */
502         @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
503         @ExperimentalLayoutApi
504         @get:ExperimentalLayoutApi
505         val Clip = ContextualFlowColumnOverflow(FlowLayoutOverflow.OverflowType.Clip)
506 
507         /**
508          * Registers an "expand indicator" composable for handling overflow in a
509          * [ContextualFlowColumn].
510          *
511          * This function allows the creation of a custom composable that can be displayed when there
512          * are more items in the [ContextualFlowColumn] than can be displayed in the available
513          * space. The "expandable indicator" composable can be tailored to show a summary, a button,
514          * or any other composable element that indicates the presence of additional items.
515          *
516          * @param content composable that visually indicates more items can be loaded.
517          */
518         @ExperimentalLayoutApi
expandIndicatornull519         fun expandIndicator(
520             content: @Composable ContextualFlowColumnOverflowScope.() -> Unit
521         ): ContextualFlowColumnOverflow {
522             val seeMoreGetter = { state: FlowLayoutOverflowState ->
523                 @Composable {
524                     val scope = ContextualFlowColumnOverflowScopeImpl(state)
525                     scope.content()
526                 }
527             }
528             return ContextualFlowColumnOverflow(
529                 OverflowType.ExpandIndicator,
530                 seeMoreGetter = seeMoreGetter
531             )
532         }
533 
534         /**
535          * Registers an "expand or collapse indicator" composable for handling overflow in a
536          * [ContextualFlowColumn].
537          *
538          * This function is designed to switch between two states: a "Expandable" state when there
539          * are more items to show, and a "Collapsable" state when all items are being displayed and
540          * can be collapsed.
541          *
542          * Prior to layout, the function evaluates both composables indicators to determine their
543          * maximum intrinsic size. Depending on the space available and the number of items, either
544          * the [expandIndicator] or the [collapseIndicator] is rendered.
545          *
546          * @sample androidx.compose.foundation.layout.samples.ContextualFlowColMaxLineDynamicSeeMore
547          * @param minColumnsToShowCollapse Specifies the minimum number of columns that should be
548          *   visible before showing the collapse option. This parameter is useful when the number of
549          *   columns is too small to be reduced further.
550          * @param minWidthToShowCollapse Specifies the minimum width at which the collapse option
551          *   should be displayed. This parameter is useful for preventing the collapse option from
552          *   appearing when the width is too narrow.
553          * @param expandIndicator composable that visually indicates more items can be loaded.
554          * @param collapseIndicator composable that visually indicates less items can be loaded.
555          */
556         @ExperimentalLayoutApi
557         @Composable
expandOrCollapseIndicatornull558         fun expandOrCollapseIndicator(
559             expandIndicator: @Composable ContextualFlowColumnOverflowScope.() -> Unit,
560             collapseIndicator: @Composable ContextualFlowColumnOverflowScope.() -> Unit,
561             minColumnsToShowCollapse: Int = 1,
562             minWidthToShowCollapse: Dp = 0.dp,
563         ): ContextualFlowColumnOverflow {
564             val minWidthToShowCollapsePx =
565                 with(LocalDensity.current) { minWidthToShowCollapse.roundToPx() }
566             return remember(
567                 minColumnsToShowCollapse,
568                 minWidthToShowCollapsePx,
569                 expandIndicator,
570                 collapseIndicator
571             ) {
572                 val seeMoreGetter = { state: FlowLayoutOverflowState ->
573                     @Composable {
574                         val scope = ContextualFlowColumnOverflowScopeImpl(state)
575                         scope.expandIndicator()
576                     }
577                 }
578 
579                 val collapseGetter = { state: FlowLayoutOverflowState ->
580                     @Composable {
581                         val scope = ContextualFlowColumnOverflowScopeImpl(state)
582                         scope.collapseIndicator()
583                     }
584                 }
585 
586                 ContextualFlowColumnOverflow(
587                     OverflowType.ExpandOrCollapseIndicator,
588                     minLinesToShowCollapse = minColumnsToShowCollapse,
589                     minCrossAxisSizeToShowCollapse = minWidthToShowCollapsePx,
590                     seeMoreGetter = seeMoreGetter,
591                     collapseGetter = collapseGetter
592                 )
593             }
594         }
595     }
596 }
597 
598 /**
599  * Overflow Handling Options
600  *
601  * This enumeration defines the available options for handling content that exceeds the boundaries
602  * of its container.
603  *
604  * Please check out the children classes on ways to initialize a FlowLayout overflow
605  *
606  * @see [FlowRowOverflow]
607  * @see [FlowColumnOverflow]
608  * @see [ContextualFlowRowOverflow]
609  * @see [ContextualFlowColumnOverflow]
610  */
611 @Deprecated("FlowLayout overflow is no longer maintained")
612 @ExperimentalLayoutApi
613 sealed class FlowLayoutOverflow(
614     internal val type: OverflowType,
615     private val minLinesToShowCollapse: Int = 0,
616     private val minCrossAxisSizeToShowCollapse: Int = 0,
617     private val seeMoreGetter: ((state: FlowLayoutOverflowState) -> @Composable () -> Unit)? = null,
618     private val collapseGetter: ((state: FlowLayoutOverflowState) -> @Composable () -> Unit)? = null
619 ) {
createOverflowStatenull620     internal fun createOverflowState() =
621         FlowLayoutOverflowState(type, minLinesToShowCollapse, minCrossAxisSizeToShowCollapse)
622 
623     internal fun addOverflowComposables(
624         state: FlowLayoutOverflowState,
625         list: MutableList<@Composable () -> Unit>
626     ) {
627         val expandIndicator = seeMoreGetter?.let { getter -> getter(state) }
628         val collapseIndicator = collapseGetter?.let { getter -> getter(state) }
629 
630         when (type) {
631             OverflowType.ExpandIndicator -> expandIndicator?.let { list.add(expandIndicator) }
632             OverflowType.ExpandOrCollapseIndicator -> {
633                 expandIndicator?.let { list.add(expandIndicator) }
634                 collapseIndicator?.let { list.add(collapseIndicator) }
635             }
636             else -> {}
637         }
638     }
639 
640     internal enum class OverflowType {
641         Visible,
642         Clip,
643         ExpandIndicator,
644         ExpandOrCollapseIndicator,
645     }
646 }
647 
lazyIntnull648 internal fun lazyInt(
649     errorMessage: String = "Lazy item is not yet initialized",
650     initializer: () -> Int
651 ): Lazy<Int> = LazyImpl(initializer, errorMessage)
652 
653 private class LazyImpl(val initializer: () -> Int, val errorMessage: String) : Lazy<Int> {
654     private var _value: Int = UNINITIALIZED_VALUE
655     override val value: Int
656         get() {
657             if (_value == UNINITIALIZED_VALUE) {
658                 _value = initializer()
659             }
660             if (_value == UNINITIALIZED_VALUE) {
661                 throw IllegalStateException(errorMessage)
662             }
663             return _value
664         }
665 
666     override fun isInitialized(): Boolean = _value != UNINITIALIZED_VALUE
667 
668     override fun toString(): String = if (isInitialized()) value.toString() else errorMessage
669 
670     companion object {
671         internal const val UNINITIALIZED_VALUE: Int = -1
672     }
673 }
674 
675 /** Overflow State for managing overflow state within FlowLayouts. */
676 @OptIn(ExperimentalLayoutApi::class)
677 @Suppress("DATA_CLASS_COPY_VISIBILITY_WILL_BE_CHANGED_WARNING")
678 internal data class FlowLayoutOverflowState
679 internal constructor(
680     internal val type: FlowLayoutOverflow.OverflowType,
681     internal val minLinesToShowCollapse: Int,
682     internal val minCrossAxisSizeToShowCollapse: Int
683 ) {
684     internal val shownItemCount: Int
685         get() {
686             if (itemShown == -1) {
687                 throw IllegalStateException(shownItemLazyErrorMessage)
688             }
689             return itemShown
690         }
691 
692     internal val shownItemLazyErrorMessage =
693         "Accessing shownItemCount before it is set. " +
694             "Are you calling this in the Composition phase, " +
695             "rather than in the draw phase? " +
696             "Consider our samples on how to use it during the draw phase " +
697             "or consider using ContextualFlowRow/ContextualFlowColumn " +
698             "which initializes this method in the composition phase."
699 
700     internal var itemShown: Int = -1
701     internal var itemCount = 0
702     private var seeMoreMeasurable: Measurable? = null
703     private var seeMorePlaceable: Placeable? = null
704     private var collapseMeasurable: Measurable? = null
705     private var collapsePlaceable: Placeable? = null
706     private var seeMoreSize: IntIntPair? = null
707     private var collapseSize: IntIntPair? = null
708     // for contextual flow row
709     private var getOverflowMeasurable:
710         ((isExpandable: Boolean, noOfItemsShown: Int) -> Measurable?)? =
711         null
712 
ellipsisSizenull713     internal fun ellipsisSize(
714         hasNext: Boolean,
715         lineIndex: Int,
716         totalCrossAxisSize: Int
717     ): IntIntPair? {
718         return when (type) {
719             FlowLayoutOverflow.OverflowType.Visible -> null
720             FlowLayoutOverflow.OverflowType.Clip -> null
721             FlowLayoutOverflow.OverflowType.ExpandIndicator ->
722                 if (hasNext) {
723                     seeMoreSize
724                 } else {
725                     null
726                 }
727             FlowLayoutOverflow.OverflowType.ExpandOrCollapseIndicator -> {
728                 if (hasNext) {
729                     seeMoreSize
730                 } else if (
731                     lineIndex + 1 >= minLinesToShowCollapse &&
732                         totalCrossAxisSize >= minCrossAxisSizeToShowCollapse
733                 ) {
734                     collapseSize
735                 } else {
736                     null
737                 }
738             }
739         }
740     }
741 
ellipsisInfonull742     internal fun ellipsisInfo(
743         hasNext: Boolean,
744         lineIndex: Int,
745         totalCrossAxisSize: Int
746     ): FlowLayoutBuildingBlocks.WrapEllipsisInfo? {
747         return when (type) {
748             FlowLayoutOverflow.OverflowType.Visible -> null
749             FlowLayoutOverflow.OverflowType.Clip -> null
750             FlowLayoutOverflow.OverflowType.ExpandIndicator,
751             FlowLayoutOverflow.OverflowType.ExpandOrCollapseIndicator -> {
752                 var measurable: Measurable? = null
753                 var placeable: Placeable? = null
754                 var ellipsisSize: IntIntPair?
755                 if (hasNext) {
756                     measurable =
757                         getOverflowMeasurable?.invoke(/* isExpandable */ true, shownItemCount)
758                             ?: seeMoreMeasurable
759                     ellipsisSize = seeMoreSize
760                     if (getOverflowMeasurable == null) {
761                         placeable = seeMorePlaceable
762                     }
763                 } else {
764                     if (
765                         lineIndex >= (minLinesToShowCollapse - 1) &&
766                             totalCrossAxisSize >= (minCrossAxisSizeToShowCollapse)
767                     ) {
768                         measurable =
769                             getOverflowMeasurable?.invoke(/* isExpandable */ false, shownItemCount)
770                                 ?: collapseMeasurable
771                     }
772                     ellipsisSize = collapseSize
773                     if (getOverflowMeasurable == null) {
774                         placeable = collapsePlaceable
775                     }
776                 }
777                 measurable ?: return null
778                 FlowLayoutBuildingBlocks.WrapEllipsisInfo(measurable, placeable, ellipsisSize!!)
779             }
780         }
781     }
782 
setOverflowMeasurablesnull783     internal fun setOverflowMeasurables(
784         seeMoreMeasurable: IntrinsicMeasurable?,
785         collapseMeasurable: IntrinsicMeasurable?,
786         isHorizontal: Boolean,
787         constraints: Constraints,
788     ) {
789         val orientation =
790             if (isHorizontal) LayoutOrientation.Horizontal else LayoutOrientation.Vertical
791         val orientationIndependentConstraints =
792             OrientationIndependentConstraints(constraints, orientation)
793         seeMoreMeasurable?.let { item ->
794             val mainAxisSize =
795                 item.mainAxisMin(isHorizontal, orientationIndependentConstraints.crossAxisMax)
796             val crossAxisSize = item.crossAxisMin(isHorizontal, mainAxisSize)
797             this.seeMoreSize = IntIntPair(mainAxisSize, crossAxisSize)
798             this.seeMoreMeasurable = item as? Measurable
799             this.seeMorePlaceable = null
800         }
801         collapseMeasurable?.let { item ->
802             val mainAxisSize =
803                 item.mainAxisMin(isHorizontal, orientationIndependentConstraints.crossAxisMax)
804             val crossAxisSize = item.crossAxisMin(isHorizontal, mainAxisSize)
805             this.collapseSize = IntIntPair(mainAxisSize, crossAxisSize)
806             this.collapseMeasurable = item as? Measurable
807             this.collapsePlaceable = null
808         }
809     }
810 
setOverflowMeasurablesnull811     internal fun setOverflowMeasurables(
812         measurePolicy: FlowLineMeasurePolicy,
813         seeMoreMeasurable: Measurable?,
814         collapseMeasurable: Measurable?,
815         constraints: Constraints,
816     ) {
817         val isHorizontal = measurePolicy.isHorizontal
818         val orientation =
819             if (isHorizontal) LayoutOrientation.Horizontal else LayoutOrientation.Vertical
820         val orientationIndependentConstraints =
821             OrientationIndependentConstraints(constraints, orientation)
822                 .copy(mainAxisMin = 0, crossAxisMin = 0)
823         val finalConstraints = orientationIndependentConstraints.toBoxConstraints(orientation)
824         seeMoreMeasurable?.let { item ->
825             item.measureAndCache(measurePolicy, finalConstraints) { placeable ->
826                 var mainAxisSize = 0
827                 var crossAxisSize = 0
828                 placeable?.let {
829                     with(measurePolicy) {
830                         mainAxisSize = placeable.mainAxisSize()
831                         crossAxisSize = placeable.crossAxisSize()
832                     }
833                 }
834                 seeMoreSize = IntIntPair(mainAxisSize, crossAxisSize)
835                 seeMorePlaceable = placeable
836             }
837             this.seeMoreMeasurable = item
838         }
839         collapseMeasurable?.let { item ->
840             item.measureAndCache(measurePolicy, finalConstraints) { placeable ->
841                 var mainAxisSize = 0
842                 var crossAxisSize = 0
843                 placeable?.let {
844                     with(measurePolicy) {
845                         mainAxisSize = placeable.mainAxisSize()
846                         crossAxisSize = placeable.crossAxisSize()
847                     }
848                 }
849                 this.collapseSize = IntIntPair(mainAxisSize, crossAxisSize)
850                 collapsePlaceable = placeable
851             }
852             this.collapseMeasurable = item
853         }
854     }
855 
setOverflowMeasurablesnull856     internal fun setOverflowMeasurables(
857         measurePolicy: FlowLineMeasurePolicy,
858         constraints: Constraints,
859         getOverflowMeasurable: ((isExpandable: Boolean, numberOfItemsShown: Int) -> Measurable?)
860     ) {
861         this.itemShown = 0
862         this.getOverflowMeasurable = getOverflowMeasurable
863         val seeMoreMeasurable =
864             getOverflowMeasurable(/* isExpandable */ true, /* numberOfItemsShown */ 0)
865         val collapseMeasurable =
866             getOverflowMeasurable(/* isExpandable */ false, /* numberOfItemsShown */ 0)
867         setOverflowMeasurables(measurePolicy, seeMoreMeasurable, collapseMeasurable, constraints)
868     }
869 }
870