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