1 /*
<lambda>null2  * Copyright (C) 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 com.android.systemui.scene.domain.interactor
18 
19 import com.android.compose.animation.scene.SceneKey
20 import com.android.systemui.dagger.SysUISingleton
21 import com.android.systemui.scene.data.model.SceneStack
22 import com.android.systemui.scene.data.model.asIterable
23 import com.android.systemui.scene.data.model.peek
24 import com.android.systemui.scene.data.model.pop
25 import com.android.systemui.scene.data.model.push
26 import com.android.systemui.scene.data.model.sceneStackOf
27 import com.android.systemui.scene.shared.logger.SceneLogger
28 import com.android.systemui.scene.shared.model.SceneContainerConfig
29 import javax.inject.Inject
30 import kotlinx.coroutines.flow.Flow
31 import kotlinx.coroutines.flow.MutableStateFlow
32 import kotlinx.coroutines.flow.StateFlow
33 import kotlinx.coroutines.flow.asStateFlow
34 import kotlinx.coroutines.flow.map
35 import kotlinx.coroutines.flow.update
36 
37 @SysUISingleton
38 class SceneBackInteractor
39 @Inject
40 constructor(
41     private val logger: SceneLogger,
42     private val sceneContainerConfig: SceneContainerConfig,
43 ) {
44     private val _backStack = MutableStateFlow(sceneStackOf())
45     val backStack: StateFlow<SceneStack> = _backStack.asStateFlow()
46 
47     /**
48      * The scene to navigate to when the user triggers back navigation.
49      *
50      * This is meant for scene implementations to consult with when they implement their destination
51      * scene flow.
52      *
53      * Note that this flow could emit any scene from the [SceneContainerConfig] and that it's an
54      * illegal state to have scene implementation map to itself in its destination scene flow. Thus,
55      * scene implementations might wish to filter their own scene key out before using this.
56      */
57     val backScene: Flow<SceneKey?> = backStack.map { it.peek() }
58 
59     fun onSceneChange(from: SceneKey, to: SceneKey) {
60         check(from != to) { "from == to, from=${from.debugName}, to=${to.debugName}" }
61         when (stackOperation(from, to)) {
62             Clear -> {
63                 _backStack.value = sceneStackOf()
64             }
65             Push -> {
66                 _backStack.update { s -> s.push(from) }
67             }
68             Pop -> {
69                 _backStack.update { s ->
70                     checkNotNull(s.pop()) { "Cannot pop ${from.debugName} when stack is empty" }
71                         .also {
72                             val popped = s.peek()
73                             check(popped == to) {
74                                 "Expected to pop ${to.debugName} but instead popped ${popped?.debugName}"
75                             }
76                         }
77                 }
78             }
79         }
80         logger.logSceneBackStack(backStack.value.asIterable())
81     }
82 
83     private fun stackOperation(from: SceneKey, to: SceneKey): StackOperation {
84         val fromDistance =
85             checkNotNull(sceneContainerConfig.navigationDistances[from]) {
86                 "No distance mapping for scene \"${from.debugName}\"!"
87             }
88         val toDistance =
89             checkNotNull(sceneContainerConfig.navigationDistances[to]) {
90                 "No distance mapping for scene \"${to.debugName}\"!"
91             }
92 
93         return when {
94             toDistance == 0 -> Clear
95             toDistance > fromDistance -> Push
96             toDistance < fromDistance -> Pop
97             else ->
98                 error(
99                     "No mapping when from=${from.debugName} (distance=$fromDistance)," +
100                         " to=${to.debugName} (distance=$toDistance)!"
101                 )
102         }
103     }
104 
105     private sealed interface StackOperation
106     private data object Clear : StackOperation
107     private data object Push : StackOperation
108     private data object Pop : StackOperation
109 }
110