1 /*
<lambda>null2  * Copyright 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 androidx.wear.protolayout.material3
18 
19 import androidx.annotation.Dimension
20 import androidx.annotation.Dimension.Companion.DP
21 import androidx.wear.protolayout.LayoutElementBuilders.Column
22 import androidx.wear.protolayout.LayoutElementBuilders.LayoutElement
23 import androidx.wear.protolayout.ModifiersBuilders.Padding
24 import androidx.wear.protolayout.material3.TitleContentPlacementInDataCard.Companion.Bottom
25 import androidx.wear.protolayout.material3.TitleContentPlacementInDataCard.Companion.Top
26 import androidx.wear.protolayout.material3.Typography.TypographyToken
27 import androidx.wear.protolayout.modifiers.padding
28 
29 internal object DataCardDefaults {
30     /**
31      * Returns [LayoutElement] describing the inner content for the data card.
32      *
33      * This is a [Column] containing the following:
34      * * icon or secondary label, if top aligned and if present
35      * * title
36      * * content
37      * * icon or secondary label, if bottom aligned and if present
38      */
39     internal fun buildContentForDataCard(
40         title: LayoutElement,
41         content: LayoutElement?,
42         style: DataCardStyle,
43         secondaryText: LayoutElement? = null,
44         secondaryIcon: LayoutElement? = null,
45         // Bottom, because when there's no secondaryIcon, label should be on top
46         titleContentPlacement: TitleContentPlacementInDataCard = Bottom
47     ): LayoutElement {
48         val verticalElementBuilder: Column.Builder = Column.Builder()
49 
50         // If it's labels only, placement doesn't matter, and icon and secondaryLabel won't be
51         // added.
52         when (titleContentPlacement) {
53             Top ->
54                 ContainerWithSpacersBuilder<LayoutElement>(
55                         { element: LayoutElement? -> verticalElementBuilder.addContent(element!!) },
56                         title
57                     )
58                     .addElement(content, horizontalSpacer(style.titleToContentSpaceDp))
59                     .addElement(
60                         secondaryIcon ?: secondaryText,
61                         horizontalSpacer(
62                             if (secondaryIcon != null) {
63                                 style.iconToTextSpaceDp
64                             } else {
65                                 style.secondaryLabelToTextSpaceDp
66                             }
67                         )
68                     )
69             Bottom ->
70                 ContainerWithSpacersBuilder<LayoutElement>(
71                         { element: LayoutElement? -> verticalElementBuilder.addContent(element!!) },
72                         secondaryIcon ?: secondaryText,
73                     )
74                     .addElement(
75                         title,
76                         horizontalSpacer(
77                             if (secondaryIcon != null) {
78                                 style.iconToTextSpaceDp
79                             } else {
80                                 style.secondaryLabelToTextSpaceDp
81                             }
82                         )
83                     )
84                     .addElement(content, horizontalSpacer(style.titleToContentSpaceDp))
85         }
86         return verticalElementBuilder.build()
87     }
88 }
89 
90 /**
91  * Defines the placement of the `title` and `content` slots in [iconDataCard], relative to other
92  * optional slots in that type of card.
93  */
94 @JvmInline
95 public value class TitleContentPlacementInDataCard private constructor(internal val value: Int) {
96     public companion object {
97         /**
98          * Slots for `title` and `content` in [iconDataCard] will be placed first, and followed by
99          * `icon` or `secondaryLabel` if present.
100          */
101         public val Top: TitleContentPlacementInDataCard = TitleContentPlacementInDataCard(0)
102 
103         /**
104          * Slots for `title` and `content` in [iconDataCard] will be placed last, with `icon` or
105          * `secondaryLabel` above it, sif present.
106          */
107         public val Bottom: TitleContentPlacementInDataCard = TitleContentPlacementInDataCard(1)
108     }
109 }
110 
111 /** Provides style values for the data card component. */
112 public class DataCardStyle
113 internal constructor(
114     internal val innerPadding: Padding,
115     @Dimension(unit = DP) internal val titleToContentSpaceDp: Int,
116     @TypographyToken internal val titleTypography: Int,
117     @TypographyToken internal val contentTypography: Int,
118     @TypographyToken internal val secondaryLabelTypography: Int,
119     @Dimension(unit = DP) internal val iconSize: Int,
120     @Dimension(unit = DP) internal val iconToTextSpaceDp: Int = 6,
121     @Dimension(unit = DP) internal val secondaryLabelToTextSpaceDp: Int = 8
122 ) {
123     public companion object {
124         /** The default spacer width or height that should be between different elements. */
125         @Dimension(unit = DP) private const val DEFAULT_SPACE_DP: Int = 4
126 
127         /** The default smaller spacer width or height that should be between different elements. */
128         @Dimension(unit = DP) private const val SMALL_SPACE_DP: Int = 2
129 
130         /** The default no spacing width or height that should be between different elements. */
131         @Dimension(unit = DP) private const val EMPTY_SPACE_DP: Int = 0
132 
133         @Dimension(unit = DP) private const val ICON_SIZE_SMALL_DP: Int = 26
134 
135         @Dimension(unit = DP) private const val ICON_SIZE_LARGE_DP: Int = 32
136 
137         @Dimension(unit = DP) private const val PADDING_SMALL_DP = 8f
138 
139         @Dimension(unit = DP) private const val PADDING_DEFAULT_DP = 10f
140 
141         @Dimension(unit = DP) private const val PADDING_LARGE_DP = 14f
142 
143         @Dimension(unit = DP) private const val PADDING_EXTRA_LARGE_DP = 16f
144 
145         /**
146          * Default style variation for the [iconDataCard] or [textDataCard] where all opinionated
147          * inner content is displayed in a small size.
148          */
smallDataCardStylenull149         public fun smallDataCardStyle(): DataCardStyle =
150             DataCardStyle(
151                 innerPadding = padding(PADDING_SMALL_DP),
152                 titleToContentSpaceDp = SMALL_SPACE_DP,
153                 titleTypography = Typography.LABEL_MEDIUM,
154                 contentTypography = Typography.BODY_SMALL,
155                 secondaryLabelTypography = Typography.BODY_MEDIUM,
156                 iconSize = ICON_SIZE_SMALL_DP
157             )
158 
159         /**
160          * Default style variation for the [iconDataCard] or [textDataCard] where all opinionated
161          * inner content is displayed in a medium size.
162          */
163         public fun defaultDataCardStyle(): DataCardStyle =
164             DataCardStyle(
165                 innerPadding = padding(PADDING_DEFAULT_DP),
166                 titleToContentSpaceDp = SMALL_SPACE_DP,
167                 titleTypography = Typography.LABEL_LARGE,
168                 contentTypography = Typography.BODY_SMALL,
169                 secondaryLabelTypography = Typography.BODY_MEDIUM,
170                 iconSize = ICON_SIZE_LARGE_DP
171             )
172 
173         /**
174          * Default style variation for the [iconDataCard] or [textDataCard] where all opinionated
175          * inner content is displayed in a large size.
176          */
177         public fun largeDataCardStyle(): DataCardStyle =
178             DataCardStyle(
179                 innerPadding = padding(PADDING_DEFAULT_DP),
180                 titleToContentSpaceDp = EMPTY_SPACE_DP,
181                 titleTypography = Typography.DISPLAY_SMALL,
182                 contentTypography = Typography.BODY_SMALL,
183                 secondaryLabelTypography = Typography.BODY_MEDIUM,
184                 iconSize = ICON_SIZE_LARGE_DP
185             )
186 
187         /**
188          * Default style variation for the [iconDataCard] or [textDataCard] where all opinionated
189          * inner content is displayed in an extra large size.
190          */
191         public fun extraLargeDataCardStyle(): DataCardStyle =
192             DataCardStyle(
193                 innerPadding =
194                     padding(horizontal = PADDING_DEFAULT_DP, vertical = PADDING_EXTRA_LARGE_DP),
195                 titleToContentSpaceDp = EMPTY_SPACE_DP,
196                 titleTypography = Typography.DISPLAY_MEDIUM,
197                 contentTypography = Typography.BODY_SMALL,
198                 secondaryLabelTypography = Typography.BODY_MEDIUM,
199                 iconSize = ICON_SIZE_LARGE_DP,
200                 iconToTextSpaceDp = DEFAULT_SPACE_DP,
201                 secondaryLabelToTextSpaceDp = DEFAULT_SPACE_DP
202             )
203 
204         /**
205          * Default style variation for the [iconDataCard] or [textDataCard] where all opinionated
206          * inner content is displayed in a small size. This should be used when [iconDataCard] or
207          * [textDataCard] only has `title` and `content`.
208          */
209         public fun smallCompactDataCardStyle(): DataCardStyle =
210             DataCardStyle(
211                 innerPadding = padding(horizontal = PADDING_LARGE_DP, vertical = PADDING_SMALL_DP),
212                 titleToContentSpaceDp = EMPTY_SPACE_DP,
213                 titleTypography = Typography.NUMERAL_MEDIUM,
214                 contentTypography = Typography.LABEL_MEDIUM,
215                 secondaryLabelTypography = Typography.BODY_MEDIUM,
216                 iconSize = EMPTY_SPACE_DP
217             )
218 
219         /**
220          * Default style variation for the [iconDataCard] or [textDataCard] where all opinionated
221          * inner content is displayed in a medium size. This should be used when [iconDataCard] or
222          * [textDataCard] only has `title` and `content`.
223          */
224         public fun defaultCompactDataCardStyle(): DataCardStyle =
225             DataCardStyle(
226                 innerPadding = padding(horizontal = PADDING_LARGE_DP, vertical = PADDING_SMALL_DP),
227                 titleToContentSpaceDp = EMPTY_SPACE_DP,
228                 titleTypography = Typography.NUMERAL_LARGE,
229                 contentTypography = Typography.LABEL_LARGE,
230                 secondaryLabelTypography = Typography.BODY_MEDIUM,
231                 iconSize = EMPTY_SPACE_DP
232             )
233 
234         /**
235          * Default style variation for the [iconDataCard] or [textDataCard] where all opinionated
236          * inner content is displayed in a large size. This should be used when [iconDataCard] or
237          * [textDataCard] only has `title` and `content`.
238          */
239         public fun largeCompactDataCardStyle(): DataCardStyle =
240             DataCardStyle(
241                 innerPadding = padding(horizontal = PADDING_LARGE_DP, vertical = PADDING_SMALL_DP),
242                 titleToContentSpaceDp = EMPTY_SPACE_DP,
243                 titleTypography = Typography.NUMERAL_EXTRA_LARGE,
244                 contentTypography = Typography.LABEL_LARGE,
245                 secondaryLabelTypography = Typography.BODY_MEDIUM,
246                 iconSize = EMPTY_SPACE_DP
247             )
248     }
249 }
250