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