forked from microsoft/fluentui-android
-
Notifications
You must be signed in to change notification settings - Fork 2
/
AppBar.kt
313 lines (302 loc) · 14.7 KB
/
AppBar.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
package com.microsoft.fluentui.tokenized
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.BasicText
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.draw.scale
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.semantics.heading
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.ExperimentalTextApi
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.microsoft.fluentui.core.R
import com.microsoft.fluentui.icons.ListItemIcons
import com.microsoft.fluentui.icons.SearchBarIcons
import com.microsoft.fluentui.icons.appbaricons.AppBarIcons
import com.microsoft.fluentui.icons.appbaricons.appbaricons.Arrowback
import com.microsoft.fluentui.icons.listitemicons.Chevron
import com.microsoft.fluentui.theme.FluentTheme
import com.microsoft.fluentui.theme.token.*
import com.microsoft.fluentui.theme.token.controlTokens.AppBarInfo
import com.microsoft.fluentui.theme.token.controlTokens.AppBarSize
import com.microsoft.fluentui.theme.token.controlTokens.AppBarTokens
/**
* An app bar appears at the top of an app screen, below the status bar,
* and enables navigation through a series of hierarchical screens.
* When a new screen is displayed, a back button, often labeled with the title of
* the previous screen, appears on the left side of the bar. Sometimes, the right side
* of a navigation bar contains a control, like an Edit or a Done button,
* for managing the content within the active view. In a split view,
* a navigation bar may appear in a single pane of the split view.
* Navigation bars are translucent, may have a background tint, and can be configured
* to hide when the keyboard is onscreen, a gesture occurs, or a view resizes.
*
* @param title Title Of the current page
* @param modifier Optional Modifier for updating appbar
* @param appBarSize Enum to define App Bar Size. Default: [AppBarSize.Medium]
* @param style Fluent Style of AppBar. Default: [FluentStyle.Neutral]
* @param subTitle Subtitle to be displayed. Default: [null]
* @param logo Composable to be placed at left of Title. Guideline is to not increase a size of 32x32. Default: [null]
* @param searchMode Boolean to enable/disable searchMode. Default: [false]
* @param navigationIcon Navigate Back Icon to be placed at extreme left. Default: [SearchBarIcons.Arrowback]
* @param postTitleIcon Icon to be placed after title making the title clickable. Default: Empty [FluentIcon]
* @param preSubtitleIcon Icon to be placed before subtitle. Default: Empty [FluentIcon]
* @param postSubtitleIcon Icon to be placed after subtitle. Default: [ListItemIcons.Chevron]
* @param rightAccessoryView Row Placeholder to be placed at right on AppTitle. Default: [null]
* @param searchBar Composable to be placed as searchbar below appTitle. Default: [null]
* @param bottomBar Composable to Be placed below appTitle. Displayed if searchbar is not provided or when in searchmode. Default: [null]
* @param bottomBorder Boolean to place a bottom border on AppBar. Applies only when searchBar and bottomBar are empty. Default: [true]
* @param appTitleDelta Ratio of opening of appTitle. Used for Shychrome and other animations. Default: [1.0F]
* @param accessoryDelta Ratio of opening of accessory View. Used for Shychrome and other animations. Default: [1.0F]
* @param appBarTokens Optional Tokens for App Bar to customize it. Default: [null]
*/
// TAGS FOR TESTING
const val APP_BAR = "Fluent App bar"
const val APP_BAR_SUBTITLE = "Fluent App bar Subtitle"
const val APP_BAR_BOTTOM_BAR = "Fluent App bar Bottom bar"
const val APP_BAR_SEARCH_BAR = "Fluent App bar Search bar"
@OptIn(ExperimentalTextApi::class)
@Composable
fun AppBar(
title: String,
modifier: Modifier = Modifier,
appBarSize: AppBarSize = AppBarSize.Medium,
style: FluentStyle = FluentStyle.Neutral,
subTitle: String? = null,
logo: @Composable (() -> Unit)? = null,
searchMode: Boolean = false,
navigationIcon: FluentIcon = FluentIcon(AppBarIcons.Arrowback, flipOnRtl = true),
postTitleIcon: FluentIcon = FluentIcon(),
preSubtitleIcon: FluentIcon = FluentIcon(),
postSubtitleIcon: FluentIcon = FluentIcon(
ListItemIcons.Chevron,
contentDescription = LocalContext.current.resources.getString(R.string.fluentui_chevron)
),
rightAccessoryView: @Composable (RowScope.() -> Unit)? = null,
searchBar: @Composable (RowScope.() -> Unit)? = null,
bottomBar: @Composable (RowScope.() -> Unit)? = null,
bottomBorder: Boolean = true,
appTitleDelta: Float = 1.0F,
accessoryDelta: Float = 1.0F,
appBarTokens: AppBarTokens? = null
) {
val themeID =
FluentTheme.themeID //Adding This only for recomposition in case of Token Updates. Unused otherwise.
val token = appBarTokens
?: FluentTheme.controlTokens.tokens[ControlTokens.ControlType.AppBarControlType] as AppBarTokens
val appBarInfo = AppBarInfo(style, appBarSize)
Box(
modifier = modifier
.fillMaxWidth()
.testTag(APP_BAR)
) {
Column(
modifier = Modifier
.fillMaxWidth()
.background(token.backgroundBrush(appBarInfo))
.then(
if (bottomBorder && searchBar == null && bottomBar == null) {
val strokeWidth =
with(LocalDensity.current) { token.borderStroke(appBarInfo).width.toPx() }
val strokeColor = token.borderStroke(appBarInfo).brush
Modifier.drawBehind {
val y = size.height - strokeWidth / 2
drawLine(
strokeColor,
Offset(0f, y),
Offset(size.width, y),
strokeWidth
)
}
} else {
Modifier
}
)
) {
Row(
Modifier
.requiredHeight(56.dp * appTitleDelta)
.animateContentSize()
.fillMaxWidth()
.scale(scaleX = 1.0F, scaleY = appTitleDelta)
.alpha(if (appTitleDelta != 1.0F) appTitleDelta / 3 else 1.0F),
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically
) {
if (appBarSize != AppBarSize.Large && navigationIcon.isIconAvailable()) {
Icon(
navigationIcon,
modifier =
Modifier.then(
if(navigationIcon.onClick != null)
Modifier.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(color = token.navigationIconRippleColor()),
enabled = true,
onClick = navigationIcon.onClick ?: {}
)
else Modifier
)
.padding(token.navigationIconPadding(appBarInfo))
.size(token.leftIconSize(appBarInfo)),
tint = token.navigationIconColor(appBarInfo)
)
}
if (appBarSize != AppBarSize.Medium) {
Box(
modifier = Modifier
.then(
if (appBarSize == AppBarSize.Large)
Modifier.padding(start = 16.dp)
else
Modifier
)
) {
logo?.invoke()
}
}
val titleTextStyle = token.titleTypography(appBarInfo)
val subtitleTextStyle = token.subtitleTypography(appBarInfo)
if (appBarSize != AppBarSize.Large && !subTitle.isNullOrBlank()) {
Column(
modifier = Modifier
.weight(1F)
.padding(token.textPadding(appBarInfo))
.testTag(APP_BAR_SUBTITLE)
) {
Row(
modifier = Modifier
.then(
if (postTitleIcon.onClick != null && appBarSize == AppBarSize.Small)
Modifier.clickable(onClick = postTitleIcon.onClick!!)
else
Modifier
), verticalAlignment = Alignment.CenterVertically
) {
BasicText(
text = title,
style = titleTextStyle.merge(
TextStyle(
color = token.titleTextColor(appBarInfo)
)
),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
if (postTitleIcon.isIconAvailable() && appBarSize == AppBarSize.Small)
Icon(
postTitleIcon.value(),
postTitleIcon.contentDescription,
modifier = Modifier
.size(token.titleIconSize(appBarInfo)),
tint = token.titleIconColor(appBarInfo),
)
}
Row(
modifier = Modifier
.then(
if (postSubtitleIcon.onClick != null)
Modifier.clickable(onClick = postSubtitleIcon.onClick!!)
else
Modifier
), verticalAlignment = Alignment.CenterVertically
) {
if (preSubtitleIcon.isIconAvailable())
Icon(
preSubtitleIcon,
modifier = Modifier
.size(
token.subtitleIconSize(
appBarInfo
)
),
tint = token.subtitleIconColor(appBarInfo)
)
BasicText(
subTitle,
style = subtitleTextStyle.merge(
TextStyle(
color = token.subtitleTextColor(
appBarInfo
)
)
),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
if (postSubtitleIcon.isIconAvailable())
Icon(
postSubtitleIcon.value(),
contentDescription = postSubtitleIcon.contentDescription,
modifier = Modifier
.size(
token.subtitleIconSize(
appBarInfo
)
),
tint = token.subtitleIconColor(appBarInfo)
)
}
}
} else {
BasicText(
text = title,
modifier = Modifier
.padding(token.textPadding(appBarInfo))
.weight(1F)
.semantics { heading() },
style = titleTextStyle.merge(
TextStyle(
color = token.titleTextColor(appBarInfo)
)
),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
if (rightAccessoryView != null) {
rightAccessoryView()
}
}
if (searchBar != null) {
Row(
modifier
.animateContentSize()
.fillMaxWidth()
.then(if (!searchMode) Modifier.height(56.dp * accessoryDelta) else Modifier)
.padding(vertical = 8.dp)
.testTag(APP_BAR_SEARCH_BAR),
horizontalArrangement = Arrangement.Center
) {
searchBar()
}
}
if (bottomBar != null && (searchMode || searchBar == null)) {
Row(
Modifier
.animateContentSize()
.fillMaxWidth()
.then(if (!searchMode) Modifier.height(48.dp * accessoryDelta) else Modifier)
.padding(vertical = 8.dp)
.testTag(APP_BAR_BOTTOM_BAR),
) {
bottomBar()
}
}
}
}
}