1 /*
2 * Copyright 2022 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 androidx.tv.material3
18
19 import androidx.compose.foundation.interaction.Interaction
20 import androidx.compose.foundation.interaction.MutableInteractionSource
21 import androidx.compose.foundation.layout.Arrangement
22 import androidx.compose.foundation.layout.Row
23 import androidx.compose.foundation.layout.RowScope
24 import androidx.compose.runtime.Composable
25 import androidx.compose.ui.Alignment
26 import androidx.compose.ui.Modifier
27 import androidx.compose.ui.focus.onFocusChanged
28 import androidx.compose.ui.graphics.Color
29 import androidx.compose.ui.graphics.RectangleShape
30 import androidx.compose.ui.semantics.Role
31 import androidx.compose.ui.semantics.role
32 import androidx.compose.ui.semantics.selected
33 import androidx.compose.ui.semantics.semantics
34
35 /**
36 * Material Design tab.
37 *
38 * A default Tab, also known as a Primary Navigation Tab. Tabs organize content across different
39 * screens, data sets, and other interactions.
40 *
41 * This should typically be used inside of a [TabRow], see the corresponding documentation for
42 * example usage.
43 *
44 * @param selected whether this tab is selected or not
45 * @param onFocus called when this tab is focused
46 * @param modifier the [Modifier] to be applied to this tab
47 * @param onClick called when this tab is clicked (with D-Pad Center)
48 * @param enabled controls the enabled state of this tab. When `false`, this component will not
49 * respond to user input, and it will appear visually disabled and disabled to accessibility
50 * services.
51 * @param colors these will be used by the tab when in different states (focused, selected, etc.)
52 * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
53 * emitting [Interaction]s for this tab. You can use this to change the tab's appearance or
54 * preview the tab in different states. Note that if `null` is provided, interactions will still
55 * happen internally.
56 * @param content content of the [Tab]
57 */
58 @Composable
Tabnull59 fun TabRowScope.Tab(
60 selected: Boolean,
61 onFocus: () -> Unit,
62 modifier: Modifier = Modifier,
63 onClick: () -> Unit = {},
64 enabled: Boolean = true,
65 colors: TabColors = TabDefaults.pillIndicatorTabColors(),
66 interactionSource: MutableInteractionSource? = null,
67 content: @Composable RowScope.() -> Unit
68 ) {
69 Surface(
70 selected = selected,
71 onClick = onClick,
72 modifier =
73 modifier
<lambda>null74 .onFocusChanged {
75 if (it.isFocused) {
76 onFocus()
77 }
78 }
<lambda>null79 .semantics {
80 this.selected = selected
81 this.role = Role.Tab
82 },
83 colors =
84 colors.toSelectableSurfaceColors(
85 doesTabRowHaveFocus = hasFocus,
86 enabled = enabled,
87 ),
88 enabled = enabled,
89 scale = SelectableSurfaceScale.None,
90 shape = SelectableSurfaceDefaults.shape(shape = RectangleShape),
91 interactionSource = interactionSource,
<lambda>null92 ) {
93 Row(
94 horizontalArrangement = Arrangement.Center,
95 verticalAlignment = Alignment.CenterVertically,
96 content = content
97 )
98 }
99 }
100
101 /**
102 * Represents the colors used in a tab in different states.
103 * - See [TabDefaults.pillIndicatorTabColors] for the default colors used in a [Tab] when using a
104 * Pill indicator.
105 * - See [TabDefaults.underlinedIndicatorTabColors] for the default colors used in a [Tab] when
106 * using an Underlined indicator
107 */
108 class TabColors
109 internal constructor(
110 internal val contentColor: Color,
111 internal val inactiveContentColor: Color = contentColor.copy(alpha = 0.4f),
112 internal val selectedContentColor: Color,
113 internal val focusedContentColor: Color,
114 internal val focusedSelectedContentColor: Color,
115 internal val disabledContentColor: Color,
116 internal val disabledInactiveContentColor: Color = disabledContentColor.copy(alpha = 0.4f),
117 internal val disabledSelectedContentColor: Color,
118 ) {
equalsnull119 override fun equals(other: Any?): Boolean {
120 if (this === other) return true
121 if (other == null || other !is TabColors) return false
122
123 if (contentColor != other.contentColor) return false
124 if (inactiveContentColor != other.inactiveContentColor) return false
125 if (selectedContentColor != other.selectedContentColor) return false
126 if (focusedContentColor != other.focusedContentColor) return false
127 if (focusedSelectedContentColor != other.focusedSelectedContentColor) return false
128 if (disabledContentColor != other.disabledContentColor) return false
129 if (disabledInactiveContentColor != other.disabledInactiveContentColor) return false
130 if (disabledSelectedContentColor != other.disabledSelectedContentColor) return false
131
132 return true
133 }
134
hashCodenull135 override fun hashCode(): Int {
136 var result = contentColor.hashCode()
137 result = 31 * result + inactiveContentColor.hashCode()
138 result = 31 * result + selectedContentColor.hashCode()
139 result = 31 * result + focusedContentColor.hashCode()
140 result = 31 * result + focusedSelectedContentColor.hashCode()
141 result = 31 * result + disabledContentColor.hashCode()
142 result = 31 * result + disabledInactiveContentColor.hashCode()
143 result = 31 * result + disabledSelectedContentColor.hashCode()
144 return result
145 }
146 }
147
148 object TabDefaults {
149 /**
150 * [Tab]'s content colors to in conjunction with underlined indicator
151 *
152 * @param contentColor applied when the any of the other tabs is focused
153 * @param inactiveContentColor the default color of the tab's content when none of the tabs are
154 * focused
155 * @param selectedContentColor applied when the current tab is selected
156 * @param focusedContentColor applied when the current tab is focused
157 * @param focusedSelectedContentColor applied when the current tab is both focused and selected
158 * @param disabledContentColor applied when any of the other tabs is focused and the current tab
159 * is disabled
160 * @param disabledInactiveContentColor applied when the current tab is disabled and none of the
161 * tabs are focused
162 * @param disabledSelectedContentColor applied when the current tab is disabled and selected
163 */
164 @Composable
underlinedIndicatorTabColorsnull165 fun underlinedIndicatorTabColors(
166 contentColor: Color = LocalContentColor.current,
167 inactiveContentColor: Color = contentColor.copy(alpha = 0.4f),
168 selectedContentColor: Color = MaterialTheme.colorScheme.onPrimaryContainer,
169 focusedContentColor: Color = MaterialTheme.colorScheme.primary,
170 focusedSelectedContentColor: Color = focusedContentColor,
171 disabledContentColor: Color = contentColor,
172 disabledInactiveContentColor: Color = disabledContentColor.copy(alpha = 0.4f),
173 disabledSelectedContentColor: Color = selectedContentColor,
174 ): TabColors =
175 TabColors(
176 contentColor = contentColor,
177 inactiveContentColor = inactiveContentColor,
178 selectedContentColor = selectedContentColor,
179 focusedContentColor = focusedContentColor,
180 focusedSelectedContentColor = focusedSelectedContentColor,
181 disabledContentColor = disabledContentColor,
182 disabledInactiveContentColor = disabledInactiveContentColor,
183 disabledSelectedContentColor = disabledSelectedContentColor,
184 )
185
186 /**
187 * [Tab]'s content colors to in conjunction with pill indicator
188 *
189 * @param contentColor applied when the any of the other tabs is focused
190 * @param inactiveContentColor the default color of the tab's content when none of the tabs are
191 * focused
192 * @param selectedContentColor applied when the current tab is selected
193 * @param focusedContentColor applied when the current tab is focused
194 * @param focusedSelectedContentColor applied when the current tab is both focused and selected
195 * @param disabledContentColor applied when any of the other tabs is focused and the current tab
196 * is disabled
197 * @param disabledInactiveContentColor applied when the current tab is disabled and none of the
198 * tabs are focused
199 * @param disabledSelectedContentColor applied when the current tab is disabled and selected
200 */
201 @Composable
202 fun pillIndicatorTabColors(
203 contentColor: Color = LocalContentColor.current,
204 inactiveContentColor: Color = contentColor.copy(alpha = 0.4f),
205 selectedContentColor: Color = MaterialTheme.colorScheme.onPrimaryContainer,
206 focusedContentColor: Color = MaterialTheme.colorScheme.surfaceVariant,
207 focusedSelectedContentColor: Color = focusedContentColor,
208 disabledContentColor: Color = contentColor,
209 disabledInactiveContentColor: Color = disabledContentColor.copy(alpha = 0.4f),
210 disabledSelectedContentColor: Color = selectedContentColor,
211 ): TabColors =
212 TabColors(
213 contentColor = contentColor,
214 inactiveContentColor = inactiveContentColor,
215 selectedContentColor = selectedContentColor,
216 focusedContentColor = focusedContentColor,
217 focusedSelectedContentColor = focusedSelectedContentColor,
218 disabledContentColor = disabledContentColor,
219 disabledInactiveContentColor = disabledInactiveContentColor,
220 disabledSelectedContentColor = disabledSelectedContentColor,
221 )
222 }
223
224 @Composable
225 internal fun TabColors.toSelectableSurfaceColors(
226 doesTabRowHaveFocus: Boolean,
227 enabled: Boolean,
228 ) =
229 SelectableSurfaceDefaults.colors(
230 contentColor = if (doesTabRowHaveFocus) contentColor else inactiveContentColor,
231 selectedContentColor = if (enabled) selectedContentColor else disabledSelectedContentColor,
232 focusedContentColor = focusedContentColor,
233 focusedSelectedContentColor = focusedSelectedContentColor,
234 disabledContentColor =
235 if (doesTabRowHaveFocus) disabledContentColor else disabledInactiveContentColor,
236 containerColor = Color.Transparent,
237 focusedContainerColor = Color.Transparent,
238 pressedContainerColor = Color.Transparent,
239 focusedSelectedContainerColor = Color.Transparent,
240 selectedContainerColor = Color.Transparent,
241 pressedSelectedContainerColor = Color.Transparent,
242 disabledContainerColor = Color.Transparent,
243 )
244