blob: 9f934d882c61aa3aed7187e52dc8d7dc7452c2eb [file] [log] [blame]
Vineet Kumar5e594582022-08-19 15:52:28 +05301/*
Vineet Kumar507211d2023-01-04 19:16:38 +05302 * Copyright 2023 The Android Open Source Project
Vineet Kumar5e594582022-08-19 15:52:28 +05303 *
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
Vineet Kumar507211d2023-01-04 19:16:38 +053017package androidx.tv.material3
Vineet Kumar5e594582022-08-19 15:52:28 +053018
Doris Liuc63c7592023-02-21 15:57:16 -080019import androidx.compose.animation.AnimatedContentTransitionScope
Vineet Kumar5e594582022-08-19 15:52:28 +053020import androidx.compose.animation.AnimatedVisibilityScope
21import androidx.compose.animation.ContentTransform
22import androidx.compose.animation.EnterTransition
23import androidx.compose.animation.ExitTransition
Vineet Kumar5e594582022-08-19 15:52:28 +053024import androidx.compose.animation.core.tween
25import androidx.compose.animation.fadeIn
26import androidx.compose.animation.fadeOut
27import androidx.compose.animation.with
Vineet Kumar5e594582022-08-19 15:52:28 +053028import androidx.compose.foundation.layout.Box
29import androidx.compose.foundation.layout.BoxScope
30import androidx.compose.runtime.Composable
31import androidx.compose.runtime.Immutable
32import androidx.compose.runtime.getValue
33import androidx.compose.runtime.mutableStateOf
34import androidx.compose.runtime.remember
35import androidx.compose.runtime.setValue
36import androidx.compose.ui.Alignment
37import androidx.compose.ui.ExperimentalComposeUiApi
38import androidx.compose.ui.Modifier
39import androidx.compose.ui.focus.FocusDirection
40import androidx.compose.ui.focus.onFocusChanged
41import androidx.compose.ui.platform.LocalFocusManager
Vineet Kumar5e594582022-08-19 15:52:28 +053042
43/**
44 * Immersive List consists of a list with multiple items and a background that displays content
45 * based on the item in focus.
46 * To animate the background's entry and exit, use [ImmersiveListBackgroundScope.AnimatedContent].
47 * To display the background only when the list is in focus, use
48 * [ImmersiveListBackgroundScope.AnimatedVisibility].
49 *
Vighnesh Raut5dd50782022-12-21 17:16:55 +053050 * @sample androidx.tv.samples.SampleImmersiveList
51 *
Vineet Kumar5e594582022-08-19 15:52:28 +053052 * @param background Composable defining the background to be displayed for a given item's
Vineet Kumar11d7e402022-12-04 13:18:52 +053053 * index. `listHasFocus` argument can be used to hide the background when the list is not in focus
Vineet Kumar5e594582022-08-19 15:52:28 +053054 * @param modifier applied to Immersive List.
55 * @param listAlignment Alignment of the List with respect to the Immersive List.
56 * @param list composable defining the list of items that has to be rendered.
57 */
58@Suppress("IllegalExperimentalApiUsage")
Vineet Kumar11d7e402022-12-04 13:18:52 +053059@OptIn(ExperimentalComposeUiApi::class)
Vineet Kumar507211d2023-01-04 19:16:38 +053060@ExperimentalTvMaterial3Api
Vineet Kumar5e594582022-08-19 15:52:28 +053061@Composable
62fun ImmersiveList(
63 background:
64 @Composable ImmersiveListBackgroundScope.(index: Int, listHasFocus: Boolean) -> Unit,
65 modifier: Modifier = Modifier,
66 listAlignment: Alignment = Alignment.BottomEnd,
67 list: @Composable ImmersiveListScope.() -> Unit,
68) {
69 var currentItemIndex by remember { mutableStateOf(0) }
70 var listHasFocus by remember { mutableStateOf(false) }
71
Vineet Kumar11d7e402022-12-04 13:18:52 +053072 Box(modifier.bringIntoViewIfChildrenAreFocused()) {
Vineet Kumar5e594582022-08-19 15:52:28 +053073 ImmersiveListBackgroundScope(this).background(currentItemIndex, listHasFocus)
74
75 val focusManager = LocalFocusManager.current
76
77 Box(Modifier.align(listAlignment).onFocusChanged { listHasFocus = it.hasFocus }) {
78 ImmersiveListScope {
79 currentItemIndex = it
80 focusManager.moveFocus(FocusDirection.Enter)
81 }.list()
82 }
83 }
84}
85
Vineet Kumar507211d2023-01-04 19:16:38 +053086@ExperimentalTvMaterial3Api
Vineet Kumar5e594582022-08-19 15:52:28 +053087object ImmersiveListDefaults {
88 /**
89 * Default transition used to bring the background content into view
90 */
91 val EnterTransition: EnterTransition = fadeIn(animationSpec = tween(300))
92
93 /**
94 * Default transition used to remove the background content from view
95 */
96 val ExitTransition: ExitTransition = fadeOut(animationSpec = tween(300))
97}
98
99@Immutable
Vineet Kumar507211d2023-01-04 19:16:38 +0530100@ExperimentalTvMaterial3Api
101public class ImmersiveListBackgroundScope internal constructor(boxScope: BoxScope) : BoxScope
Vineet Kumar5e594582022-08-19 15:52:28 +0530102by boxScope {
103
104 /**
105 * [ImmersiveListBackgroundScope.AnimatedVisibility] composable animates the appearance and
106 * disappearance of its content, as [visible] value changes. Different [EnterTransition]s and
107 * [ExitTransition]s can be defined in [enter] and [exit] for the appearance and disappearance
108 * animation.
109 *
110 * @param visible defines whether the content should be visible
111 * @param modifier modifier for the Layout created to contain the [content]
112 * @param enter EnterTransition(s) used for the appearing animation, fading in by default
113 * @param exit ExitTransition(s) used for the disappearing animation, fading out by default
Vineet Kumar507211d2023-01-04 19:16:38 +0530114 * @param label helps differentiate from other animations in Android Studio
Vineet Kumar5e594582022-08-19 15:52:28 +0530115 * @param content Content to appear or disappear based on the value of [visible]
116 *
117 * @link androidx.compose.animation.AnimatedVisibility
118 * @see androidx.compose.animation.AnimatedVisibility
119 * @see EnterTransition
120 * @see ExitTransition
121 * @see AnimatedVisibilityScope
122 */
123 @Composable
124 fun AnimatedVisibility(
125 visible: Boolean,
126 modifier: Modifier = Modifier,
127 enter: EnterTransition = ImmersiveListDefaults.EnterTransition,
128 exit: ExitTransition = ImmersiveListDefaults.ExitTransition,
129 label: String = "AnimatedVisibility",
130 content: @Composable AnimatedVisibilityScope.() -> Unit
131 ) {
132 androidx.compose.animation.AnimatedVisibility(
133 visible,
134 modifier,
135 enter,
136 exit,
137 label,
Vineet Kumar11d7e402022-12-04 13:18:52 +0530138 content
139 )
Vineet Kumar5e594582022-08-19 15:52:28 +0530140 }
141
142 /**
143 * [ImmersiveListBackgroundScope.AnimatedContent] is a container that automatically animates its
144 * content when [targetState] changes. Its [content] for different target states is defined in a
145 * mapping between a target state and a composable function.
146 *
147 * @param targetState defines the key to choose the content to be displayed
148 * @param modifier modifier for the Layout created to contain the [content]
149 * @param transitionSpec defines the EnterTransition(s) and ExitTransition(s) used to display
150 * and remove the content, fading in and fading out by default
Vighnesh Raut5dd50782022-12-21 17:16:55 +0530151 * @param contentAlignment specifies how the background content should be aligned in the
152 * container
Vineet Kumar5e594582022-08-19 15:52:28 +0530153 * @param content Content to appear or disappear based on the value of [targetState]
154 *
155 * @link androidx.compose.animation.AnimatedContent
156 * @see androidx.compose.animation.AnimatedContent
157 * @see ContentTransform
Doris Liuc63c7592023-02-21 15:57:16 -0800158 * @see AnimatedContentTransitionScope
Vineet Kumar5e594582022-08-19 15:52:28 +0530159 */
Vineet Kumar5e594582022-08-19 15:52:28 +0530160 @Composable
161 fun AnimatedContent(
162 targetState: Int,
163 modifier: Modifier = Modifier,
Doris Liuc63c7592023-02-21 15:57:16 -0800164 transitionSpec: AnimatedContentTransitionScope<Int>.() -> ContentTransform = {
Vineet Kumar5e594582022-08-19 15:52:28 +0530165 ImmersiveListDefaults.EnterTransition.with(ImmersiveListDefaults.ExitTransition)
166 },
167 contentAlignment: Alignment = Alignment.TopStart,
168 content: @Composable AnimatedVisibilityScope.(targetState: Int) -> Unit
169 ) {
170 androidx.compose.animation.AnimatedContent(
171 targetState,
172 modifier,
173 transitionSpec,
174 contentAlignment,
Kseniia Grey78441cf2022-10-20 13:01:44 +0100175 content = content
176 )
Vineet Kumar5e594582022-08-19 15:52:28 +0530177 }
178}
179
180@Immutable
Vineet Kumar507211d2023-01-04 19:16:38 +0530181@ExperimentalTvMaterial3Api
182public class ImmersiveListScope internal constructor(private val onFocused: (Int) -> Unit) {
Vineet Kumar5e594582022-08-19 15:52:28 +0530183 /**
184 * Modifier to be added to each of the items of the list within ImmersiveList to inform the
Vighnesh Raut5dd50782022-12-21 17:16:55 +0530185 * ImmersiveList of the index of the item in focus
Vineet Kumar5e594582022-08-19 15:52:28 +0530186 *
Vighnesh Raut5dd50782022-12-21 17:16:55 +0530187 * > **NOTE**: This modifier needs to be paired with either the "focusable" or the "clickable"
188 * modifier for it to work
189 *
190 * @param index index of the item within the list
Vineet Kumar5e594582022-08-19 15:52:28 +0530191 */
Vighnesh Raut5dd50782022-12-21 17:16:55 +0530192 fun Modifier.immersiveListItem(index: Int): Modifier {
193 return onFocusChanged {
194 if (it.isFocused) {
195 onFocused(index)
196 }
197 }
Vineet Kumar5e594582022-08-19 15:52:28 +0530198 }
Vineet Kumar11d7e402022-12-04 13:18:52 +0530199}