1 /*
<lambda>null2 * Copyright (C) 2025 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.kairos
18
19 import com.android.systemui.kairos.internal.IncrementalImpl
20 import com.android.systemui.kairos.internal.constInit
21 import com.android.systemui.kairos.internal.init
22 import com.android.systemui.kairos.internal.mapImpl
23 import com.android.systemui.kairos.internal.switchDeferredImplSingle
24 import com.android.systemui.kairos.internal.switchPromptImplSingle
25 import com.android.systemui.kairos.util.mapPatchFromFullDiff
26
27 /**
28 * Returns an [Events] that switches to the [Events] contained within this [State] whenever it
29 * changes.
30 *
31 * This switch does take effect until the *next* transaction after [State] changes. For a switch
32 * that takes effect immediately, see [switchEventsPromptly].
33 *
34 * @sample com.android.systemui.kairos.KairosSamples.switchEvents
35 */
36 @ExperimentalKairosApi
37 fun <A> State<Events<A>>.switchEvents(): Events<A> {
38 val patches =
39 mapImpl({ init.connect(this).changes }) { newEvents, _ -> newEvents.init.connect(this) }
40 return EventsInit(
41 constInit(
42 name = null,
43 switchDeferredImplSingle(
44 getStorage = {
45 init.connect(this).getCurrentWithEpoch(this).first.init.connect(this)
46 },
47 getPatches = { patches },
48 ),
49 )
50 )
51 }
52
53 /**
54 * Returns an [Events] that switches to the [Events] contained within this [State] whenever it
55 * changes.
56 *
57 * This switch takes effect immediately within the same transaction that [State] changes. If the
58 * newly-switched-in [Events] is emitting a value within this transaction, then that value will be
59 * emitted from this switch. If not, but the previously-switched-in [Events] *is* emitting, then
60 * that value will be emitted from this switch instead. Otherwise, there will be no emission.
61 *
62 * In general, you should prefer [switchEvents] over this method. It is both safer and more
63 * performant.
64 *
65 * @sample com.android.systemui.kairos.KairosSamples.switchEventsPromptly
66 */
67 // TODO: parameter to handle coincidental emission from both old and new
68 @ExperimentalKairosApi
switchEventsPromptlynull69 fun <A> State<Events<A>>.switchEventsPromptly(): Events<A> {
70 val patches =
71 mapImpl({ init.connect(this).changes }) { newEvents, _ -> newEvents.init.connect(this) }
72 return EventsInit(
73 constInit(
74 name = null,
75 switchPromptImplSingle(
76 getStorage = {
77 init.connect(this).getCurrentWithEpoch(this).first.init.connect(this)
78 },
79 getPatches = { patches },
80 ),
81 )
82 )
83 }
84
85 /** Returns an [Incremental] that behaves like current value of this [State]. */
switchIncrementalnull86 fun <K, V> State<Incremental<K, V>>.switchIncremental(): Incremental<K, V> {
87 val stateChangePatches =
88 transitions.mapNotNull { (old, new) ->
89 mapPatchFromFullDiff(old.sample(), new.sample()).takeIf { it.isNotEmpty() }
90 }
91 val innerChanges =
92 map { inner ->
93 merge(stateChangePatches, inner.updates) { switchPatch, upcomingPatch ->
94 switchPatch + upcomingPatch
95 }
96 }
97 .switchEventsPromptly()
98 val flattened = flatten()
99 return IncrementalInit(
100 init("switchIncremental") {
101 val upstream = flattened.init.connect(this)
102 IncrementalImpl(
103 "switchIncremental",
104 "switchIncremental",
105 upstream.changes,
106 innerChanges.init.connect(this),
107 upstream.store,
108 )
109 }
110 )
111 }
112