• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.log.table.Diffable
22 import com.android.systemui.log.table.TableLogBuffer
23 import com.android.systemui.log.table.TableRowLogger
24 import com.android.systemui.scene.data.model.SceneStack
25 import com.android.systemui.scene.data.model.asIterable
26 import com.android.systemui.scene.data.model.peek
27 import com.android.systemui.scene.data.model.pop
28 import com.android.systemui.scene.data.model.push
29 import com.android.systemui.scene.data.model.sceneStackOf
30 import com.android.systemui.scene.domain.SceneFrameworkTableLog
31 import com.android.systemui.scene.shared.logger.SceneLogger
32 import com.android.systemui.scene.shared.model.SceneContainerConfig
33 import javax.inject.Inject
34 import kotlinx.coroutines.flow.Flow
35 import kotlinx.coroutines.flow.MutableStateFlow
36 import kotlinx.coroutines.flow.StateFlow
37 import kotlinx.coroutines.flow.asStateFlow
38 import kotlinx.coroutines.flow.map
39 import kotlinx.coroutines.flow.update
40 
41 @SysUISingleton
42 class SceneBackInteractor
43 @Inject
44 constructor(
45     private val logger: SceneLogger,
46     private val sceneContainerConfig: SceneContainerConfig,
47     @SceneFrameworkTableLog private val tableLogBuffer: TableLogBuffer,
48 ) {
49     private val _backStack = MutableStateFlow(sceneStackOf())
50     val backStack: StateFlow<SceneStack> = _backStack.asStateFlow()
51 
52     /**
53      * The scene to navigate to when the user triggers back navigation.
54      *
55      * This is meant for scene implementations to consult with when they implement their destination
56      * scene flow.
57      *
58      * Note that this flow could emit any scene from the [SceneContainerConfig] and that it's an
59      * illegal state to have scene implementation map to itself in its destination scene flow. Thus,
60      * scene implementations might wish to filter their own scene key out before using this.
61      */
62     val backScene: Flow<SceneKey?> = backStack.map { it.peek() }
63 
64     fun onSceneChange(from: SceneKey, to: SceneKey) {
65         check(from != to) { "from == to, from=${from.debugName}, to=${to.debugName}" }
66 
67         val prevVal = backStack.value
68         _backStack.update { stack ->
69             when (stackOperation(from, to, stack)) {
70                 null -> stack
71                 Clear -> sceneStackOf()
72                 Push -> stack.push(from)
73                 Pop ->
74                     checkNotNull(stack.pop()) { "Cannot pop ${from.debugName} when stack is empty" }
75             }
76         }
77         logger.logSceneBackStack(backStack.value)
78         tableLogBuffer.logDiffs(
79             prevVal = DiffableSceneStack(prevVal),
80             newVal = DiffableSceneStack(backStack.value),
81         )
82     }
83 
84     /** Applies the given [transform] to the back stack. */
85     fun updateBackStack(transform: (SceneStack) -> SceneStack) {
86         val prevVal = backStack.value
87         _backStack.update { stack -> transform(stack) }
88         logger.logSceneBackStack(backStack.value)
89         tableLogBuffer.logDiffs(
90             prevVal = DiffableSceneStack(prevVal),
91             newVal = DiffableSceneStack(backStack.value),
92         )
93     }
94 
95     private fun stackOperation(from: SceneKey, to: SceneKey, stack: SceneStack): StackOperation? {
96         val fromDistance =
97             checkNotNull(sceneContainerConfig.navigationDistances[from]) {
98                 "No distance mapping for scene \"${from.debugName}\"!"
99             }
100         val toDistance =
101             checkNotNull(sceneContainerConfig.navigationDistances[to]) {
102                 "No distance mapping for scene \"${to.debugName}\"!"
103             }
104 
105         return when {
106             toDistance == 0 -> Clear
107             toDistance > fromDistance -> Push
108             stack.peek() != to -> null
109             toDistance < fromDistance -> Pop
110             else ->
111                 error(
112                     "No mapping when from=${from.debugName} (distance=$fromDistance)," +
113                         " to=${to.debugName} (distance=$toDistance)!"
114                 )
115         }
116     }
117 
118     private sealed interface StackOperation
119 
120     private data object Clear : StackOperation
121 
122     private data object Push : StackOperation
123 
124     private data object Pop : StackOperation
125 
126     private class DiffableSceneStack(private val sceneStack: SceneStack) :
127         Diffable<DiffableSceneStack> {
128 
129         override fun logDiffs(prevVal: DiffableSceneStack, row: TableRowLogger) {
130             row.logChange(
131                 columnName = "backStack",
132                 value = sceneStack.asIterable().joinToString { it.debugName },
133             )
134         }
135     }
136 }
137