1 /*
2  * Copyright 2020 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.interaction
18 
19 import androidx.compose.runtime.Stable
20 import androidx.compose.runtime.State
21 import androidx.compose.runtime.annotation.RememberInComposition
22 import androidx.compose.runtime.remember
23 import kotlin.js.JsName
24 import kotlinx.coroutines.channels.BufferOverflow
25 import kotlinx.coroutines.flow.Flow
26 import kotlinx.coroutines.flow.MutableSharedFlow
27 
28 /**
29  * InteractionSource represents a stream of [Interaction]s corresponding to events emitted by a
30  * component. These [Interaction]s can be used to change how components appear in different states,
31  * such as when a component is pressed or dragged.
32  *
33  * A common use case is [androidx.compose.foundation.indication], where
34  * [androidx.compose.foundation.Indication] implementations can subscribe to an [InteractionSource]
35  * to draw indication for different [Interaction]s, such as a ripple effect for
36  * [PressInteraction.Press] and a state overlay for [DragInteraction.Start].
37  *
38  * For simple cases where you are interested in the binary state of an [Interaction], such as
39  * whether a component is pressed or not, you can use [InteractionSource.collectIsPressedAsState]
40  * and other extension functions that subscribe and return a [Boolean] [State] representing whether
41  * the component is in this state or not.
42  *
43  * @sample androidx.compose.foundation.samples.SimpleInteractionSourceSample
44  *
45  * For more complex cases, such as when building an [androidx.compose.foundation.Indication], the
46  * order of the events can change how a component / indication should be drawn. For example, if a
47  * component is being dragged and then becomes focused, the most recent [Interaction] is
48  * [FocusInteraction.Focus], so the component should appear in a focused state to signal this event
49  * to the user.
50  *
51  * InteractionSource exposes [interactions] to support these use cases - a [Flow] representing the
52  * stream of all emitted [Interaction]s. This also provides more information, such as the press
53  * position of [PressInteraction.Press], so you can show an effect at the specific point the
54  * component was pressed, and whether the press was [PressInteraction.Release] or
55  * [PressInteraction.Cancel], for cases when a component should behave differently if the press was
56  * released normally or interrupted by another gesture.
57  *
58  * You can collect from [interactions] as you would with any other [Flow]:
59  *
60  * @sample androidx.compose.foundation.samples.InteractionSourceFlowSample
61  *
62  * To emit [Interaction]s so that consumers can react to them, see [MutableInteractionSource].
63  *
64  * @see MutableInteractionSource
65  * @see Interaction
66  */
67 @Stable
68 interface InteractionSource {
69     /**
70      * [Flow] representing the stream of all [Interaction]s emitted through this
71      * [InteractionSource]. This can be used to see [Interaction]s emitted in order, and with
72      * additional metadata, such as the press position for [PressInteraction.Press].
73      *
74      * @sample androidx.compose.foundation.samples.InteractionSourceFlowSample
75      */
76     val interactions: Flow<Interaction>
77 }
78 
79 /**
80  * MutableInteractionSource represents a stream of [Interaction]s corresponding to events emitted by
81  * a component. These [Interaction]s can be used to change how components appear in different
82  * states, such as when a component is pressed or dragged.
83  *
84  * Lower level interaction APIs such as [androidx.compose.foundation.clickable] and
85  * [androidx.compose.foundation.gestures.draggable] have an [MutableInteractionSource] parameter,
86  * which allows you to hoist an [MutableInteractionSource] and combine multiple interactions into
87  * one event stream.
88  *
89  * MutableInteractionSource exposes [emit] and [tryEmit] functions. These emit the provided
90  * [Interaction] to the underlying [interactions] [Flow], allowing consumers to react to these new
91  * [Interaction]s.
92  *
93  * An instance of MutableInteractionSource can be created by using the [MutableInteractionSource]
94  * factory function. This instance should be [remember]ed before it is passed to other components
95  * that consume it.
96  *
97  * @see InteractionSource
98  * @see Interaction
99  */
100 @Stable
101 interface MutableInteractionSource : InteractionSource {
102     /**
103      * Emits [interaction] into [interactions]. This method is not thread-safe and should not be
104      * invoked concurrently.
105      *
106      * @see tryEmit
107      */
emitnull108     suspend fun emit(interaction: Interaction)
109 
110     /**
111      * Tries to emit [interaction] into [interactions] without suspending. It returns `true` if the
112      * value was emitted successfully.
113      *
114      * @see emit
115      */
116     fun tryEmit(interaction: Interaction): Boolean
117 }
118 
119 /**
120  * Return a new [MutableInteractionSource] that can be hoisted and provided to components, allowing
121  * listening to [Interaction] changes inside those components.
122  *
123  * This should be [remember]ed before it is provided to components, so it can maintain its state
124  * across compositions.
125  *
126  * @see InteractionSource
127  * @see MutableInteractionSource
128  */
129 @JsName("funMutableInteractionSource")
130 @RememberInComposition
131 fun MutableInteractionSource(): MutableInteractionSource = MutableInteractionSourceImpl()
132 
133 @Stable
134 private class MutableInteractionSourceImpl : MutableInteractionSource {
135     // TODO: consider replay for new indication instances during events?
136     override val interactions =
137         MutableSharedFlow<Interaction>(
138             extraBufferCapacity = 16,
139             onBufferOverflow = BufferOverflow.DROP_OLDEST,
140         )
141 
142     override suspend fun emit(interaction: Interaction) {
143         interactions.emit(interaction)
144     }
145 
146     override fun tryEmit(interaction: Interaction): Boolean {
147         return interactions.tryEmit(interaction)
148     }
149 }
150