* removed compositionProviders

* updating test

---------

Co-authored-by: PraveenKumar <pyeruva@microsoft.com>
This commit is contained in:
PraveenKumar yeruva 2023-04-24 16:46:10 +05:30 коммит произвёл GitHub
Родитель ce6cf2ddb3
Коммит 831e1d6c75
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
36 изменённых файлов: 3369 добавлений и 3938 удалений

Просмотреть файл

@ -17,7 +17,7 @@ import com.microsoft.fluentui.theme.token.controlTokens.CircularProgressIndicato
import com.microsoft.fluentui.theme.token.controlTokens.ShimmerShape
import com.microsoft.fluentui.tokenized.progress.CircularProgressIndicator
import com.microsoft.fluentui.tokenized.progress.LinearProgressIndicator
import com.microsoft.fluentui.tokenized.progress.Shimmer
import com.microsoft.fluentui.tokenized.shimmer.Shimmer
import org.junit.Rule
import org.junit.Test
@ -76,11 +76,11 @@ class V2ProgressIndicatorUITest {
fun testShimmerBounds(){
composeTestRule.setContent {
FluentTheme {
Shimmer(ShimmerShape.Box, modifier = Modifier
Shimmer(shape = ShimmerShape.Box, modifier = Modifier
.height(50.dp)
.width(50.dp)
.testTag("shimmer"))
Shimmer(ShimmerShape.Circle, modifier = Modifier.size(50.dp).testTag("circleShimmer"))
Shimmer(shape= ShimmerShape.Circle, modifier = Modifier.size(50.dp).testTag("circleShimmer"))
}
}
composeTestRule.onNodeWithTag("shimmer").assertHeightIsEqualTo(50.dp).assertWidthIsEqualTo(50.dp)

Просмотреть файл

@ -61,31 +61,31 @@ class V2BadgeActivity : DemoActivity() {
LazyRow {
item {
Spacer(modifier = Modifier.width(8.dp))
Badge("1", badgeType = BadgeType.Character)
Badge(text = "1", badgeType = BadgeType.Character)
}
item {
Spacer(modifier = Modifier.width(8.dp))
Badge("2", badgeType = BadgeType.Character)
Badge(text = "2", badgeType = BadgeType.Character)
}
item {
Spacer(modifier = Modifier.width(8.dp))
Badge("8", badgeType = BadgeType.Character)
Badge(text = "8", badgeType = BadgeType.Character)
}
item {
Spacer(modifier = Modifier.width(8.dp))
Badge("12", badgeType = BadgeType.Character)
Badge(text = "12", badgeType = BadgeType.Character)
}
item {
Spacer(modifier = Modifier.width(8.dp))
Badge("123", badgeType = BadgeType.Character)
Badge(text = "123", badgeType = BadgeType.Character)
}
item {
Spacer(modifier = Modifier.width(8.dp))
Badge("12345678910", badgeType = BadgeType.Character)
Badge(text = "12345678910", badgeType = BadgeType.Character)
}
item {
Spacer(modifier = Modifier.width(8.dp))
Badge("Badge", badgeType = BadgeType.Character)
Badge(text = "Badge", badgeType = BadgeType.Character)
}
}
}
@ -101,31 +101,31 @@ class V2BadgeActivity : DemoActivity() {
LazyRow {
item {
Spacer(modifier = Modifier.width(8.dp))
Badge("1", badgeType = BadgeType.List)
Badge(text = "1", badgeType = BadgeType.List)
}
item {
Spacer(modifier = Modifier.width(8.dp))
Badge("2", badgeType = BadgeType.List)
Badge(text = "2", badgeType = BadgeType.List)
}
item {
Spacer(modifier = Modifier.width(8.dp))
Badge("8", badgeType = BadgeType.List)
Badge(text = "8", badgeType = BadgeType.List)
}
item {
Spacer(modifier = Modifier.width(8.dp))
Badge("12", badgeType = BadgeType.List)
Badge(text = "12", badgeType = BadgeType.List)
}
item {
Spacer(modifier = Modifier.width(8.dp))
Badge("123", badgeType = BadgeType.List)
Badge(text = "123", badgeType = BadgeType.List)
}
item {
Spacer(modifier = Modifier.width(8.dp))
Badge("12345678910", badgeType = BadgeType.List)
Badge(text = "12345678910", badgeType = BadgeType.List)
}
item {
Spacer(modifier = Modifier.width(8.dp))
Badge("Badge", badgeType = BadgeType.List)
Badge(text = "Badge", badgeType = BadgeType.List)
}
}
}

Просмотреть файл

@ -19,7 +19,7 @@ import com.microsoft.fluentui.theme.token.controlTokens.CircularProgressIndicato
import com.microsoft.fluentui.theme.token.controlTokens.ShimmerShape
import com.microsoft.fluentui.tokenized.progress.CircularProgressIndicator
import com.microsoft.fluentui.tokenized.progress.LinearProgressIndicator
import com.microsoft.fluentui.tokenized.progress.Shimmer
import com.microsoft.fluentui.tokenized.shimmer.Shimmer
import com.microsoft.fluentuidemo.DemoActivity
import com.microsoft.fluentuidemo.databinding.V2ActivityComposeBinding
import kotlinx.coroutines.delay

Просмотреть файл

@ -87,7 +87,7 @@ class V2ScaffoldActivity : DemoActivity() {
invokeToast(resources.getString(R.string.tabBar_mail), context)
selectedIndex = 1
},
badge = { Badge("123+", badgeType = BadgeType.Character) }
badge = { Badge(text = "123+", badgeType = BadgeType.Character) }
),
TabData(
title = resources.getString(R.string.tabBar_settings),

Просмотреть файл

@ -65,7 +65,7 @@ class V2TabBarActivity : DemoActivity() {
invokeToast(resources.getString(R.string.tabBar_mail), context)
selectedIndex = 1
},
badge = { Badge("123+", badgeType = BadgeType.Character) }
badge = { Badge(text = "123+", badgeType = BadgeType.Character) }
),
TabData(
title = resources.getString(R.string.tabBar_settings),
@ -84,7 +84,7 @@ class V2TabBarActivity : DemoActivity() {
invokeToast(resources.getString(R.string.tabBar_notification), context)
selectedIndex = 3
},
badge = { Badge("10", badgeType = BadgeType.Character) }
badge = { Badge(text = "10", badgeType = BadgeType.Character) }
),
TabData(
title = resources.getString(R.string.tabBar_more),

Просмотреть файл

@ -10,7 +10,9 @@ import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Text
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.runtime.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
@ -41,9 +43,6 @@ import com.microsoft.fluentui.theme.token.controlTokens.ContextualCommandBarToke
import kotlinx.coroutines.launch
import kotlin.math.max
private val LocalContextualCommandBarTokens = compositionLocalOf { ContextualCommandBarTokens() }
private val LocalContextualCommandBarInfo = compositionLocalOf { ContextualCommandBarInfo() }
enum class ActionButtonPosition {
None,
Start,
@ -86,228 +85,90 @@ fun ContextualCommandBar(
val token = contextualCommandBarToken
?: FluentTheme.controlTokens.tokens[ControlTokens.ControlType.ContextualCommandBar] as ContextualCommandBarTokens
CompositionLocalProvider(
LocalContextualCommandBarTokens provides token,
LocalContextualCommandBarInfo provides ContextualCommandBarInfo()
) {
val groupBorderRadius =
getContextualCommandBarTokens().groupBorderRadius(getContextualCommandBarInfo())
val itemBorderRadius =
getContextualCommandBarTokens().itemBorderRadius(getContextualCommandBarInfo())
val contextualCommandBarInfo = ContextualCommandBarInfo()
val groupBorderRadius =
token.groupBorderRadius(contextualCommandBarInfo)
val itemBorderRadius =
token.itemBorderRadius(contextualCommandBarInfo)
val soloItemShape = RoundedCornerShape(groupBorderRadius)
val startShape = RoundedCornerShape(
topStart = groupBorderRadius,
bottomStart = groupBorderRadius,
topEnd = itemBorderRadius,
bottomEnd = itemBorderRadius
)
val defaultShape = RoundedCornerShape(itemBorderRadius)
val endShape = RoundedCornerShape(
topEnd = groupBorderRadius,
bottomEnd = groupBorderRadius,
topStart = itemBorderRadius,
bottomStart = itemBorderRadius
)
val focusStroke = getContextualCommandBarTokens().focusStroke(
getContextualCommandBarInfo()
)
var focusedBorderModifier: Modifier = Modifier
val soloItemShape = RoundedCornerShape(groupBorderRadius)
val startShape = RoundedCornerShape(
topStart = groupBorderRadius,
bottomStart = groupBorderRadius,
topEnd = itemBorderRadius,
bottomEnd = itemBorderRadius
)
val defaultShape = RoundedCornerShape(itemBorderRadius)
val endShape = RoundedCornerShape(
topEnd = groupBorderRadius,
bottomEnd = groupBorderRadius,
topStart = itemBorderRadius,
bottomStart = itemBorderRadius
)
val focusStroke = token.focusStroke(
contextualCommandBarInfo
)
var focusedBorderModifier: Modifier = Modifier
val selectedString = LocalContext.current.resources.getString(R.string.fluentui_selected)
val selectedString = LocalContext.current.resources.getString(R.string.fluentui_selected)
val lazyListState = rememberLazyListState()
val scope = rememberCoroutineScope()
val lazyListState = rememberLazyListState()
val scope = rememberCoroutineScope()
ConstraintLayout(
modifier = modifier
.focusable(enabled = false)
.fillMaxWidth()
.background(
getContextualCommandBarTokens().contextualCommandBarBackgroundColor(
getContextualCommandBarInfo()
)
ConstraintLayout(
modifier = modifier
.focusable(enabled = false)
.fillMaxWidth()
.background(
token.contextualCommandBarBackgroundColor(
contextualCommandBarInfo
)
) {
val (KeyboardDismiss, Content) = createRefs()
val contentPaddingWithActionButton =
-(getContextualCommandBarTokens().actionButtonGradientWidth(
getContextualCommandBarInfo()
) + getContextualCommandBarTokens().buttonPadding(getContextualCommandBarInfo()))
if (scrollable) {
LazyRow(modifier = Modifier
.focusable(enabled = false)
.constrainAs(Content) {
when (actionButtonPosition) {
ActionButtonPosition.Start -> {
start.linkTo(
KeyboardDismiss.end,
margin = contentPaddingWithActionButton
)
end.linkTo(parent.end)
}
ActionButtonPosition.End -> {
start.linkTo(parent.start)
end.linkTo(
KeyboardDismiss.start,
margin = contentPaddingWithActionButton
)
}
ActionButtonPosition.None -> {
start.linkTo(parent.start)
end.linkTo(parent.end)
}
)
) {
val (KeyboardDismiss, Content) = createRefs()
val contentPaddingWithActionButton =
-(token.actionButtonGradientWidth(
contextualCommandBarInfo
) + token.buttonPadding(contextualCommandBarInfo))
if (scrollable) {
LazyRow(modifier = Modifier
.focusable(enabled = false)
.constrainAs(Content) {
when (actionButtonPosition) {
ActionButtonPosition.Start -> {
start.linkTo(
KeyboardDismiss.end,
margin = contentPaddingWithActionButton
)
end.linkTo(parent.end)
}
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
width = Dimension.fillToConstraints
}
.padding(
getContextualCommandBarTokens().buttonPadding(
getContextualCommandBarInfo()
)
), state = lazyListState) {
var itemKey = 0
for ((index, commandGroup) in groups.withIndex()) {
for ((itemIndex, item) in commandGroup.items.withIndex()) {
val key = itemKey.toString()
item(key) {
val shape = if (commandGroup.items.size == 1) soloItemShape
else if (item == commandGroup.items.first()) startShape
else if (item == commandGroup.items.last()) endShape
else defaultShape
val interactionSource: MutableInteractionSource =
remember { MutableInteractionSource() }
val clickableModifier = Modifier.combinedClickable(
enabled = item.enabled,
onClick = item.onClick,
onClickLabel = null,
onLongClick = item.onLongClick,
role = Role.Button,
interactionSource = interactionSource,
indication = rememberRipple()
)
focusedBorderModifier = Modifier
for (borderStroke in focusStroke) {
focusedBorderModifier =
focusedBorderModifier.border(borderStroke, shape)
}
Row(
modifier = Modifier.height(IntrinsicSize.Min),
verticalAlignment = Alignment.CenterVertically,
) {
Box(
Modifier
.onFocusEvent { focusState ->
if (focusState.isFocused) {
scope.launch {
lazyListState.animateScrollToItem(
max(
0, key.toInt() - 2
)
)
}
}
}
.defaultMinSize(
minWidth = getContextualCommandBarTokens().buttonMinWidth(
getContextualCommandBarInfo()
)
)
.height(IntrinsicSize.Min)
.clip(shape)
.background(
getContextualCommandBarTokens()
.buttonBackgroundColor(
getContextualCommandBarInfo()
)
.getColorByState(
enabled = item.enabled,
selected = item.selected,
interactionSource = interactionSource
), shape = shape
)
.then(clickableModifier)
.then(if (interactionSource.collectIsFocusedAsState().value || interactionSource.collectIsHoveredAsState().value) focusedBorderModifier else Modifier)
.semantics {
contentDescription =
item.label +
if (item.selected)
selectedString
else ""
}, contentAlignment = Alignment.Center
) {
CommandItemComposable(item, interactionSource, commandGroup)
}
if (itemIndex != commandGroup.items.size - 1) {
Spacer(
Modifier
.requiredWidth(
getContextualCommandBarTokens().buttonSpacing(
getContextualCommandBarInfo()
)
)
.fillMaxHeight()
.background(Color.Transparent)
)
} else if (index != groups.size - 1) {
Spacer(
Modifier
.requiredWidth(
getContextualCommandBarTokens().groupSpacing(
getContextualCommandBarInfo()
)
)
.fillMaxHeight()
.background(Color.Transparent)
)
}
}
}
itemKey += 1
ActionButtonPosition.End -> {
start.linkTo(parent.start)
end.linkTo(
KeyboardDismiss.start,
margin = contentPaddingWithActionButton
)
}
ActionButtonPosition.None -> {
start.linkTo(parent.start)
end.linkTo(parent.end)
}
}
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
width = Dimension.fillToConstraints
}
} else {
Row(modifier = Modifier
.constrainAs(Content) {
when (actionButtonPosition) {
ActionButtonPosition.Start -> {
start.linkTo(
KeyboardDismiss.end,
margin = contentPaddingWithActionButton
)
end.linkTo(parent.end)
}
ActionButtonPosition.End -> {
start.linkTo(parent.start)
end.linkTo(
KeyboardDismiss.start,
margin = contentPaddingWithActionButton
)
}
ActionButtonPosition.None -> {
start.linkTo(parent.start)
end.linkTo(parent.end)
}
}
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
width = Dimension.fillToConstraints
}
.fillMaxWidth()
.padding(
getContextualCommandBarTokens().buttonPadding(
getContextualCommandBarInfo()
)
)) {
for ((index, commandGroup) in groups.withIndex()) {
for ((itemIndex, item) in commandGroup.items.withIndex()) {
.padding(
token.buttonPadding(
contextualCommandBarInfo
)
), state = lazyListState) {
var itemKey = 0
for ((index, commandGroup) in groups.withIndex()) {
for ((itemIndex, item) in commandGroup.items.withIndex()) {
val key = itemKey.toString()
item(key) {
val shape = if (commandGroup.items.size == 1) soloItemShape
else if (item == commandGroup.items.first()) startShape
else if (item == commandGroup.items.last()) endShape
@ -330,21 +191,35 @@ fun ContextualCommandBar(
focusedBorderModifier =
focusedBorderModifier.border(borderStroke, shape)
}
Row(
modifier = Modifier
.height(IntrinsicSize.Min)
.weight(commandGroup.weight),
verticalAlignment = Alignment.CenterVertically
modifier = Modifier.height(IntrinsicSize.Min),
verticalAlignment = Alignment.CenterVertically,
) {
Box(
Modifier
.onFocusEvent { focusState ->
if (focusState.isFocused) {
scope.launch {
lazyListState.animateScrollToItem(
max(
0, key.toInt() - 2
)
)
}
}
}
.defaultMinSize(
minWidth = token.buttonMinWidth(
contextualCommandBarInfo
)
)
.height(IntrinsicSize.Min)
.fillMaxWidth()
.clip(shape)
.background(
getContextualCommandBarTokens()
token
.buttonBackgroundColor(
getContextualCommandBarInfo()
contextualCommandBarInfo
)
.getColorByState(
enabled = item.enabled,
@ -356,124 +231,252 @@ fun ContextualCommandBar(
.then(if (interactionSource.collectIsFocusedAsState().value || interactionSource.collectIsHoveredAsState().value) focusedBorderModifier else Modifier)
.semantics {
contentDescription =
item.label + if (item.selected) selectedString else ""
item.label +
if (item.selected)
selectedString
else ""
}, contentAlignment = Alignment.Center
) {
CommandItemComposable(
item = item,
interactionSource = interactionSource,
commandGroup = commandGroup
token,
contextualCommandBarInfo,
item,
interactionSource,
commandGroup
)
}
if (itemIndex != commandGroup.items.size - 1) {
Spacer(
Modifier
.requiredWidth(
token.buttonSpacing(
contextualCommandBarInfo
)
)
.fillMaxHeight()
.background(Color.Transparent)
)
} else if (index != groups.size - 1) {
Spacer(
Modifier
.requiredWidth(
token.groupSpacing(
contextualCommandBarInfo
)
)
.fillMaxHeight()
.background(Color.Transparent)
)
}
}
}
if (itemIndex != commandGroup.items.size - 1) {
Spacer(
Modifier
.requiredWidth(
getContextualCommandBarTokens().buttonSpacing(
getContextualCommandBarInfo()
}
itemKey += 1
}
}
}
} else {
Row(modifier = Modifier
.constrainAs(Content) {
when (actionButtonPosition) {
ActionButtonPosition.Start -> {
start.linkTo(
KeyboardDismiss.end,
margin = contentPaddingWithActionButton
)
end.linkTo(parent.end)
}
ActionButtonPosition.End -> {
start.linkTo(parent.start)
end.linkTo(
KeyboardDismiss.start,
margin = contentPaddingWithActionButton
)
}
ActionButtonPosition.None -> {
start.linkTo(parent.start)
end.linkTo(parent.end)
}
}
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
width = Dimension.fillToConstraints
}
.fillMaxWidth()
.padding(
token.buttonPadding(
contextualCommandBarInfo
)
)) {
for ((index, commandGroup) in groups.withIndex()) {
for ((itemIndex, item) in commandGroup.items.withIndex()) {
val shape = if (commandGroup.items.size == 1) soloItemShape
else if (item == commandGroup.items.first()) startShape
else if (item == commandGroup.items.last()) endShape
else defaultShape
val interactionSource: MutableInteractionSource =
remember { MutableInteractionSource() }
val clickableModifier = Modifier.combinedClickable(
enabled = item.enabled,
onClick = item.onClick,
onClickLabel = null,
onLongClick = item.onLongClick,
role = Role.Button,
interactionSource = interactionSource,
indication = rememberRipple()
)
focusedBorderModifier = Modifier
for (borderStroke in focusStroke) {
focusedBorderModifier =
focusedBorderModifier.border(borderStroke, shape)
}
Row(
modifier = Modifier
.height(IntrinsicSize.Min)
.weight(commandGroup.weight),
verticalAlignment = Alignment.CenterVertically
) {
Box(
Modifier
.height(IntrinsicSize.Min)
.fillMaxWidth()
.clip(shape)
.background(
token
.buttonBackgroundColor(
contextualCommandBarInfo
)
)
.background(Color.Transparent)
)
} else if (index != groups.size - 1) {
Spacer(
Modifier
.requiredWidth(
getContextualCommandBarTokens().groupSpacing(
getContextualCommandBarInfo()
)
)
.background(Color.Transparent)
.getColorByState(
enabled = item.enabled,
selected = item.selected,
interactionSource = interactionSource
), shape = shape
)
.then(clickableModifier)
.then(if (interactionSource.collectIsFocusedAsState().value || interactionSource.collectIsHoveredAsState().value) focusedBorderModifier else Modifier)
.semantics {
contentDescription =
item.label + if (item.selected) selectedString else ""
}, contentAlignment = Alignment.Center
) {
CommandItemComposable(
token,
contextualCommandBarInfo,
item = item,
interactionSource = interactionSource,
commandGroup = commandGroup
)
}
}
if (itemIndex != commandGroup.items.size - 1) {
Spacer(
Modifier
.requiredWidth(
token.buttonSpacing(
contextualCommandBarInfo
)
)
.background(Color.Transparent)
)
} else if (index != groups.size - 1) {
Spacer(
Modifier
.requiredWidth(
token.groupSpacing(
contextualCommandBarInfo
)
)
.background(Color.Transparent)
)
}
}
}
}
if (actionButtonPosition != ActionButtonPosition.None) {
val keyboardController = LocalSoftwareKeyboardController.current
val keyboardDismiss: (() -> Unit) = { keyboardController?.hide() }
val actionButtonClickable = Modifier.clickable(enabled = true,
onClick = actionButtonIcon.onClick ?: keyboardDismiss,
role = Role.Button,
onClickLabel = actionButtonIcon.contentDescription,
indication = rememberRipple(),
interactionSource = remember { MutableInteractionSource() })
}
if (actionButtonPosition != ActionButtonPosition.None) {
val keyboardController = LocalSoftwareKeyboardController.current
val keyboardDismiss: (() -> Unit) = { keyboardController?.hide() }
val actionButtonClickable = Modifier.clickable(enabled = true,
onClick = actionButtonIcon.onClick ?: keyboardDismiss,
role = Role.Button,
onClickLabel = actionButtonIcon.contentDescription,
indication = rememberRipple(),
interactionSource = remember { MutableInteractionSource() })
val isRtl: Boolean = LocalLayoutDirection.current == LayoutDirection.Rtl
val isRtl: Boolean = LocalLayoutDirection.current == LayoutDirection.Rtl
Row(
Modifier
.height(IntrinsicSize.Min)
.constrainAs(KeyboardDismiss) {
if (actionButtonPosition == ActionButtonPosition.Start) {
start.linkTo(parent.start)
} else {
end.linkTo(parent.end)
}
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
}, verticalAlignment = Alignment.CenterVertically
) {
if (actionButtonPosition == ActionButtonPosition.End)
Spacer(
modifier = Modifier
.requiredWidth(
getContextualCommandBarTokens().actionButtonGradientWidth(
getContextualCommandBarInfo()
)
)
.fillMaxHeight()
.background(
Brush.horizontalGradient(
getContextualCommandBarTokens().actionButtonGradient(
getContextualCommandBarInfo()
),
startX = if (!isRtl) 0.0F else Float.POSITIVE_INFINITY,
endX = if (!isRtl) Float.POSITIVE_INFINITY else 0.0F
)
)
)
Icon(
actionButtonIcon,
Row(
Modifier
.height(IntrinsicSize.Min)
.constrainAs(KeyboardDismiss) {
if (actionButtonPosition == ActionButtonPosition.Start) {
start.linkTo(parent.start)
} else {
end.linkTo(parent.end)
}
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
}, verticalAlignment = Alignment.CenterVertically
) {
if (actionButtonPosition == ActionButtonPosition.End)
Spacer(
modifier = Modifier
.then(actionButtonClickable)
.background(
getContextualCommandBarTokens().actionButtonBackgroundColor(
getContextualCommandBarInfo()
.requiredWidth(
token.actionButtonGradientWidth(
contextualCommandBarInfo
)
)
.padding(
getContextualCommandBarTokens().actionButtonIconPadding(
getContextualCommandBarInfo()
.fillMaxHeight()
.background(
Brush.horizontalGradient(
token.actionButtonGradient(
contextualCommandBarInfo
),
startX = if (!isRtl) 0.0F else Float.POSITIVE_INFINITY,
endX = if (!isRtl) Float.POSITIVE_INFINITY else 0.0F
)
),
tint = getContextualCommandBarTokens().actionButtonIconColor(
getContextualCommandBarInfo()
)
)
)
if (actionButtonPosition == ActionButtonPosition.Start)
Spacer(
modifier = Modifier
.requiredWidth(
getContextualCommandBarTokens().actionButtonGradientWidth(
getContextualCommandBarInfo()
)
)
.fillMaxHeight()
.background(
Brush.horizontalGradient(
getContextualCommandBarTokens().actionButtonGradient(
getContextualCommandBarInfo()
),
startX = if (!isRtl) Float.POSITIVE_INFINITY else 0F,
endX = if (!isRtl) 0F else Float.POSITIVE_INFINITY
)
)
Icon(
actionButtonIcon,
modifier = Modifier
.then(actionButtonClickable)
.background(
token.actionButtonBackgroundColor(
contextualCommandBarInfo
)
)
}
.padding(
token.actionButtonIconPadding(
contextualCommandBarInfo
)
),
tint = token.actionButtonIconColor(
contextualCommandBarInfo
)
)
if (actionButtonPosition == ActionButtonPosition.Start)
Spacer(
modifier = Modifier
.requiredWidth(
token.actionButtonGradientWidth(
contextualCommandBarInfo
)
)
.fillMaxHeight()
.background(
Brush.horizontalGradient(
token.actionButtonGradient(
contextualCommandBarInfo
),
startX = if (!isRtl) Float.POSITIVE_INFINITY else 0F,
endX = if (!isRtl) 0F else Float.POSITIVE_INFINITY
)
)
)
}
}
}
@ -481,60 +484,64 @@ fun ContextualCommandBar(
@Composable
private fun CommandItemComposable(
item: CommandItem, interactionSource: MutableInteractionSource, commandGroup: CommandGroup
token: ContextualCommandBarTokens,
contextualCommandBarInfo: ContextualCommandBarInfo,
item: CommandItem,
interactionSource: MutableInteractionSource,
commandGroup: CommandGroup
) {
val foregroundColor = getContextualCommandBarTokens().iconColor(
getContextualCommandBarInfo()
val foregroundColor = token.iconColor(
contextualCommandBarInfo
).getColorByState(
enabled = item.enabled, selected = item.selected, interactionSource = interactionSource
)
val contentPadding: PaddingValues = if (commandGroup.items.size == 1) {
PaddingValues(
top = getContextualCommandBarTokens().iconVerticalPadding(
getContextualCommandBarInfo()
), bottom = getContextualCommandBarTokens().iconVerticalPadding(
getContextualCommandBarInfo()
), start = getContextualCommandBarTokens().groupIconHorizontalPadding(
getContextualCommandBarInfo()
), end = getContextualCommandBarTokens().groupIconHorizontalPadding(
getContextualCommandBarInfo()
top = token.iconVerticalPadding(
contextualCommandBarInfo
), bottom = token.iconVerticalPadding(
contextualCommandBarInfo
), start = token.groupIconHorizontalPadding(
contextualCommandBarInfo
), end = token.groupIconHorizontalPadding(
contextualCommandBarInfo
)
)
} else if (item == commandGroup.items.first()) {
PaddingValues(
top = getContextualCommandBarTokens().iconVerticalPadding(
getContextualCommandBarInfo()
), bottom = getContextualCommandBarTokens().iconVerticalPadding(
getContextualCommandBarInfo()
), start = getContextualCommandBarTokens().groupIconHorizontalPadding(
getContextualCommandBarInfo()
), end = getContextualCommandBarTokens().itemIconHorizontalPadding(
getContextualCommandBarInfo()
top = token.iconVerticalPadding(
contextualCommandBarInfo
), bottom = token.iconVerticalPadding(
contextualCommandBarInfo
), start = token.groupIconHorizontalPadding(
contextualCommandBarInfo
), end = token.itemIconHorizontalPadding(
contextualCommandBarInfo
)
)
} else if (item == commandGroup.items.last()) {
PaddingValues(
top = getContextualCommandBarTokens().iconVerticalPadding(
getContextualCommandBarInfo()
), bottom = getContextualCommandBarTokens().iconVerticalPadding(
getContextualCommandBarInfo()
), start = getContextualCommandBarTokens().itemIconHorizontalPadding(
getContextualCommandBarInfo()
), end = getContextualCommandBarTokens().groupIconHorizontalPadding(
getContextualCommandBarInfo()
top = token.iconVerticalPadding(
contextualCommandBarInfo
), bottom = token.iconVerticalPadding(
contextualCommandBarInfo
), start = token.itemIconHorizontalPadding(
contextualCommandBarInfo
), end = token.groupIconHorizontalPadding(
contextualCommandBarInfo
)
)
} else {
PaddingValues(
top = getContextualCommandBarTokens().iconVerticalPadding(
getContextualCommandBarInfo()
), bottom = getContextualCommandBarTokens().iconVerticalPadding(
getContextualCommandBarInfo()
), start = getContextualCommandBarTokens().itemIconHorizontalPadding(
getContextualCommandBarInfo()
), end = getContextualCommandBarTokens().itemIconHorizontalPadding(
getContextualCommandBarInfo()
top = token.iconVerticalPadding(
contextualCommandBarInfo
), bottom = token.iconVerticalPadding(
contextualCommandBarInfo
), start = token.itemIconHorizontalPadding(
contextualCommandBarInfo
), end = token.itemIconHorizontalPadding(
contextualCommandBarInfo
)
)
}
@ -544,21 +551,21 @@ private fun CommandItemComposable(
Modifier
.padding(contentPadding)
.requiredSize(
getContextualCommandBarTokens().iconSize(
getContextualCommandBarInfo()
token.iconSize(
contextualCommandBarInfo
)
), tint = foregroundColor
)
} else {
val fontTypography = getContextualCommandBarTokens().typography(
getContextualCommandBarInfo()
val fontTypography = token.typography(
contextualCommandBarInfo
)
Box(
modifier = Modifier
.padding(contentPadding)
.requiredHeight(
getContextualCommandBarTokens().iconSize(
getContextualCommandBarInfo()
token.iconSize(
contextualCommandBarInfo
)
), contentAlignment = Alignment.Center
) {
@ -574,16 +581,6 @@ private fun CommandItemComposable(
}
}
@Composable
private fun getContextualCommandBarTokens(): ContextualCommandBarTokens {
return LocalContextualCommandBarTokens.current
}
@Composable
private fun getContextualCommandBarInfo(): ContextualCommandBarInfo {
return LocalContextualCommandBarInfo.current
}
data class CommandGroup(
val groupName: String,
val items: List<CommandItem>,

Просмотреть файл

@ -11,8 +11,6 @@ import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -32,9 +30,6 @@ import com.microsoft.fluentui.theme.token.controlTokens.ButtonSize
import com.microsoft.fluentui.theme.token.controlTokens.ButtonStyle
import com.microsoft.fluentui.theme.token.controlTokens.ButtonTokens
val LocalButtonTokens = compositionLocalOf { ButtonTokens() }
val LocalButtonInfo = compositionLocalOf { ButtonInfo() }
/**
* API to create a button, containing icon as well as text.
*
@ -64,112 +59,97 @@ fun Button(
buttonTokens: ButtonTokens? = null
) {
val token = buttonTokens ?: FluentTheme.controlTokens.tokens[ControlType.Button] as ButtonTokens
CompositionLocalProvider(
LocalButtonTokens provides token,
LocalButtonInfo provides ButtonInfo(style, size)
) {
val clickAndSemanticsModifier = Modifier.clickable(
interactionSource = interactionSource,
indication = rememberRipple(),
val buttonInfo = ButtonInfo(style, size)
val clickAndSemanticsModifier = Modifier.clickable(
interactionSource = interactionSource,
indication = rememberRipple(),
enabled = enabled,
onClickLabel = null,
role = Role.Button,
onClick = onClick
)
val backgroundColor =
token.backgroundColor(buttonInfo = buttonInfo).getColorByState(
enabled = enabled,
onClickLabel = null,
role = Role.Button,
onClick = onClick
selected = false,
interactionSource = interactionSource
)
val backgroundColor =
getButtonToken().backgroundColor(buttonInfo = getButtonInfo()).getColorByState(
enabled = enabled,
selected = false,
interactionSource = interactionSource
val contentPadding = token.padding(buttonInfo)
val iconSpacing = token.spacing(buttonInfo)
val shape = RoundedCornerShape(token.cornerRadius(buttonInfo))
val borders: List<BorderStroke> =
token.borderStroke(buttonInfo = buttonInfo).getBorderStrokeByState(
enabled = enabled,
selected = false,
interactionSource = interactionSource
)
var borderModifier: Modifier = Modifier
var borderWidth = 0.dp
for (border in borders) {
borderWidth += border.width
borderModifier = borderModifier.border(borderWidth, border.brush, shape)
}
Box(
modifier
.height(token.fixedHeight(buttonInfo))
.background(
color = backgroundColor,
shape = shape
)
val contentPadding = getButtonToken().padding(getButtonInfo())
val iconSpacing = getButtonToken().spacing(getButtonInfo())
val shape = RoundedCornerShape(getButtonToken().cornerRadius(getButtonInfo()))
val borders: List<BorderStroke> =
getButtonToken().borderStroke(buttonInfo = getButtonInfo()).getBorderStrokeByState(
enabled = enabled,
selected = false,
interactionSource = interactionSource
)
var borderModifier: Modifier = Modifier
var borderWidth = 0.dp
for (border in borders) {
borderWidth += border.width
borderModifier = borderModifier.border(borderWidth, border.brush, shape)
}
Box(
modifier
.height(getButtonToken().fixedHeight(getButtonInfo()))
.background(
color = backgroundColor,
shape = shape
)
.clip(shape)
.semantics(true) {
editableText = AnnotatedString(text ?: "")
this.contentDescription = contentDescription ?: ""
}
.then(clickAndSemanticsModifier)
.then(borderModifier),
propagateMinConstraints = true
) {
Row(
Modifier.padding(contentPadding),
horizontalArrangement = Arrangement.spacedBy(
iconSpacing,
Alignment.CenterHorizontally
),
verticalAlignment = Alignment.CenterVertically
) {
if (icon != null)
Icon(
imageVector = icon,
contentDescription = null,
modifier = Modifier
.size(
getButtonToken().iconSize(buttonInfo = getButtonInfo())
),
tint = getButtonToken().iconColor(buttonInfo = getButtonInfo())
.getColorByState(
enabled = enabled,
selected = false,
interactionSource = interactionSource
)
)
if (text != null)
Text(
text = text,
modifier = Modifier.clearAndSetSemantics { },
color = getButtonToken().textColor(buttonInfo = getButtonInfo())
.getColorByState(
enabled = enabled,
selected = false,
interactionSource = interactionSource
),
style = getButtonToken().typography(getButtonInfo()).merge(
TextStyle(
platformStyle = PlatformTextStyle(includeFontPadding = false)
)
),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
.clip(shape)
.semantics(true) {
editableText = AnnotatedString(text ?: "")
this.contentDescription = contentDescription ?: ""
}
.then(clickAndSemanticsModifier)
.then(borderModifier),
propagateMinConstraints = true
) {
Row(
Modifier.padding(contentPadding),
horizontalArrangement = Arrangement.spacedBy(
iconSpacing,
Alignment.CenterHorizontally
),
verticalAlignment = Alignment.CenterVertically
) {
if (icon != null)
Icon(
imageVector = icon,
contentDescription = null,
modifier = Modifier
.size(
token.iconSize(buttonInfo = buttonInfo)
),
tint = token.iconColor(buttonInfo = buttonInfo)
.getColorByState(
enabled = enabled,
selected = false,
interactionSource = interactionSource
)
)
if (text != null)
Text(
text = text,
modifier = Modifier.clearAndSetSemantics { },
color = token.textColor(buttonInfo = buttonInfo)
.getColorByState(
enabled = enabled,
selected = false,
interactionSource = interactionSource
),
style = token.typography(buttonInfo).merge(
TextStyle(
platformStyle = PlatformTextStyle(includeFontPadding = false)
)
),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
}
@Composable
fun getButtonToken(): ButtonTokens {
return LocalButtonTokens.current
}
@Composable
fun getButtonInfo(): ButtonInfo {
return LocalButtonInfo.current
}

Просмотреть файл

@ -15,8 +15,6 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Done
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -32,9 +30,6 @@ import com.microsoft.fluentui.theme.token.ControlTokens.ControlType
import com.microsoft.fluentui.theme.token.controlTokens.CheckBoxInfo
import com.microsoft.fluentui.theme.token.controlTokens.CheckBoxTokens
val LocalCheckBoxTokens = compositionLocalOf { CheckBoxTokens() }
val LocalCheckBoxInfo = compositionLocalOf { CheckBoxInfo() }
/**
* API to create a checkbox. A checkbox is a type of button that lets the user choose between two opposite states,
* actions, or values. A selected checkbox is considered on when it contains a checkmark and off when
@ -59,86 +54,70 @@ fun CheckBox(
val token = checkBoxToken
?: FluentTheme.controlTokens.tokens[ControlType.CheckBox] as CheckBoxTokens
CompositionLocalProvider(
LocalCheckBoxTokens provides token,
LocalCheckBoxInfo provides CheckBoxInfo(checked)
) {
val toggleModifier =
modifier.triStateToggleable(
state = ToggleableState(checked),
enabled = enabled,
onClick = { onCheckedChanged(!checked) },
role = Role.Checkbox,
interactionSource = interactionSource,
indication = rememberRipple(
bounded = false,
radius = 24.dp
)
val checkBoxInfo = CheckBoxInfo(checked)
val toggleModifier =
modifier.triStateToggleable(
state = ToggleableState(checked),
enabled = enabled,
onClick = { onCheckedChanged(!checked) },
role = Role.Checkbox,
interactionSource = interactionSource,
indication = rememberRipple(
bounded = false,
radius = 24.dp
)
)
val backgroundColor: Color =
getCheckBoxToken().backgroundColor(checkBoxInfo = getCheckBoxInfo()).getColorByState(
val backgroundColor: Color =
token.backgroundColor(checkBoxInfo = checkBoxInfo).getColorByState(
enabled = enabled,
selected = checked,
interactionSource = interactionSource
)
val iconColor: Color =
token.iconColor(checkBoxInfo = checkBoxInfo).getColorByState(
enabled = enabled,
selected = checked,
interactionSource = interactionSource
)
val shape: Shape = RoundedCornerShape(token.fixedBorderRadius)
val borders: List<BorderStroke> =
token.borderStroke(checkBoxInfo = checkBoxInfo)
.getBorderStrokeByState(
enabled = enabled,
selected = checked,
interactionSource = interactionSource
)
val iconColor: Color =
getCheckBoxToken().iconColor(checkBoxInfo = getCheckBoxInfo()).getColorByState(
enabled = enabled,
selected = checked,
interactionSource = interactionSource
)
val shape: Shape = RoundedCornerShape(getCheckBoxToken().fixedBorderRadius)
val borders: List<BorderStroke> =
getCheckBoxToken().borderStroke(checkBoxInfo = getCheckBoxInfo())
.getBorderStrokeByState(
enabled = enabled,
selected = checked,
interactionSource = interactionSource
)
var borderModifier: Modifier = Modifier
var borderWidth = 0.dp
for (border in borders) {
borderWidth += border.width
borderModifier = borderModifier.border(borderWidth, border.brush, shape)
}
Box(
modifier = Modifier.indication(interactionSource, null),
contentAlignment = Alignment.Center
) {
Spacer(
modifier = Modifier
.size(getCheckBoxToken().fixedSize)
.clip(shape)
.background(backgroundColor)
.then(borderModifier)
.then(toggleModifier)
)
AnimatedVisibility(checked, enter = fadeIn(), exit = fadeOut()) {
Icon(
Icons.Filled.Done,
null,
modifier = Modifier
.size(getCheckBoxToken().fixedIconSize)
.focusable(false)
.clearAndSetSemantics {},
tint = iconColor
)
}
}
var borderModifier: Modifier = Modifier
var borderWidth = 0.dp
for (border in borders) {
borderWidth += border.width
borderModifier = borderModifier.border(borderWidth, border.brush, shape)
}
}
@Composable
fun getCheckBoxToken(): CheckBoxTokens {
return LocalCheckBoxTokens.current
}
@Composable
fun getCheckBoxInfo(): CheckBoxInfo {
return LocalCheckBoxInfo.current
Box(
modifier = Modifier.indication(interactionSource, null),
contentAlignment = Alignment.Center
) {
Spacer(
modifier = Modifier
.size(token.fixedSize)
.clip(shape)
.background(backgroundColor)
.then(borderModifier)
.then(toggleModifier)
)
AnimatedVisibility(checked, enter = fadeIn(), exit = fadeOut()) {
Icon(
Icons.Filled.Done,
null,
modifier = Modifier
.size(token.fixedIconSize)
.focusable(false)
.clearAndSetSemantics {},
tint = iconColor
)
}
}
}

Просмотреть файл

@ -8,8 +8,6 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -29,9 +27,6 @@ import com.microsoft.fluentui.theme.token.controlTokens.FABSize
import com.microsoft.fluentui.theme.token.controlTokens.FABState
import com.microsoft.fluentui.theme.token.controlTokens.FABTokens
val LocalFABTokens = compositionLocalOf { FABTokens() }
val LocalFABInfo = compositionLocalOf { FABInfo() }
/**
* API to create a Floating Action Button. This button has elevation and can be expanded and collapsed.
* In expanded state, Icon + Text are displayed. In collapsed state, only icon is displayed.
@ -64,118 +59,103 @@ fun FloatingActionButton(
val token = fabTokens
?: FluentTheme.controlTokens.tokens[ControlType.FloatingActionButton] as FABTokens
CompositionLocalProvider(
LocalFABTokens provides token,
LocalFABInfo provides FABInfo(state, size)
) {
val clickAndSemanticsModifier = Modifier.clickable(
interactionSource = interactionSource,
indication = LocalIndication.current,
enabled = enabled,
onClickLabel = null,
role = Role.Button,
onClick = onClick
)
val isFabExpanded: Boolean =
(text != null && text != "" && getFABInfo().state == FABState.Expanded)
val backgroundColor = getFABToken().backgroundColor(fabInfo = getFABInfo()).getColorByState(
val fabInfo = FABInfo(state, size)
val clickAndSemanticsModifier = Modifier.clickable(
interactionSource = interactionSource,
indication = LocalIndication.current,
enabled = enabled,
onClickLabel = null,
role = Role.Button,
onClick = onClick
)
val isFabExpanded: Boolean =
(text != null && text != "" && fabInfo.state == FABState.Expanded)
val backgroundColor = token.backgroundColor(fabInfo = fabInfo).getColorByState(
enabled = enabled,
selected = false,
interactionSource = interactionSource
)
val contentPadding = if (isFabExpanded) token.textPadding(fabInfo)
else token.iconPadding(fabInfo)
val iconSpacing = if (isFabExpanded) token.spacing(fabInfo) else 0.dp
val shape = CircleShape
val borders: List<BorderStroke> =
token.borderStroke(fabInfo = fabInfo).getBorderStrokeByState(
enabled = enabled,
selected = false,
interactionSource = interactionSource
)
val contentPadding = if (isFabExpanded) getFABToken().textPadding(getFABInfo())
else getFABToken().iconPadding(getFABInfo())
val iconSpacing = if (isFabExpanded) getFABToken().spacing(getFABInfo()) else 0.dp
val shape = CircleShape
val borders: List<BorderStroke> =
getFABToken().borderStroke(fabInfo = getFABInfo()).getBorderStrokeByState(
enabled = enabled,
selected = false,
interactionSource = interactionSource
var borderModifier: Modifier = Modifier
var borderWidth = 0.dp
for (border in borders) {
borderWidth += border.width
borderModifier = borderModifier.border(borderWidth, border.brush, shape)
}
Box(
modifier
.height(token.fixedHeight(fabInfo))
.defaultMinSize(minWidth = token.minWidth(fabInfo))
.shadow(
elevation = token
.elevation(fabInfo = fabInfo)
.getElevationByState(
enabled = enabled,
selected = false,
interactionSource = interactionSource
),
shape = CircleShape
)
var borderModifier: Modifier = Modifier
var borderWidth = 0.dp
for (border in borders) {
borderWidth += border.width
borderModifier = borderModifier.border(borderWidth, border.brush, shape)
}
Box(
modifier
.height(getFABToken().fixedHeight(getFABInfo()))
.defaultMinSize(minWidth = getFABToken().minWidth(getFABInfo()))
.shadow(
elevation = getFABToken()
.elevation(fabInfo = getFABInfo())
.getElevationByState(
enabled = enabled,
selected = false,
interactionSource = interactionSource
),
shape = CircleShape
)
.background(
color = backgroundColor,
shape = shape
)
.clip(shape)
.semantics(mergeDescendants = true) { contentDescription = text ?: "" }
.then(clickAndSemanticsModifier)
.then(borderModifier),
propagateMinConstraints = true
.background(
color = backgroundColor,
shape = shape
)
.clip(shape)
.semantics(mergeDescendants = true) { contentDescription = text ?: "" }
.then(clickAndSemanticsModifier)
.then(borderModifier),
propagateMinConstraints = true
) {
Row(
Modifier.padding(contentPadding),
horizontalArrangement = Arrangement.spacedBy(
iconSpacing,
Alignment.CenterHorizontally
),
verticalAlignment = Alignment.CenterVertically
) {
Row(
Modifier.padding(contentPadding),
horizontalArrangement = Arrangement.spacedBy(
iconSpacing,
Alignment.CenterHorizontally
),
verticalAlignment = Alignment.CenterVertically
) {
if (icon != null)
Icon(
imageVector = icon,
contentDescription = text,
modifier = Modifier
.size(
getFABToken().iconSize(getFABInfo())
)
.clearAndSetSemantics { },
tint = getFABToken().iconColor(fabInfo = getFABInfo()).getColorByState(
enabled = enabled,
selected = false,
interactionSource = interactionSource
if (icon != null)
Icon(
imageVector = icon,
contentDescription = text,
modifier = Modifier
.size(
token.iconSize(fabInfo)
)
.clearAndSetSemantics { },
tint = token.iconColor(fabInfo = fabInfo).getColorByState(
enabled = enabled,
selected = false,
interactionSource = interactionSource
)
)
AnimatedVisibility(isFabExpanded) {
Text(
text = text!!,
modifier = Modifier.clearAndSetSemantics { },
style = getFABToken().typography(getFABInfo()),
color = getFABToken().textColor(fabInfo = getFABInfo()).getColorByState(
enabled = enabled,
selected = false,
interactionSource = interactionSource
),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
AnimatedVisibility(isFabExpanded) {
Text(
text = text!!,
modifier = Modifier.clearAndSetSemantics { },
style = token.typography(fabInfo),
color = token.textColor(fabInfo = fabInfo).getColorByState(
enabled = enabled,
selected = false,
interactionSource = interactionSource
),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
}
}
@Composable
fun getFABToken(): FABTokens {
return LocalFABTokens.current
}
@Composable
fun getFABInfo(): FABInfo {
return LocalFABInfo.current
}

Просмотреть файл

@ -9,8 +9,6 @@ import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.selection.selectable
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -27,9 +25,6 @@ import com.microsoft.fluentui.theme.token.ControlTokens
import com.microsoft.fluentui.theme.token.controlTokens.RadioButtonInfo
import com.microsoft.fluentui.theme.token.controlTokens.RadioButtonTokens
val LocalRadioButtonTokens = compositionLocalOf { RadioButtonTokens() }
val LocalRadioButtonInfo = compositionLocalOf { RadioButtonInfo() }
/**
* API to create a Radio Button. A Radio selection lets user choose one option out of all values.
* This API provides a single instance of radio button and not a group.
@ -52,73 +47,57 @@ fun RadioButton(
) {
val token = radioButtonToken
?: FluentTheme.controlTokens.tokens[ControlTokens.ControlType.RadioButton] as RadioButtonTokens
val radioButtonInfo = RadioButtonInfo(selected)
val dotRadius = animateDpAsState(
targetValue = if (selected) token.innerCircleRadius else 0.dp,
animationSpec = tween(durationMillis = 100)
)
CompositionLocalProvider(
LocalRadioButtonTokens provides token,
LocalRadioButtonInfo provides RadioButtonInfo(selected)
) {
val dotRadius = animateDpAsState(
targetValue = if (selected) getRadioButtonTokens().innerCircleRadius else 0.dp,
animationSpec = tween(durationMillis = 100)
val selectableModifier = modifier.selectable(
selected = selected,
enabled = enabled,
onClick = onClick,
role = Role.RadioButton,
interactionSource = interactionSource,
indication = rememberRipple(
bounded = false,
radius = 24.dp
)
)
val selectableModifier = modifier.selectable(
selected = selected,
enabled = enabled,
onClick = onClick,
role = Role.RadioButton,
interactionSource = interactionSource,
indication = rememberRipple(
bounded = false,
radius = 24.dp
)
)
val outerStrokeColor =
getRadioButtonTokens().backgroundColor(radioButtonInfo = getRadioButtonInfo())
.getColorByState(
enabled = enabled,
selected = selected,
interactionSource = interactionSource
)
val innerColor = getRadioButtonTokens().iconColor(radioButtonInfo = getRadioButtonInfo())
val outerStrokeColor =
token.backgroundColor(radioButtonInfo = radioButtonInfo)
.getColorByState(
enabled = enabled,
selected = selected,
interactionSource = interactionSource
)
val innerColor = token.iconColor(radioButtonInfo = radioButtonInfo)
.getColorByState(
enabled = enabled,
selected = selected,
interactionSource = interactionSource
)
val outerRadius = getRadioButtonTokens().outerCircleRadius
val strokeWidth = getRadioButtonTokens().strokeWidthInwards
val outerRadius = token.outerCircleRadius
val strokeWidth = token.strokeWidthInwards
val contentDesc = LocalContext.current.resources.getString(R.string.fluentui_radio_button)
Canvas(
modifier = Modifier
.then(selectableModifier)
.size(24.dp)
.wrapContentSize(Alignment.Center)
.semantics { contentDescription = contentDesc }
) {
drawCircle(
outerStrokeColor,
(outerRadius - (strokeWidth / 2)).toPx(),
style = Stroke(1.5.dp.toPx())
)
val contentDesc = LocalContext.current.resources.getString(R.string.fluentui_radio_button)
Canvas(
modifier = Modifier
.then(selectableModifier)
.size(24.dp)
.wrapContentSize(Alignment.Center)
.semantics { contentDescription = contentDesc }
) {
drawCircle(
outerStrokeColor,
(outerRadius - (strokeWidth / 2)).toPx(),
style = Stroke(1.5.dp.toPx())
)
if (dotRadius.value > 0.dp) {
drawCircle(innerColor, (dotRadius.value).toPx(), style = Fill)
}
if (dotRadius.value > 0.dp) {
drawCircle(innerColor, (dotRadius.value).toPx(), style = Fill)
}
}
}
@Composable
fun getRadioButtonTokens(): RadioButtonTokens {
return LocalRadioButtonTokens.current
}
@Composable
fun getRadioButtonInfo(): RadioButtonInfo {
return LocalRadioButtonInfo.current
}

Просмотреть файл

@ -35,9 +35,6 @@ import com.microsoft.fluentui.theme.token.controlTokens.ToggleSwitchInfo
import com.microsoft.fluentui.theme.token.controlTokens.ToggleSwitchTokens
import kotlin.math.roundToInt
val LocalToggleSwitchTokens = compositionLocalOf { ToggleSwitchTokens() }
val LocalToggleSwitchInfo = compositionLocalOf { ToggleSwitchInfo() }
/**
* API to create a Toggle Switch. This switch toggles state on tap and swipe gestures.
* The two states of toggle are mutually exclusive.
@ -62,128 +59,112 @@ fun ToggleSwitch(
val token = switchTokens
?: FluentTheme.controlTokens.tokens[ControlType.ToggleSwitch] as ToggleSwitchTokens
CompositionLocalProvider(
LocalToggleSwitchTokens provides token,
LocalToggleSwitchInfo provides ToggleSwitchInfo(checkedState)
) {
val backgroundColor: Color = animateColorAsState(
targetValue =
getToggleSwitchToken().trackColor(switchInfo = getToggleSwitchInfo()).getColorByState(
enabled = enabledSwitch,
selected = checkedState,
interactionSource = interactionSource
),
animationSpec = tween(500)
).value
val foregroundColor: Color =
getToggleSwitchToken().knobColor(switchInfo = getToggleSwitchInfo()).getColorByState(
enabled = enabledSwitch,
selected = checkedState,
interactionSource = interactionSource
)
val elevation: Dp =
getToggleSwitchToken().elevation(switchInfo = getToggleSwitchInfo())
.getElevationByState(
enabled = enabledSwitch,
selected = checkedState,
interactionSource = interactionSource
)
val padding: Dp = animateDpAsState(
targetValue =
if (interactionSource.collectIsPressedAsState().value)
getToggleSwitchToken().pressedPaddingTrack
else
getToggleSwitchToken().restPaddingTrack
).value
val knobMovementWidth: Dp = animateDpAsState(
targetValue =
if (interactionSource.collectIsPressedAsState().value)
22.dp
else
23.dp
).value
// Swipe Logic
val minBound = with(LocalDensity.current) { padding.toPx() }
val maxBound = with(LocalDensity.current) { knobMovementWidth.toPx() }
val animationSpec = TweenSpec<Float>(durationMillis = 300)
val swipeState =
rememberSwipeableState(checkedState, animationSpec, confirmStateChange = { true })
val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
val forceAnimationCheck = remember { mutableStateOf(false) }
LaunchedEffect(checkedState, forceAnimationCheck.value) {
if (checkedState != swipeState.currentValue) {
swipeState.animateTo(checkedState)
}
}
DisposableEffect(swipeState.currentValue) {
if (checkedState != swipeState.currentValue) {
onValueChange.invoke(swipeState.currentValue)
forceAnimationCheck.value = !forceAnimationCheck.value
}
onDispose { }
}
// Toggle Logic
val toggleModifier = modifier.toggleable(
value = getToggleSwitchInfo().checked,
val toggleSwitchInfo = ToggleSwitchInfo(checkedState)
val backgroundColor: Color = animateColorAsState(
targetValue =
token.trackColor(switchInfo = toggleSwitchInfo).getColorByState(
enabled = enabledSwitch,
role = Role.Switch,
onValueChange = onValueChange,
interactionSource = interactionSource,
indication = rememberRipple()
selected = checkedState,
interactionSource = interactionSource
),
animationSpec = tween(500)
).value
val foregroundColor: Color =
token.knobColor(switchInfo = toggleSwitchInfo).getColorByState(
enabled = enabledSwitch,
selected = checkedState,
interactionSource = interactionSource
)
val elevation: Dp =
token.elevation(switchInfo = toggleSwitchInfo)
.getElevationByState(
enabled = enabledSwitch,
selected = checkedState,
interactionSource = interactionSource
)
val padding: Dp = animateDpAsState(
targetValue =
if (interactionSource.collectIsPressedAsState().value)
token.pressedPaddingTrack
else
token.restPaddingTrack
).value
val knobMovementWidth: Dp = animateDpAsState(
targetValue =
if (interactionSource.collectIsPressedAsState().value)
22.dp
else
23.dp
).value
// UI Implementation
Box(
modifier = Modifier
.then(toggleModifier)
.swipeable(
state = swipeState,
anchors = mapOf(minBound to false, maxBound to true),
thresholds = { _, _ -> FractionalThreshold(0.5f) },
orientation = Orientation.Horizontal,
enabled = enabledSwitch,
reverseDirection = isRtl,
interactionSource = interactionSource,
resistance = null
), contentAlignment = Alignment.CenterStart
) {
Box(
modifier = Modifier
.width(getToggleSwitchToken().fixedTrackWidth)
.height(getToggleSwitchToken().fixedTrackHeight)
.clip(CircleShape)
.background(backgroundColor)
)
Spacer(modifier = Modifier
.offset { IntOffset(swipeState.offset.value.roundToInt(), 0) }
.shadow(elevation, CircleShape)
.size(
animateDpAsState(
targetValue =
if (interactionSource.collectIsPressedAsState().value)
getToggleSwitchToken().pressedKnobDiameter
else
getToggleSwitchToken().restKnobDiameter
).value
)
.clip(CircleShape)
.background(foregroundColor)
)
// Swipe Logic
val minBound = with(LocalDensity.current) { padding.toPx() }
val maxBound = with(LocalDensity.current) { knobMovementWidth.toPx() }
val animationSpec = TweenSpec<Float>(durationMillis = 300)
val swipeState =
rememberSwipeableState(checkedState, animationSpec, confirmStateChange = { true })
val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
val forceAnimationCheck = remember { mutableStateOf(false) }
LaunchedEffect(checkedState, forceAnimationCheck.value) {
if (checkedState != swipeState.currentValue) {
swipeState.animateTo(checkedState)
}
}
}
DisposableEffect(swipeState.currentValue) {
if (checkedState != swipeState.currentValue) {
onValueChange.invoke(swipeState.currentValue)
forceAnimationCheck.value = !forceAnimationCheck.value
}
onDispose { }
}
@Composable
fun getToggleSwitchToken(): ToggleSwitchTokens {
return LocalToggleSwitchTokens.current
}
// Toggle Logic
val toggleModifier = modifier.toggleable(
value = toggleSwitchInfo.checked,
enabled = enabledSwitch,
role = Role.Switch,
onValueChange = onValueChange,
interactionSource = interactionSource,
indication = rememberRipple()
)
@Composable
fun getToggleSwitchInfo(): ToggleSwitchInfo {
return LocalToggleSwitchInfo.current
// UI Implementation
Box(
modifier = Modifier
.then(toggleModifier)
.swipeable(
state = swipeState,
anchors = mapOf(minBound to false, maxBound to true),
thresholds = { _, _ -> FractionalThreshold(0.5f) },
orientation = Orientation.Horizontal,
enabled = enabledSwitch,
reverseDirection = isRtl,
interactionSource = interactionSource,
resistance = null
), contentAlignment = Alignment.CenterStart
) {
Box(
modifier = Modifier
.width(token.fixedTrackWidth)
.height(token.fixedTrackHeight)
.clip(CircleShape)
.background(backgroundColor)
)
Spacer(modifier = Modifier
.offset { IntOffset(swipeState.offset.value.roundToInt(), 0) }
.shadow(elevation, CircleShape)
.size(
animateDpAsState(
targetValue =
if (interactionSource.collectIsPressedAsState().value)
token.pressedKnobDiameter
else
token.restKnobDiameter
).value
)
.clip(CircleShape)
.background(foregroundColor)
)
}
}

Просмотреть файл

@ -6,6 +6,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp
import com.microsoft.fluentui.theme.FluentTheme
import com.microsoft.fluentui.theme.token.AliasTokens
import com.microsoft.fluentui.theme.token.ControlInfo
import com.microsoft.fluentui.theme.token.ControlToken
import com.microsoft.fluentui.theme.token.GlobalTokens
import kotlinx.parcelize.Parcelize
@ -14,23 +15,25 @@ enum class ShimmerShape {
Box,
Circle
}
data class ShimmerInfo(
val shape: ShimmerShape = ShimmerShape.Box
):ControlInfo
@Parcelize
open class ShimmerTokens : ControlToken, Parcelable {
@Composable
open fun cornerRadius(): Dp {
open fun cornerRadius(shimmerInfo: ShimmerInfo): Dp {
return GlobalTokens.cornerRadius(GlobalTokens.CornerRadiusTokens.CornerRadius40)
}
@Composable
open fun knockoutEffectColor(): Color {
open fun knockoutEffectColor(shimmerInfo: ShimmerInfo): Color {
return FluentTheme.aliasTokens.neutralBackgroundColor[AliasTokens.NeutralBackgroundColorTokens.Stencil2].value(
themeMode = FluentTheme.themeMode
)
}
@Composable
open fun color(): Color {
open fun color(shimmerInfo: ShimmerInfo): Color {
return FluentTheme.aliasTokens.neutralBackgroundColor[AliasTokens.NeutralBackgroundColorTokens.Stencil1].value(
themeMode = FluentTheme.themeMode
)

Просмотреть файл

@ -182,19 +182,6 @@ private const val BOTTOMSHEET_SCRIM_TAG = "BottomSheet Scrim"
private const val BottomSheetOpenFraction = 0.5f
internal val LocalBottomSheetTokens = compositionLocalOf { BottomSheetTokens() }
internal val LocalBottomSheetInfo = compositionLocalOf { BottomSheetInfo() }
@Composable
private fun getDrawerTokens(): BottomSheetTokens {
return LocalBottomSheetTokens.current
}
@Composable
private fun getBottomSheetInfo(): BottomSheetInfo {
return LocalBottomSheetInfo.current
}
/**
*
* Bottom sheets present a set of choices while blocking interaction with the rest of the
@ -238,197 +225,192 @@ fun BottomSheet(
val tokens = bottomSheetTokens
?: FluentTheme.controlTokens.tokens[ControlTokens.ControlType.BottomSheet] as BottomSheetTokens
CompositionLocalProvider(
LocalBottomSheetTokens provides tokens,
LocalBottomSheetInfo provides BottomSheetInfo()
) {
val bottomSheetInfo = BottomSheetInfo()
val sheetShape: Shape = RoundedCornerShape(
topStart = tokens.cornerRadius(bottomSheetInfo),
topEnd = tokens.cornerRadius(bottomSheetInfo)
)
val sheetElevation: Dp = tokens.elevation(bottomSheetInfo)
val sheetBackgroundColor: Color = tokens.backgroundColor(bottomSheetInfo)
val sheetContentColor: Color = Color.Transparent
val sheetHandleColor: Color = tokens.handleColor(bottomSheetInfo)
val scrimOpacity: Float = tokens.scrimOpacity(bottomSheetInfo)
val scrimColor: Color =
tokens.scrimColor(bottomSheetInfo).copy(alpha = scrimOpacity)
val sheetShape: Shape = RoundedCornerShape(
topStart = getDrawerTokens().cornerRadius(getBottomSheetInfo()),
topEnd = getDrawerTokens().cornerRadius(getBottomSheetInfo())
)
val sheetElevation: Dp = getDrawerTokens().elevation(getBottomSheetInfo())
val sheetBackgroundColor: Color = getDrawerTokens().backgroundColor(getBottomSheetInfo())
val sheetContentColor: Color = Color.Transparent
val sheetHandleColor: Color = getDrawerTokens().handleColor(getBottomSheetInfo())
val scrimOpacity: Float = getDrawerTokens().scrimOpacity(getBottomSheetInfo())
val scrimColor: Color =
getDrawerTokens().scrimColor(getBottomSheetInfo()).copy(alpha = scrimOpacity)
val scope = rememberCoroutineScope()
val scope = rememberCoroutineScope()
BoxWithConstraints(modifier) {
val fullHeight = constraints.maxHeight.toFloat()
val sheetHeightState =
remember(sheetContent.hashCode()) { mutableStateOf<Float?>(null) }
BoxWithConstraints(modifier) {
val fullHeight = constraints.maxHeight.toFloat()
val sheetHeightState =
remember(sheetContent.hashCode()) { mutableStateOf<Float?>(null) }
Box(Modifier.fillMaxSize()) {
content()
if (slideOver) {
Scrim(
color = if (scrimVisible) scrimColor else Color.Transparent,
onDismiss = {
if (sheetState.confirmStateChange(BottomSheetValue.Hidden)) {
scope.launch { sheetState.show() }
}
},
fraction = {
if (sheetState.anchors.isEmpty()
|| !sheetState.anchors.containsValue(BottomSheetValue.Expanded)
) {
0.toFloat()
} else {
calculateFraction(
sheetState.anchors.entries.firstOrNull { it.value == BottomSheetValue.Shown }?.key!!,
sheetState.anchors.entries.firstOrNull { it.value == BottomSheetValue.Expanded }?.key!!,
sheetState.offset.value
)
}
},
visible = (sheetState.targetValue == BottomSheetValue.Expanded
|| (sheetState.targetValue == BottomSheetValue.Shown
&& sheetState.currentValue == BottomSheetValue.Expanded)
)
)
}
}
Surface(
Modifier
.fillMaxWidth()
.nestedScroll(if (slideOver) sheetState.PreUpPostDownNestedScrollConnection else sheetState.PostDownNestedScrollConnection)
.offset {
val y = if (sheetState.anchors.isEmpty()) {
// if we don't know our anchors yet, render the sheet as hidden
fullHeight.roundToInt()
} else {
// if we do know our anchors, respect them
sheetState.offset.value.roundToInt()
}
IntOffset(0, y)
}
.bottomSheetSwipeable(
sheetState,
expandable,
peekHeight,
fullHeight,
sheetHeightState.value,
slideOver
)
.onGloballyPositioned {
if (slideOver) {
val originalSize = it.size.height.toFloat()
sheetHeightState.value = if (expandable) {
originalSize
} else {
min(
originalSize,
min(dpToPx(peekHeight), fullHeight * BottomSheetOpenFraction)
)
}
}
}
.sheetHeight(
expandable,
slideOver,
fullHeight,
peekHeight,
sheetState
)
.semantics(mergeDescendants = true) {
if (sheetState.isVisible) {
dismiss {
if (sheetState.confirmStateChange(BottomSheetValue.Hidden)) {
scope.launch { sheetState.hide() }
}
true
}
if (sheetState.currentValue == BottomSheetValue.Shown) {
expand {
if (sheetState.confirmStateChange(BottomSheetValue.Expanded)) {
scope.launch { sheetState.expand() }
}
true
}
} else if (sheetState.hasExpandedState) {
collapse {
if (sheetState.confirmStateChange(BottomSheetValue.Shown)) {
scope.launch { sheetState.show() }
}
true
}
}
Box(Modifier.fillMaxSize()) {
content()
if (slideOver) {
Scrim(
color = if (scrimVisible) scrimColor else Color.Transparent,
onDismiss = {
if (sheetState.confirmStateChange(BottomSheetValue.Hidden)) {
scope.launch { sheetState.show() }
}
},
shape = sheetShape,
elevation = sheetElevation,
color = sheetBackgroundColor,
contentColor = sheetContentColor
) {
Column {
if (showHandle) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.padding(vertical = 8.dp)
.fillMaxWidth()
//TODO : Revisit SwipeableState usage across module to abstract out common modifier.
.draggable(
orientation = Orientation.Vertical,
state = rememberDraggableState { delta ->
sheetState.performDrag(delta)
},
onDragStopped = { velocity ->
launch {
sheetState.performFling(velocity)
if (!sheetState.isVisible) {
scope.launch { sheetState.hide() }
}
}
},
)
.testTag(BOTTOMSHEET_HANDLE_TAG)
fraction = {
if (sheetState.anchors.isEmpty()
|| !sheetState.anchors.containsValue(BottomSheetValue.Expanded)
) {
Icon(
painterResource(id = com.microsoft.fluentui.drawer.R.drawable.ic_drawer_handle),
contentDescription =
if (sheetState.currentValue == BottomSheetValue.Expanded || (sheetState.hasExpandedState && sheetState.isVisible)) {
LocalContext.current.resources.getString(R.string.drag_handle)
} else {
null
},
tint = sheetHandleColor,
modifier = Modifier
.clickable(
enabled = sheetState.hasExpandedState,
role = Role.Button,
onClickLabel =
if (sheetState.currentValue == BottomSheetValue.Expanded) {
LocalContext.current.resources.getString(R.string.collapse)
} else {
if (sheetState.hasExpandedState && sheetState.isVisible) LocalContext.current.resources.getString(
R.string.expand
) else null
}
) {
if (sheetState.currentValue == BottomSheetValue.Expanded) {
if (sheetState.confirmStateChange(BottomSheetValue.Shown)) {
scope.launch { sheetState.show() }
}
} else if (sheetState.hasExpandedState) {
if (sheetState.confirmStateChange(BottomSheetValue.Expanded)) {
scope.launch { sheetState.expand() }
}
}
}
0.toFloat()
} else {
calculateFraction(
sheetState.anchors.entries.firstOrNull { it.value == BottomSheetValue.Shown }?.key!!,
sheetState.anchors.entries.firstOrNull { it.value == BottomSheetValue.Expanded }?.key!!,
sheetState.offset.value
)
}
},
visible = (sheetState.targetValue == BottomSheetValue.Expanded
|| (sheetState.targetValue == BottomSheetValue.Shown
&& sheetState.currentValue == BottomSheetValue.Expanded)
)
)
}
}
Surface(
Modifier
.fillMaxWidth()
.nestedScroll(if (slideOver) sheetState.PreUpPostDownNestedScrollConnection else sheetState.PostDownNestedScrollConnection)
.offset {
val y = if (sheetState.anchors.isEmpty()) {
// if we don't know our anchors yet, render the sheet as hidden
fullHeight.roundToInt()
} else {
// if we do know our anchors, respect them
sheetState.offset.value.roundToInt()
}
IntOffset(0, y)
}
.bottomSheetSwipeable(
sheetState,
expandable,
peekHeight,
fullHeight,
sheetHeightState.value,
slideOver
)
.onGloballyPositioned {
if (slideOver) {
val originalSize = it.size.height.toFloat()
sheetHeightState.value = if (expandable) {
originalSize
} else {
min(
originalSize,
min(dpToPx(peekHeight), fullHeight * BottomSheetOpenFraction)
)
}
}
Column(modifier = Modifier
.testTag(BOTTOMSHEET_CONTENT_TAG)
.then(if (slideOver) Modifier else Modifier.fillMaxSize()),
content = { sheetContent() })
}
.sheetHeight(
expandable,
slideOver,
fullHeight,
peekHeight,
sheetState
)
.semantics(mergeDescendants = true) {
if (sheetState.isVisible) {
dismiss {
if (sheetState.confirmStateChange(BottomSheetValue.Hidden)) {
scope.launch { sheetState.hide() }
}
true
}
if (sheetState.currentValue == BottomSheetValue.Shown) {
expand {
if (sheetState.confirmStateChange(BottomSheetValue.Expanded)) {
scope.launch { sheetState.expand() }
}
true
}
} else if (sheetState.hasExpandedState) {
collapse {
if (sheetState.confirmStateChange(BottomSheetValue.Shown)) {
scope.launch { sheetState.show() }
}
true
}
}
}
},
shape = sheetShape,
elevation = sheetElevation,
color = sheetBackgroundColor,
contentColor = sheetContentColor
) {
Column {
if (showHandle) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.padding(vertical = 8.dp)
.fillMaxWidth()
//TODO : Revisit SwipeableState usage across module to abstract out common modifier.
.draggable(
orientation = Orientation.Vertical,
state = rememberDraggableState { delta ->
sheetState.performDrag(delta)
},
onDragStopped = { velocity ->
launch {
sheetState.performFling(velocity)
if (!sheetState.isVisible) {
scope.launch { sheetState.hide() }
}
}
},
)
.testTag(BOTTOMSHEET_HANDLE_TAG)
) {
Icon(
painterResource(id = R.drawable.ic_drawer_handle),
contentDescription =
if (sheetState.currentValue == BottomSheetValue.Expanded || (sheetState.hasExpandedState && sheetState.isVisible)) {
LocalContext.current.resources.getString(R.string.drag_handle)
} else {
null
},
tint = sheetHandleColor,
modifier = Modifier
.clickable(
enabled = sheetState.hasExpandedState,
role = Role.Button,
onClickLabel =
if (sheetState.currentValue == BottomSheetValue.Expanded) {
LocalContext.current.resources.getString(R.string.collapse)
} else {
if (sheetState.hasExpandedState && sheetState.isVisible) LocalContext.current.resources.getString(
R.string.expand
) else null
}
) {
if (sheetState.currentValue == BottomSheetValue.Expanded) {
if (sheetState.confirmStateChange(BottomSheetValue.Shown)) {
scope.launch { sheetState.show() }
}
} else if (sheetState.hasExpandedState) {
if (sheetState.confirmStateChange(BottomSheetValue.Expanded)) {
scope.launch { sheetState.expand() }
}
}
}
)
}
}
Column(modifier = Modifier
.testTag(BOTTOMSHEET_CONTENT_TAG)
.then(if (slideOver) Modifier else Modifier.fillMaxSize()),
content = { sheetContent() })
}
}
}

Просмотреть файл

@ -783,20 +783,6 @@ private fun BottomDrawer(
}
}
internal val LocalDrawerTokens = compositionLocalOf { DrawerTokens() }
internal val LocalDrawerInfo = compositionLocalOf { DrawerInfo() }
@Composable
private fun getDrawerTokens(): DrawerTokens {
return LocalDrawerTokens.current
}
@Composable
private fun getDrawerInfo(): DrawerInfo {
return LocalDrawerInfo.current
}
/**
*
* Drawer block interaction with the rest of an apps content with a scrim.
@ -835,82 +821,77 @@ fun Drawer(
scope.launch { drawerState.close() }
}
}
CompositionLocalProvider(
LocalDrawerTokens provides tokens,
LocalDrawerInfo provides DrawerInfo(type = behaviorType)
) {
Popup(
onDismissRequest = close,
popupPositionProvider = popupPositionProvider,
properties = PopupProperties(focusable = true)
)
{
val drawerShape: Shape =
when (behaviorType) {
BehaviorType.BOTTOM, BehaviorType.BOTTOM_SLIDE_OVER -> RoundedCornerShape(
topStart = getDrawerTokens().borderRadius(getDrawerInfo()),
topEnd = getDrawerTokens().borderRadius(getDrawerInfo())
)
BehaviorType.TOP -> RoundedCornerShape(
bottomStart = getDrawerTokens().borderRadius(getDrawerInfo()),
bottomEnd = getDrawerTokens().borderRadius(getDrawerInfo())
)
else -> RoundedCornerShape(getDrawerTokens().borderRadius(getDrawerInfo()))
}
val drawerElevation: Dp = getDrawerTokens().elevation(getDrawerInfo())
val drawerBackgroundColor: Color =
getDrawerTokens().backgroundColor(getDrawerInfo())
val drawerContentColor: Color = Color.Transparent
val drawerHandleColor: Color = getDrawerTokens().handleColor(getDrawerInfo())
val scrimOpacity: Float = getDrawerTokens().scrimOpacity(getDrawerInfo())
val scrimColor: Color =
getDrawerTokens().scrimColor(getDrawerInfo()).copy(alpha = scrimOpacity)
val drawerInfo = DrawerInfo(type = behaviorType)
Popup(
onDismissRequest = close,
popupPositionProvider = popupPositionProvider,
properties = PopupProperties(focusable = true)
)
{
val drawerShape: Shape =
when (behaviorType) {
BehaviorType.BOTTOM, BehaviorType.BOTTOM_SLIDE_OVER -> BottomDrawer(
modifier = modifier,
drawerState = drawerState,
drawerShape = drawerShape,
drawerElevation = drawerElevation,
drawerBackgroundColor = drawerBackgroundColor,
drawerContentColor = drawerContentColor,
drawerHandleColor = drawerHandleColor,
scrimColor = scrimColor,
scrimVisible = scrimVisible,
expandable = expandable,
slideOver = behaviorType == BehaviorType.BOTTOM_SLIDE_OVER,
onDismiss = close,
drawerContent = drawerContent
BehaviorType.BOTTOM, BehaviorType.BOTTOM_SLIDE_OVER -> RoundedCornerShape(
topStart = tokens.borderRadius(drawerInfo),
topEnd = tokens.borderRadius(drawerInfo)
)
BehaviorType.TOP -> TopDrawer(
modifier = modifier,
drawerState = drawerState,
drawerShape = drawerShape,
drawerElevation = drawerElevation,
drawerBackgroundColor = drawerBackgroundColor,
drawerContentColor = drawerContentColor,
drawerHandleColor = drawerHandleColor,
scrimColor = scrimColor,
scrimVisible = scrimVisible,
onDismiss = close,
drawerContent = drawerContent
)
BehaviorType.LEFT_SLIDE_OVER, BehaviorType.RIGHT_SLIDE_OVER -> HorizontalDrawer(
behaviorType = behaviorType,
modifier = modifier,
drawerState = drawerState,
drawerShape = drawerShape,
drawerElevation = drawerElevation,
drawerBackgroundColor = drawerBackgroundColor,
drawerContentColor = drawerContentColor,
scrimColor = scrimColor,
scrimVisible = scrimVisible,
onDismiss = close,
drawerContent = drawerContent
BehaviorType.TOP -> RoundedCornerShape(
bottomStart = tokens.borderRadius(drawerInfo),
bottomEnd = tokens.borderRadius(drawerInfo)
)
else -> RoundedCornerShape(tokens.borderRadius(drawerInfo))
}
val drawerElevation: Dp = tokens.elevation(drawerInfo)
val drawerBackgroundColor: Color =
tokens.backgroundColor(drawerInfo)
val drawerContentColor: Color = Color.Transparent
val drawerHandleColor: Color = tokens.handleColor(drawerInfo)
val scrimOpacity: Float = tokens.scrimOpacity(drawerInfo)
val scrimColor: Color =
tokens.scrimColor(drawerInfo).copy(alpha = scrimOpacity)
when (behaviorType) {
BehaviorType.BOTTOM, BehaviorType.BOTTOM_SLIDE_OVER -> BottomDrawer(
modifier = modifier,
drawerState = drawerState,
drawerShape = drawerShape,
drawerElevation = drawerElevation,
drawerBackgroundColor = drawerBackgroundColor,
drawerContentColor = drawerContentColor,
drawerHandleColor = drawerHandleColor,
scrimColor = scrimColor,
scrimVisible = scrimVisible,
expandable = expandable,
slideOver = behaviorType == BehaviorType.BOTTOM_SLIDE_OVER,
onDismiss = close,
drawerContent = drawerContent
)
BehaviorType.TOP -> TopDrawer(
modifier = modifier,
drawerState = drawerState,
drawerShape = drawerShape,
drawerElevation = drawerElevation,
drawerBackgroundColor = drawerBackgroundColor,
drawerContentColor = drawerContentColor,
drawerHandleColor = drawerHandleColor,
scrimColor = scrimColor,
scrimVisible = scrimVisible,
onDismiss = close,
drawerContent = drawerContent
)
BehaviorType.LEFT_SLIDE_OVER, BehaviorType.RIGHT_SLIDE_OVER -> HorizontalDrawer(
behaviorType = behaviorType,
modifier = modifier,
drawerState = drawerState,
drawerShape = drawerShape,
drawerElevation = drawerElevation,
drawerBackgroundColor = drawerBackgroundColor,
drawerContentColor = drawerContentColor,
scrimColor = scrimColor,
scrimVisible = scrimVisible,
onDismiss = close,
drawerContent = drawerContent
)
}
}
}

Просмотреть файл

@ -19,7 +19,6 @@ import com.microsoft.fluentui.theme.token.FluentStyle
import com.microsoft.fluentui.theme.token.controlTokens.*
import com.microsoft.fluentui.tokenized.divider.Divider
import com.microsoft.fluentui.tokenized.listitem.ListItem
import com.microsoft.fluentui.tokenized.listitem.getListItemInfo
import com.microsoft.fluentui.tokenized.tabItem.TabItem
import kotlinx.coroutines.launch
import kotlin.math.max
@ -307,7 +306,7 @@ class ListContentBuilder {
subText = item.subTitle,
leadingAccessoryView = {
if (item.icon != null) {
Icon(item.icon!!, null, tint = token.iconColor(getListItemInfo()).rest)
Icon(item.icon!!, null, tint = token.iconColor(ListItemInfo()).rest)
}
},
trailingAccessoryView = item.accessory,

Просмотреть файл

@ -4,8 +4,6 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.focusable
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@ -14,19 +12,6 @@ import com.microsoft.fluentui.theme.token.ControlTokens
import com.microsoft.fluentui.theme.token.controlTokens.DividerInfo
import com.microsoft.fluentui.theme.token.controlTokens.DividerTokens
val LocalDividerTokens = compositionLocalOf { DividerTokens() }
val LocalDividerInfo = compositionLocalOf { DividerInfo() }
@Composable
fun getDividerTokens(): DividerTokens {
return LocalDividerTokens.current
}
@Composable
fun getDividerInfo(): DividerInfo {
return LocalDividerInfo.current
}
@Composable
fun Divider(
height: Dp = 1.dp,
@ -35,23 +20,19 @@ fun Divider(
val token =
dividerToken
?: FluentTheme.controlTokens.tokens[ControlTokens.ControlType.Divider] as DividerTokens
CompositionLocalProvider(
LocalDividerTokens provides token,
LocalDividerInfo provides DividerInfo()
val dividerInfo = DividerInfo()
Column(
Modifier
.fillMaxWidth()
.background(token.background(dividerInfo = dividerInfo))
.focusable(false)
.padding(vertical = 8.dp)
) {
Column(
Box(
Modifier
.fillMaxWidth()
.background(getDividerTokens().background(dividerInfo = getDividerInfo()))
.focusable(false)
.padding(vertical = 8.dp)
) {
Box(
Modifier
.fillMaxWidth()
.requiredHeight(height)
.background(getDividerTokens().dividerColor(dividerInfo = getDividerInfo()))
)
}
.requiredHeight(height)
.background(token.dividerColor(dividerInfo = dividerInfo))
)
}
}

Просмотреть файл

@ -1,6 +1,5 @@
package com.microsoft.fluentui.tokenized.listitem
import android.R
import androidx.compose.animation.*
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.Canvas
@ -44,19 +43,6 @@ import com.microsoft.fluentui.theme.token.controlTokens.TextPlacement.Bottom
import com.microsoft.fluentui.theme.token.controlTokens.TextPlacement.Top
import com.microsoft.fluentui.util.dpToPx
val LocalListItemTokens = compositionLocalOf { ListItemTokens() }
val LocalListItemInfo = compositionLocalOf { ListItemInfo() }
@Composable
internal fun getListItemTokens(): ListItemTokens {
return LocalListItemTokens.current
}
@Composable
internal fun getListItemInfo(): ListItemInfo {
return LocalListItemInfo.current
}
object ListItem {
private fun clearSemantics(properties: (SemanticsPropertyReceiver.() -> Unit)?): Modifier {
@ -260,174 +246,169 @@ object ListItem {
}
val token = listItemTokens
?: FluentTheme.controlTokens.tokens[ControlType.ListItem] as ListItemTokens
CompositionLocalProvider(
LocalListItemTokens provides token, LocalListItemInfo provides ListItemInfo(
listItemType = listItemType,
borderInset = borderInset,
horizontalSpacing = GlobalTokens.SizeTokens.Size160,
verticalSpacing = GlobalTokens.SizeTokens.Size160,
unreadDot = unreadDot
val listItemInfo = ListItemInfo(
listItemType = listItemType,
borderInset = borderInset,
horizontalSpacing = GlobalTokens.SizeTokens.Size160,
verticalSpacing = GlobalTokens.SizeTokens.Size160,
unreadDot = unreadDot
)
val backgroundColor =
token.backgroundColor(listItemInfo).getColorByState(
enabled = true, selected = false, interactionSource = interactionSource
)
val cellHeight = token.cellHeight(listItemInfo)
val primaryTextTypography = token.primaryTextTypography(listItemInfo)
val subTextTypography = token.subTextTypography(listItemInfo)
val secondarySubTextTypography =
token.secondarySubTextTypography(listItemInfo)
val primaryTextColor = token.primaryTextColor(
listItemInfo
).getColorByState(
enabled = enabled, selected = false, interactionSource = interactionSource
)
val subTextColor = token.subTextColor(
listItemInfo
).getColorByState(
enabled = enabled, selected = false, interactionSource = interactionSource
)
val secondarySubTextColor = token.secondarySubTextColor(
listItemInfo
).getColorByState(
enabled = enabled, selected = false, interactionSource = interactionSource
)
val rippleColor = token.rippleColor(listItemInfo)
val unreadDotColor = token.unreadDotColor(listItemInfo)
val padding = token.padding(listItemInfo)
val borderSize = token.borderSize(listItemInfo).value
val borderInsetToPx = with(LocalDensity.current) {
token.borderInset(listItemInfo).toPx()
}
val borderColor = token.borderColor(listItemInfo).getColorByState(
enabled = enabled, selected = false, interactionSource = interactionSource
)
Row(
modifier
.background(backgroundColor)
.fillMaxWidth()
.heightIn(min = cellHeight)
.borderModifier(border, borderColor, borderSize, borderInsetToPx)
.clickAndSemanticsModifier(
interactionSource, onClick = onClick ?: {}, enabled, rippleColor
), verticalAlignment = Alignment.CenterVertically
) {
val backgroundColor =
getListItemTokens().backgroundColor(getListItemInfo()).getColorByState(
enabled = true, selected = false, interactionSource = interactionSource
)
val cellHeight = getListItemTokens().cellHeight(getListItemInfo())
val primaryTextTypography = getListItemTokens().primaryTextTypography(getListItemInfo())
val subTextTypography = getListItemTokens().subTextTypography(getListItemInfo())
val secondarySubTextTypography =
getListItemTokens().secondarySubTextTypography(getListItemInfo())
val primaryTextColor = getListItemTokens().primaryTextColor(
getListItemInfo()
).getColorByState(
enabled = enabled, selected = false, interactionSource = interactionSource
)
val subTextColor = getListItemTokens().subTextColor(
getListItemInfo()
).getColorByState(
enabled = enabled, selected = false, interactionSource = interactionSource
)
val secondarySubTextColor = getListItemTokens().secondarySubTextColor(
getListItemInfo()
).getColorByState(
enabled = enabled, selected = false, interactionSource = interactionSource
)
val rippleColor = getListItemTokens().rippleColor(getListItemInfo())
val unreadDotColor = getListItemTokens().unreadDotColor(getListItemInfo())
val padding = getListItemTokens().padding(getListItemInfo())
val borderSize = getListItemTokens().borderSize(getListItemInfo()).value
val borderInsetToPx = with(LocalDensity.current) {
getListItemTokens().borderInset(getListItemInfo()).toPx()
}
val borderColor = getListItemTokens().borderColor(getListItemInfo()).getColorByState(
enabled = enabled, selected = false, interactionSource = interactionSource
)
Row(
modifier
.background(backgroundColor)
.fillMaxWidth()
.heightIn(min = cellHeight)
.borderModifier(border, borderColor, borderSize, borderInsetToPx)
.clickAndSemanticsModifier(
interactionSource, onClick = onClick ?: {}, enabled, rippleColor
)
, verticalAlignment = Alignment.CenterVertically
) {
if (unreadDot) {
Canvas(
modifier = Modifier
.padding(start = 4.dp)
.sizeIn(minWidth = 8.dp, minHeight = 8.dp)
) {
drawCircle(
color = unreadDotColor, style = Fill, radius = dpToPx(4.dp)
)
}
}
if (leadingAccessoryView != null && textAlignment == ListItemTextAlignment.Regular) {
Box(
Modifier.padding(
start = if (unreadDot) 4.dp else padding.calculateStartPadding(
LocalLayoutDirection.current
)
), contentAlignment = Alignment.Center
) {
leadingAccessoryView()
}
}
val contentAlignment =
if (textAlignment == ListItemTextAlignment.Regular) Alignment.CenterStart else Alignment.Center
Box(
Modifier
.padding(horizontal = padding.calculateStartPadding(LocalLayoutDirection.current))
.weight(1f)
.then(clearSemantics(textAccessibilityProperties)),contentAlignment = contentAlignment
if (unreadDot) {
Canvas(
modifier = Modifier
.padding(start = 4.dp)
.sizeIn(minWidth = 8.dp, minHeight = 8.dp)
) {
Column(Modifier.padding(vertical = padding.calculateTopPadding())) {
drawCircle(
color = unreadDotColor, style = Fill, radius = dpToPx(4.dp)
)
}
}
if (leadingAccessoryView != null && textAlignment == ListItemTextAlignment.Regular) {
Box(
Modifier.padding(
start = if (unreadDot) 4.dp else padding.calculateStartPadding(
LocalLayoutDirection.current
)
), contentAlignment = Alignment.Center
) {
leadingAccessoryView()
}
}
val contentAlignment =
if (textAlignment == ListItemTextAlignment.Regular) Alignment.CenterStart else Alignment.Center
Box(
Modifier
.padding(horizontal = padding.calculateStartPadding(LocalLayoutDirection.current))
.weight(1f)
.then(clearSemantics(textAccessibilityProperties)),
contentAlignment = contentAlignment
) {
Column(Modifier.padding(vertical = padding.calculateTopPadding())) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
if (primaryTextLeadingIcons != null) {
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
primaryTextLeadingIcons.icon1()
primaryTextLeadingIcons.icon2?.let { it() }
}
}
Text(
text = text,
style = primaryTextTypography,
color = primaryTextColor,
maxLines = textMaxLines,
overflow = TextOverflow.Ellipsis
)
if (primaryTextTrailingIcons != null) {
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
primaryTextTrailingIcons.icon1()
primaryTextTrailingIcons.icon2?.let { it() }
}
}
}
Row(verticalAlignment = Alignment.CenterVertically) {
if (subText != null && textAlignment == ListItemTextAlignment.Regular) {
Text(
text = subText,
style = subTextTypography,
color = subTextColor,
maxLines = subTextMaxLines,
overflow = TextOverflow.Ellipsis
)
}
}
if (textAlignment == ListItemTextAlignment.Regular) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
if (primaryTextLeadingIcons != null) {
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
primaryTextLeadingIcons.icon1()
primaryTextLeadingIcons.icon2?.let { it() }
if (bottomView != null) {
Row(modifier.padding(top = 7.dp, bottom = 7.dp)) {
bottomView()
}
}
Text(
text = text,
style = primaryTextTypography,
color = primaryTextColor,
maxLines = textMaxLines,
overflow = TextOverflow.Ellipsis
)
if (primaryTextTrailingIcons != null) {
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
primaryTextTrailingIcons.icon1()
primaryTextTrailingIcons.icon2?.let { it() }
} else {
if (secondarySubTextLeadingIcons != null) {
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
secondarySubTextLeadingIcons.icon1()
secondarySubTextLeadingIcons.icon2?.let { it() }
}
}
}
}
Row(verticalAlignment = Alignment.CenterVertically) {
if (subText != null && textAlignment == ListItemTextAlignment.Regular) {
Text(
text = subText,
style = subTextTypography,
color = subTextColor,
maxLines = subTextMaxLines,
overflow = TextOverflow.Ellipsis
)
}
}
if (textAlignment == ListItemTextAlignment.Regular) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
if (bottomView != null) {
Row(modifier.padding(top = 7.dp, bottom = 7.dp)) {
bottomView()
}
} else {
if (secondarySubTextLeadingIcons != null) {
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
secondarySubTextLeadingIcons.icon1()
secondarySubTextLeadingIcons.icon2?.let { it() }
}
}
if (secondarySubText != null) {
Text(
text = secondarySubText,
style = secondarySubTextTypography,
color = secondarySubTextColor,
maxLines = secondarySubTextMaxLines,
overflow = TextOverflow.Ellipsis
)
}
if (secondarySubTextTailingIcons != null) {
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
secondarySubTextTailingIcons.icon1()
secondarySubTextTailingIcons.icon2?.let { it() }
}
if (secondarySubText != null) {
Text(
text = secondarySubText,
style = secondarySubTextTypography,
color = secondarySubTextColor,
maxLines = secondarySubTextMaxLines,
overflow = TextOverflow.Ellipsis
)
}
if (secondarySubTextTailingIcons != null) {
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
secondarySubTextTailingIcons.icon1()
secondarySubTextTailingIcons.icon2?.let { it() }
}
}
}
}
}
}
if (bottomView == null && trailingAccessoryView != null && textAlignment == ListItemTextAlignment.Regular) {
Box(
Modifier.padding(end = padding.calculateEndPadding(LocalLayoutDirection.current)),
contentAlignment = Alignment.CenterEnd
) {
trailingAccessoryView()
}
}
if (bottomView == null && trailingAccessoryView != null && textAlignment == ListItemTextAlignment.Regular) {
Box(
Modifier.padding(end = padding.calculateEndPadding(LocalLayoutDirection.current)),
contentAlignment = Alignment.CenterEnd
) {
trailingAccessoryView()
}
}
}
}
@ -478,136 +459,132 @@ object ListItem {
val token = listItemTokens
?: FluentTheme.controlTokens.tokens[ControlType.ListItem] as ListItemTokens
CompositionLocalProvider(
LocalListItemTokens provides token, LocalListItemInfo provides ListItemInfo(
listItemType = SectionHeader,
borderInset = borderInset,
horizontalSpacing = GlobalTokens.SizeTokens.Size160,
verticalSpacing = GlobalTokens.SizeTokens.Size120,
style = style
val listItemInfo = ListItemInfo(
listItemType = SectionHeader,
borderInset = borderInset,
horizontalSpacing = GlobalTokens.SizeTokens.Size160,
verticalSpacing = GlobalTokens.SizeTokens.Size120,
style = style
)
val backgroundColor =
token.backgroundColor(listItemInfo).getColorByState(
enabled = true, selected = false, interactionSource = interactionSource
)
) {
val backgroundColor =
getListItemTokens().backgroundColor(getListItemInfo()).getColorByState(
enabled = true, selected = false, interactionSource = interactionSource
val cellHeight = token.cellHeight(listItemInfo)
val primaryTextTypography =
token.sectionHeaderPrimaryTextTypography(listItemInfo)
val actionTextTypography =
token.sectionHeaderActionTextTypography(listItemInfo)
val primaryTextColor = token.primaryTextColor(
listItemInfo
).getColorByState(
enabled = enabled, selected = false, interactionSource = interactionSource
)
val actionTextColor = token.actionTextColor(
listItemInfo
).getColorByState(
enabled = enabled, selected = false, interactionSource = interactionSource
)
val rippleColor = token.rippleColor(listItemInfo)
val padding = token.padding(listItemInfo)
val borderSize = token.borderSize(listItemInfo).value
val borderInsetToPx = with(LocalDensity.current) {
token.borderInset(listItemInfo).toPx()
}
val borderColor = token.borderColor(listItemInfo).getColorByState(
enabled = enabled, selected = false, interactionSource = interactionSource
)
val chevronTint = token.chevronTint(listItemInfo)
var expandedState by rememberSaveable { mutableStateOf(false) }
val rotationState by animateFloatAsState(
targetValue = if (!enableContentOpenCloseTransition || expandedState) chevronOrientation.enterTransition else chevronOrientation.exitTransition
)
Surface(
modifier = modifier
.fillMaxWidth()
.heightIn(min = cellHeight)
.background(backgroundColor)
.clickAndSemanticsModifier(
interactionSource,
onClick = { expandedState = !expandedState },
enabled,
rippleColor
)
val cellHeight = getListItemTokens().cellHeight(getListItemInfo())
val primaryTextTypography =
getListItemTokens().sectionHeaderPrimaryTextTypography(getListItemInfo())
val actionTextTypography =
getListItemTokens().sectionHeaderActionTextTypography(getListItemInfo())
val primaryTextColor = getListItemTokens().primaryTextColor(
getListItemInfo()
).getColorByState(
enabled = enabled, selected = false, interactionSource = interactionSource
)
val actionTextColor = getListItemTokens().actionTextColor(
getListItemInfo()
).getColorByState(
enabled = enabled, selected = false, interactionSource = interactionSource
)
val rippleColor = getListItemTokens().rippleColor(getListItemInfo())
val padding = getListItemTokens().padding(getListItemInfo())
val borderSize = getListItemTokens().borderSize(getListItemInfo()).value
val borderInsetToPx = with(LocalDensity.current) {
getListItemTokens().borderInset(getListItemInfo()).toPx()
}
val borderColor = getListItemTokens().borderColor(getListItemInfo()).getColorByState(
enabled = enabled, selected = false, interactionSource = interactionSource
)
val chevronTint = getListItemTokens().chevronTint(getListItemInfo())
var expandedState by rememberSaveable { mutableStateOf(false) }
val rotationState by animateFloatAsState(
targetValue = if (!enableContentOpenCloseTransition || expandedState) chevronOrientation.enterTransition else chevronOrientation.exitTransition
)
Surface(
modifier = modifier
.fillMaxWidth()
.heightIn(min = cellHeight)
.background(backgroundColor)
.clickAndSemanticsModifier(
interactionSource,
onClick = { expandedState = !expandedState },
enabled,
rippleColor
)
.borderModifier(border, borderColor, borderSize, borderInsetToPx)
) {
Column {
Row(
.borderModifier(border, borderColor, borderSize, borderInsetToPx)
) {
Column {
Row(
Modifier
.background(backgroundColor)
.fillMaxWidth()
.heightIn(min = cellHeight)
.padding(bottom = padding.calculateBottomPadding()),
verticalAlignment = Alignment.Bottom
) {
Box(
Modifier
.background(backgroundColor)
.fillMaxWidth()
.heightIn(min = cellHeight)
.padding(bottom = padding.calculateBottomPadding()),
verticalAlignment = Alignment.Bottom
.padding(
horizontal = padding.calculateStartPadding(
LocalLayoutDirection.current
)
)
.weight(1f), contentAlignment = Alignment.BottomStart
) {
Row(verticalAlignment = Alignment.CenterVertically) {
if (enableChevron) {
Icon(painter = rememberVectorPainter(image = ListItemIcons.Chevron),
contentDescription = "Chevron",
Modifier
.clickable { expandedState = !expandedState }
.rotate(rotationState),
tint = chevronTint)
}
Text(
text = title,
style = primaryTextTypography,
color = primaryTextColor,
maxLines = titleMaxLines,
overflow = TextOverflow.Ellipsis
)
}
}
Row(Modifier.padding(end = padding.calculateEndPadding(LocalLayoutDirection.current))) {
if (accessoryTextTitle != null) {
Text(
text = accessoryTextTitle,
Modifier.clickable(role = Role.Button,
onClick = accessoryTextOnClick ?: {}),
color = actionTextColor,
style = actionTextTypography
)
}
}
if (trailingAccessoryView != null) {
Box(
Modifier
.padding(
horizontal = padding.calculateStartPadding(
LocalLayoutDirection.current
)
Modifier.padding(
end = padding.calculateEndPadding(
LocalLayoutDirection.current
)
.weight(1f), contentAlignment = Alignment.BottomStart
), contentAlignment = Alignment.BottomStart
) {
Row(verticalAlignment = Alignment.CenterVertically) {
if (enableChevron) {
Icon(painter = rememberVectorPainter(image = ListItemIcons.Chevron),
contentDescription = "Chevron",
Modifier
.clickable { expandedState = !expandedState }
.rotate(rotationState),
tint = chevronTint)
}
Text(
text = title,
style = primaryTextTypography,
color = primaryTextColor,
maxLines = titleMaxLines,
overflow = TextOverflow.Ellipsis
)
}
}
Row(Modifier.padding(end = padding.calculateEndPadding(LocalLayoutDirection.current))) {
if (accessoryTextTitle != null) {
Text(
text = accessoryTextTitle,
Modifier.clickable(role = Role.Button,
onClick = accessoryTextOnClick ?: {}),
color = actionTextColor,
style = actionTextTypography
)
}
}
if (trailingAccessoryView != null) {
Box(
Modifier.padding(
end = padding.calculateEndPadding(
LocalLayoutDirection.current
)
), contentAlignment = Alignment.BottomStart
) {
trailingAccessoryView()
}
trailingAccessoryView()
}
}
Row {
if (content != null) {
AnimatedVisibility(
visible = !enableContentOpenCloseTransition || expandedState,
enter = enter,
exit = exit
) {
content()
}
}
Row {
if (content != null) {
AnimatedVisibility(
visible = !enableContentOpenCloseTransition || expandedState,
enter = enter,
exit = exit
) {
content()
}
}
}
}
}
@ -648,100 +625,96 @@ object ListItem {
) {
val token = listItemTokens
?: FluentTheme.controlTokens.tokens[ControlType.ListItem] as ListItemTokens
CompositionLocalProvider(
LocalListItemTokens provides token, LocalListItemInfo provides ListItemInfo(
listItemType = SectionDescription,
horizontalSpacing = GlobalTokens.SizeTokens.Size160,
verticalSpacing = GlobalTokens.SizeTokens.Size80,
borderInset = borderInset,
placement = descriptionPlacement
val listItemInfo = ListItemInfo(
listItemType = SectionDescription,
horizontalSpacing = GlobalTokens.SizeTokens.Size160,
verticalSpacing = GlobalTokens.SizeTokens.Size80,
borderInset = borderInset,
placement = descriptionPlacement
)
val backgroundColor =
token.backgroundColor(listItemInfo).getColorByState(
enabled = true, selected = false, interactionSource = interactionSource
)
val cellHeight = token.cellHeight(listItemInfo)
val descriptionTextTypography =
token.descriptionTextTypography(listItemInfo)
val actionTextTypography = token.actionTextTypography(listItemInfo)
val descriptionTextColor = token.descriptionTextColor(
listItemInfo
).getColorByState(
enabled = enabled, selected = false, interactionSource = interactionSource
)
val actionTextColor = token.actionTextColor(
listItemInfo
).getColorByState(
enabled = enabled, selected = false, interactionSource = interactionSource
)
val rippleColor = token.rippleColor(listItemInfo)
val borderSize = token.borderSize(listItemInfo).value
val borderInsetToPx = with(LocalDensity.current) {
token.borderInset(listItemInfo).toPx()
}
val borderColor = token.borderColor(listItemInfo).getColorByState(
enabled = enabled, selected = false, interactionSource = interactionSource
)
val descriptionAlignment = token.descriptionPlacement(listItemInfo)
val padding = token.padding(listItemInfo)
Row(
modifier
.fillMaxWidth()
.heightIn(min = cellHeight)
.background(backgroundColor)
.borderModifier(border, borderColor, borderSize, borderInsetToPx)
.clickAndSemanticsModifier(
interactionSource, onClick = onClick ?: {}, enabled, rippleColor
), verticalAlignment = descriptionAlignment
) {
val backgroundColor =
getListItemTokens().backgroundColor(getListItemInfo()).getColorByState(
enabled = true, selected = false, interactionSource = interactionSource
)
val cellHeight = getListItemTokens().cellHeight(getListItemInfo())
val descriptionTextTypography =
getListItemTokens().descriptionTextTypography(getListItemInfo())
val actionTextTypography = getListItemTokens().actionTextTypography(getListItemInfo())
val descriptionTextColor = getListItemTokens().descriptionTextColor(
getListItemInfo()
).getColorByState(
enabled = enabled, selected = false, interactionSource = interactionSource
)
val actionTextColor = getListItemTokens().actionTextColor(
getListItemInfo()
).getColorByState(
enabled = enabled, selected = false, interactionSource = interactionSource
)
val rippleColor = getListItemTokens().rippleColor(getListItemInfo())
val borderSize = getListItemTokens().borderSize(getListItemInfo()).value
val borderInsetToPx = with(LocalDensity.current) {
getListItemTokens().borderInset(getListItemInfo()).toPx()
}
val borderColor = getListItemTokens().borderColor(getListItemInfo()).getColorByState(
enabled = enabled, selected = false, interactionSource = interactionSource
)
val descriptionAlignment = getListItemTokens().descriptionPlacement(getListItemInfo())
val padding = getListItemTokens().padding(getListItemInfo())
Row(
modifier
.fillMaxWidth()
.heightIn(min = cellHeight)
.background(backgroundColor)
.borderModifier(border, borderColor, borderSize, borderInsetToPx)
.clickAndSemanticsModifier(
interactionSource, onClick = onClick ?: {}, enabled, rippleColor
), verticalAlignment = descriptionAlignment
) {
if (leadingAccessoryView != null && descriptionPlacement == Top) {
Box(
Modifier.padding(padding.calculateStartPadding(LocalLayoutDirection.current)),
contentAlignment = Alignment.Center
) {
leadingAccessoryView()
}
}
if (leadingAccessoryView != null && descriptionPlacement == Top) {
Box(
Modifier
.padding(
start = if (leadingAccessoryView == null) padding.calculateStartPadding(
LocalLayoutDirection.current
) else 0.dp,
end = padding.calculateEndPadding(LocalLayoutDirection.current),
top = if (descriptionPlacement == Top) padding.calculateTopPadding() else 0.dp,
bottom = if (descriptionPlacement == Bottom) padding.calculateTopPadding() else 0.dp
)
.weight(1f)
Modifier.padding(padding.calculateStartPadding(LocalLayoutDirection.current)),
contentAlignment = Alignment.Center
) {
if (!actionText.isNullOrBlank()) {
InlineText(
description = description,
actionText = actionText,
onClick = onActionClick ?: {},
actionTextTypography = actionTextTypography,
actionTextColor = actionTextColor,
descriptionTextColor = descriptionTextColor,
descriptionTextTypography = descriptionTextTypography,
backgroundColor = backgroundColor
)
} else {
Text(
text = description,
color = descriptionTextColor,
style = descriptionTextTypography
)
}
leadingAccessoryView()
}
if (trailingAccessoryView != null) {
Box(
Modifier.padding(end = padding.calculateEndPadding(LocalLayoutDirection.current)),
contentAlignment = Alignment.Center
) {
trailingAccessoryView()
}
}
Box(
Modifier
.padding(
start = if (leadingAccessoryView == null) padding.calculateStartPadding(
LocalLayoutDirection.current
) else 0.dp,
end = padding.calculateEndPadding(LocalLayoutDirection.current),
top = if (descriptionPlacement == Top) padding.calculateTopPadding() else 0.dp,
bottom = if (descriptionPlacement == Bottom) padding.calculateTopPadding() else 0.dp
)
.weight(1f)
) {
if (!actionText.isNullOrBlank()) {
InlineText(
description = description,
actionText = actionText,
onClick = onActionClick ?: {},
actionTextTypography = actionTextTypography,
actionTextColor = actionTextColor,
descriptionTextColor = descriptionTextColor,
descriptionTextTypography = descriptionTextTypography,
backgroundColor = backgroundColor
)
} else {
Text(
text = description,
color = descriptionTextColor,
style = descriptionTextTypography
)
}
}
if (trailingAccessoryView != null) {
Box(
Modifier.padding(end = padding.calculateEndPadding(LocalLayoutDirection.current)),
contentAlignment = Alignment.Center
) {
trailingAccessoryView()
}
}
}
@ -781,98 +754,94 @@ object ListItem {
) {
val token = listItemTokens
?: FluentTheme.controlTokens.tokens[ControlType.ListItem] as ListItemTokens
CompositionLocalProvider(
LocalListItemTokens provides token, LocalListItemInfo provides ListItemInfo(
listItemType = OneLine,
style = style,
horizontalSpacing = GlobalTokens.SizeTokens.Size160,
verticalSpacing = GlobalTokens.SizeTokens.Size80,
borderInset = borderInset
val listItemInfo = ListItemInfo(
listItemType = OneLine,
style = style,
horizontalSpacing = GlobalTokens.SizeTokens.Size160,
verticalSpacing = GlobalTokens.SizeTokens.Size80,
borderInset = borderInset
)
val backgroundColor =
token.backgroundColor(listItemInfo).getColorByState(
enabled = true, selected = false, interactionSource = interactionSource
)
val cellHeight = token.cellHeight(listItemInfo)
val primaryTextTypography =
token.sectionHeaderPrimaryTextTypography(listItemInfo)
val actionTextTypography =
token.sectionHeaderActionTextTypography(listItemInfo)
val primaryTextColor = token.primaryTextColor(
listItemInfo
).getColorByState(
enabled = enabled, selected = false, interactionSource = interactionSource
)
val actionTextColor = token.actionTextColor(
listItemInfo
).getColorByState(
enabled = enabled, selected = false, interactionSource = interactionSource
)
val padding = token.padding(listItemInfo)
val borderSize = token.borderSize(listItemInfo).value
val borderInsetToPx = with(LocalDensity.current) {
token.borderInset(listItemInfo).toPx()
}
val borderColor = token.borderColor(listItemInfo).getColorByState(
enabled = enabled, selected = false, interactionSource = interactionSource
)
Surface(
modifier = modifier
.fillMaxWidth()
.heightIn(min = cellHeight)
.background(backgroundColor)
.borderModifier(border, borderColor, borderSize, borderInsetToPx)
.focusable(false)
) {
val backgroundColor =
getListItemTokens().backgroundColor(getListItemInfo()).getColorByState(
enabled = true, selected = false, interactionSource = interactionSource
)
val cellHeight = getListItemTokens().cellHeight(getListItemInfo())
val primaryTextTypography =
getListItemTokens().sectionHeaderPrimaryTextTypography(getListItemInfo())
val actionTextTypography =
getListItemTokens().sectionHeaderActionTextTypography(getListItemInfo())
val primaryTextColor = getListItemTokens().primaryTextColor(
getListItemInfo()
).getColorByState(
enabled = enabled, selected = false, interactionSource = interactionSource
)
val actionTextColor = getListItemTokens().actionTextColor(
getListItemInfo()
).getColorByState(
enabled = enabled, selected = false, interactionSource = interactionSource
)
val padding = getListItemTokens().padding(getListItemInfo())
val borderSize = getListItemTokens().borderSize(getListItemInfo()).value
val borderInsetToPx = with(LocalDensity.current) {
getListItemTokens().borderInset(getListItemInfo()).toPx()
}
val borderColor = getListItemTokens().borderColor(getListItemInfo()).getColorByState(
enabled = enabled, selected = false, interactionSource = interactionSource
)
Surface(
modifier = modifier
Row(
Modifier
.fillMaxWidth()
.heightIn(min = cellHeight)
.background(backgroundColor)
.borderModifier(border, borderColor, borderSize, borderInsetToPx)
.focusable(false)
.focusable(true), verticalAlignment = Alignment.Bottom
) {
Row(
Modifier
.fillMaxWidth()
.heightIn(min = cellHeight)
.background(backgroundColor)
.focusable(true), verticalAlignment = Alignment.Bottom
) {
Text(
text = title,
modifier = Modifier
.padding(
start = padding.calculateStartPadding(LocalLayoutDirection.current),
end = padding.calculateEndPadding(LocalLayoutDirection.current),
bottom = padding.calculateBottomPadding()
)
.weight(1f),
style = primaryTextTypography,
color = primaryTextColor,
maxLines = titleMaxLines,
overflow = TextOverflow.Ellipsis
)
if (accessoryTextTitle != null) {
Text(
text = title,
modifier = Modifier
text = accessoryTextTitle,
Modifier
.padding(
start = padding.calculateStartPadding(LocalLayoutDirection.current),
end = padding.calculateEndPadding(LocalLayoutDirection.current),
bottom = padding.calculateBottomPadding()
)
.weight(1f),
style = primaryTextTypography,
color = primaryTextColor,
maxLines = titleMaxLines,
overflow = TextOverflow.Ellipsis
.clickable(
role = Role.Button,
onClick = accessoryTextOnClick ?: {}),
color = actionTextColor,
style = actionTextTypography
)
if (accessoryTextTitle != null) {
Text(
text = accessoryTextTitle,
Modifier
.padding(
end = padding.calculateEndPadding(LocalLayoutDirection.current),
bottom = padding.calculateBottomPadding()
)
.clickable(
role = Role.Button,
onClick = accessoryTextOnClick ?: {}),
color = actionTextColor,
style = actionTextTypography
)
}
if (trailingAccessoryView != null) {
Box(
Modifier.padding(
end = padding.calculateEndPadding(LocalLayoutDirection.current),
bottom = padding.calculateBottomPadding()
), contentAlignment = Alignment.BottomStart
) {
trailingAccessoryView()
}
}
if (trailingAccessoryView != null) {
Box(
Modifier.padding(
end = padding.calculateEndPadding(LocalLayoutDirection.current),
bottom = padding.calculateBottomPadding()
), contentAlignment = Alignment.BottomStart
) {
trailingAccessoryView()
}
}
}

Просмотреть файл

@ -8,8 +8,6 @@ import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -30,20 +28,6 @@ import com.microsoft.fluentui.theme.token.controlTokens.TabItemInfo
import com.microsoft.fluentui.theme.token.controlTokens.TabItemTokens
import com.microsoft.fluentui.theme.token.controlTokens.TabTextAlignment
val LocalTabItemTokens = compositionLocalOf { TabItemTokens() }
val LocalTabItemInfo =
compositionLocalOf { TabItemInfo(TabTextAlignment.VERTICAL, FluentStyle.Neutral) }
@Composable
fun getTabItemTokens(): TabItemTokens {
return LocalTabItemTokens.current
}
@Composable
fun getTabItemInfo(): TabItemInfo {
return LocalTabItemInfo.current
}
@Composable
fun TabItem(
title: String,
@ -63,169 +47,165 @@ fun TabItem(
tabItemTokens
?: FluentTheme.controlTokens.tokens[ControlTokens.ControlType.TabItem] as TabItemTokens
CompositionLocalProvider(
LocalTabItemTokens provides token,
LocalTabItemInfo provides TabItemInfo(textAlignment, style)
) {
val textColor =
getTabItemTokens().textColor(tabItemInfo = getTabItemInfo()).getColorByState(
enabled = enabled,
selected = selected,
interactionSource = interactionSource
)
val iconColor =
getTabItemTokens().iconColor(tabItemInfo = getTabItemInfo()).getColorByState(
enabled = enabled,
selected = selected,
interactionSource = interactionSource
val tabItemInfo = TabItemInfo(textAlignment, style)
val textColor =
token.textColor(tabItemInfo = tabItemInfo).getColorByState(
enabled = enabled,
selected = selected,
interactionSource = interactionSource
)
val iconColor =
token.iconColor(tabItemInfo = tabItemInfo).getColorByState(
enabled = enabled,
selected = selected,
interactionSource = interactionSource
)
val backgroundColor = token.backgroundColor(tabItemInfo = tabItemInfo)
val rippleColor = token.rippleColor(tabItemInfo = tabItemInfo)
val clickableModifier = Modifier
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(color = rippleColor),
onClickLabel = null,
enabled = enabled,
onClick = onClick,
role = Role.Tab
)
val widthModifier = if (fixedWidth) {
Modifier.width(token.width(tabItemInfo = tabItemInfo))
} else {
Modifier
}
val iconContent: @Composable () -> Unit = {
Icon(
imageVector = icon,
modifier = Modifier.size(if (textAlignment == TabTextAlignment.NO_TEXT) 28.dp else 24.dp),
contentDescription = if (textAlignment == TabTextAlignment.NO_TEXT) title else null,
tint = iconColor
)
}
if (textAlignment == TabTextAlignment.HORIZONTAL) {
ConstraintLayout(
modifier = modifier
.then(clickableModifier)
.background(backgroundColor)
.padding(top = 8.dp, start = 4.dp, bottom = 4.dp, end = 8.dp)
.then(widthModifier)
)
{
val (iconConstrain, textConstrain, badgeConstrain) = createRefs()
Box(modifier = Modifier.constrainAs(iconConstrain) {
start.linkTo(parent.start)
end.linkTo(textConstrain.start)
}
) {
iconContent()
}
Text(
text = title,
modifier = Modifier
.constrainAs(textConstrain) {
start.linkTo(iconConstrain.end)
end.linkTo(badgeConstrain.start)
width = Dimension.preferredWrapContent
}
.padding(start = 8.dp),
color = textColor,
textAlign = TextAlign.Center,
overflow = TextOverflow.Ellipsis,
maxLines = 1
)
val backgroundColor = getTabItemTokens().backgroundColor(tabItemInfo = getTabItemInfo())
val rippleColor = getTabItemTokens().rippleColor(tabItemInfo = getTabItemInfo())
val clickableModifier = Modifier
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(color = rippleColor),
onClickLabel = null,
enabled = enabled,
onClick = onClick,
role = Role.Tab
)
val widthModifier = if (fixedWidth) {
Modifier.width(getTabItemTokens().width(tabItemInfo = getTabItemInfo()))
} else {
Modifier
}
val iconContent: @Composable () -> Unit = {
Icon(
imageVector = icon,
modifier = Modifier.size(if (textAlignment == TabTextAlignment.NO_TEXT) 28.dp else 24.dp),
contentDescription = if (textAlignment == TabTextAlignment.NO_TEXT) title else null,
tint = iconColor
)
}
if (textAlignment == TabTextAlignment.HORIZONTAL) {
ConstraintLayout(
modifier = modifier
.then(clickableModifier)
.background(backgroundColor)
.padding(top = 8.dp, start = 4.dp, bottom = 4.dp, end = 8.dp)
.then(widthModifier)
)
{
val (iconConstrain, textConstrain, badgeConstrain) = createRefs()
Box(modifier = Modifier.constrainAs(iconConstrain) {
start.linkTo(parent.start)
end.linkTo(textConstrain.start)
}
if (accessory != null) {
Box(modifier = Modifier
.constrainAs(badgeConstrain) {
start.linkTo(textConstrain.end)
end.linkTo(parent.end)
}
) {
iconContent()
accessory()
}
}
createHorizontalChain(
iconConstrain,
textConstrain,
badgeConstrain,
chainStyle = ChainStyle.Packed
)
}
} else {
val badgeWithIcon: @Composable () -> Unit = {
Layout(
{
Box(
modifier = Modifier.layoutId("anchor"),
contentAlignment = Alignment.Center
) {
iconContent()
}
Box(modifier = Modifier.layoutId("badge")) {
if (accessory != null)
accessory()
}
}
) { measurables, constraints ->
val badgePlaceable = measurables.first { it.layoutId == "badge" }.measure(
// Measure with loose constraints for height as we don't want the text to take up more
// space than it needs.
constraints.copy(minHeight = 0)
)
val anchorPlaceable =
measurables.first { it.layoutId == "anchor" }.measure(constraints)
// Use the width of the badge to infer whether it has any content (based on radius used
// in [Badge]) and determine its horizontal offset.
val hasContent = badgePlaceable.width > 16.dp.roundToPx()
val contentOffset = if (hasContent) -2.dp.roundToPx() else 0
val badgeHorizontalOffset = -anchorPlaceable.width / 2 + contentOffset
val badgeVerticalOffset = (-4).dp
val totalWidth = anchorPlaceable.width
val totalHeight = anchorPlaceable.height
layout(
totalWidth,
totalHeight
) {
anchorPlaceable.placeRelative(0, 0)
val badgeX = anchorPlaceable.width + badgeHorizontalOffset
val badgeY = badgeVerticalOffset.roundToPx()
badgePlaceable.placeRelative(badgeX, badgeY)
}
}
}
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = modifier
.then(clickableModifier)
.background(backgroundColor)
.padding(top = 8.dp, start = 8.dp, bottom = 4.dp, end = 8.dp)
.then(widthModifier)
) {
badgeWithIcon()
if (textAlignment == TabTextAlignment.VERTICAL) {
Spacer(modifier = Modifier.height(2.dp))
Text(
text = title,
modifier = Modifier
.constrainAs(textConstrain) {
start.linkTo(iconConstrain.end)
end.linkTo(badgeConstrain.start)
width = Dimension.preferredWrapContent
}
.padding(start = 8.dp),
color = textColor,
textAlign = TextAlign.Center,
overflow = TextOverflow.Ellipsis,
maxLines = 1
textAlign = TextAlign.Center
)
if (accessory != null) {
Box(modifier = Modifier
.constrainAs(badgeConstrain) {
start.linkTo(textConstrain.end)
end.linkTo(parent.end)
}
) {
accessory()
}
}
createHorizontalChain(
iconConstrain,
textConstrain,
badgeConstrain,
chainStyle = ChainStyle.Packed
)
}
} else {
val badgeWithIcon: @Composable () -> Unit = {
Layout(
{
Box(
modifier = Modifier.layoutId("anchor"),
contentAlignment = Alignment.Center
) {
iconContent()
}
Box(modifier = Modifier.layoutId("badge")) {
if (accessory != null)
accessory()
}
}
) { measurables, constraints ->
val badgePlaceable = measurables.first { it.layoutId == "badge" }.measure(
// Measure with loose constraints for height as we don't want the text to take up more
// space than it needs.
constraints.copy(minHeight = 0)
)
val anchorPlaceable =
measurables.first { it.layoutId == "anchor" }.measure(constraints)
// Use the width of the badge to infer whether it has any content (based on radius used
// in [Badge]) and determine its horizontal offset.
val hasContent = badgePlaceable.width > 16.dp.roundToPx()
val contentOffset = if (hasContent) -2.dp.roundToPx() else 0
val badgeHorizontalOffset = -anchorPlaceable.width / 2 + contentOffset
val badgeVerticalOffset = (-4).dp
val totalWidth = anchorPlaceable.width
val totalHeight = anchorPlaceable.height
layout(
totalWidth,
totalHeight
) {
anchorPlaceable.placeRelative(0, 0)
val badgeX = anchorPlaceable.width + badgeHorizontalOffset
val badgeY = badgeVerticalOffset.roundToPx()
badgePlaceable.placeRelative(badgeX, badgeY)
}
}
}
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = modifier
.then(clickableModifier)
.background(backgroundColor)
.padding(top = 8.dp, start = 8.dp, bottom = 4.dp, end = 8.dp)
.then(widthModifier)
) {
badgeWithIcon()
if (textAlignment == TabTextAlignment.VERTICAL) {
Spacer(modifier = Modifier.height(2.dp))
Text(
text = title,
color = textColor,
textAlign = TextAlign.Center
)
}
}
}
}

Просмотреть файл

@ -6,11 +6,8 @@ import androidx.compose.foundation.border
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.LocalTextStyle
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@ -26,19 +23,6 @@ import com.microsoft.fluentui.theme.token.controlTokens.BadgeTokens
import com.microsoft.fluentui.theme.token.controlTokens.BadgeType
import com.microsoft.fluentui.util.dpToPx
private val LocalBadgeInfo = compositionLocalOf { BadgeInfo(BadgeType.Character) }
private val LocalBadgeToken = compositionLocalOf { BadgeTokens() }
@Composable
private fun getBadgeInfo(): BadgeInfo {
return LocalBadgeInfo.current
}
@Composable
private fun getBadgeTokens(): BadgeTokens {
return LocalBadgeToken.current
}
/**
* Badge represents dynamic information such as a number of pending requests on tab.
* @param text Text to display, if not provided then it appear as dot.
@ -51,69 +35,65 @@ private fun getBadgeTokens(): BadgeTokens {
@OptIn(ExperimentalTextApi::class)
@Composable
fun Badge(
text: String? = null,
modifier: Modifier = Modifier,
text: String? = null,
badgeType: BadgeType = BadgeType.List,
badgeTokens: BadgeTokens? = null
) {
val token = badgeTokens
?: FluentTheme.controlTokens.tokens[ControlTokens.ControlType.Badge] as BadgeTokens
CompositionLocalProvider(
LocalBadgeToken provides token,
LocalBadgeInfo provides BadgeInfo(badgeType)
) {
val background = getBadgeTokens().backgroundColor(badgeInfo = getBadgeInfo())
val borderStroke = getBadgeTokens().borderStroke(badgeInfo = getBadgeInfo())
if (text.isNullOrEmpty()) {
Box(
modifier = modifier
.defaultMinSize(16.dp, 16.dp)
val badgeInfo = BadgeInfo(badgeType)
val background = token.backgroundColor(badgeInfo = badgeInfo)
val borderStroke = token.borderStroke(badgeInfo = badgeInfo)
if (text.isNullOrEmpty()) {
Box(
modifier = modifier
.defaultMinSize(16.dp, 16.dp)
) {
Canvas(
modifier = Modifier
.padding(start = 5.dp, end = 3.dp, top = 3.dp, bottom = 5.dp)
.sizeIn(minWidth = 8.dp, minHeight = 8.dp)
) {
Canvas(
modifier = Modifier
.padding(start = 5.dp, end = 3.dp, top = 3.dp, bottom = 5.dp)
.sizeIn(minWidth = 8.dp, minHeight = 8.dp)
) {
drawCircle(
brush = borderStroke.brush,
radius = dpToPx(borderStroke.width + 4.dp)
)
drawCircle(
color = background,
style = Fill,
radius = dpToPx(4.dp)
)
}
}
} else {
val textColor = getBadgeTokens().textColor(badgeInfo = getBadgeInfo())
val typography = getBadgeTokens().typography(badgeInfo = getBadgeInfo())
val paddingValues = getBadgeTokens().padding(badgeInfo = getBadgeInfo())
Row(
modifier
.requiredHeight(if (badgeType == BadgeType.Character) 20.dp else 27.dp)
.border(borderStroke.width, borderStroke.brush, CircleShape)
.padding(0.5.dp) //TODO to check fix for https://issuetracker.google.com/issues/228985905
.background(background, CircleShape)
.clip(RoundedCornerShape(100.dp)),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
Text(
text,
modifier = Modifier.padding(paddingValues),
color = textColor,
style = typography.merge(
TextStyle(
platformStyle = PlatformTextStyle(
includeFontPadding = false
)
)
)
drawCircle(
brush = borderStroke.brush,
radius = dpToPx(borderStroke.width + 4.dp)
)
drawCircle(
color = background,
style = Fill,
radius = dpToPx(4.dp)
)
}
}
} else {
val textColor = token.textColor(badgeInfo = badgeInfo)
val typography = token.typography(badgeInfo = badgeInfo)
val paddingValues = token.padding(badgeInfo = badgeInfo)
Row(
modifier
.requiredHeight(if (badgeType == BadgeType.Character) 20.dp else 27.dp)
.border(borderStroke.width, borderStroke.brush, CircleShape)
.padding(0.5.dp) //TODO to check fix for https://issuetracker.google.com/issues/228985905
.background(background, CircleShape)
.clip(RoundedCornerShape(100.dp)),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
Text(
text,
modifier = Modifier.padding(paddingValues),
color = textColor,
style = typography.merge(
TextStyle(
platformStyle = PlatformTextStyle(
includeFontPadding = false
)
)
)
)
}
}
}

Просмотреть файл

@ -38,17 +38,14 @@ import com.microsoft.fluentui.tokenized.segmentedcontrols.PillMetaData
import kotlin.math.roundToInt
import androidx.compose.material.Icon as MaterialIcon
private val LocalCardNudgeTokens = compositionLocalOf { CardNudgeTokens() }
private val LocalCardNudgeInfo = compositionLocalOf { CardNudgeInfo() }
// TAGS FOR TESTING
private val CARDNUDGE = "CardNudge"
private val ICON = "ICON"
private val ACCENT_ICON = "Accent Icon"
private val ACCENT_TEXT = "Accent Text"
private val SUBTITLE = "Subtitle"
private val ACTION_BUTTON = "Action Button"
private val DISMISS_BUTTON = "Dismiss Button"
private const val CARD_NUDGE = "CardNudge"
private const val ICON = "ICON"
private const val ACCENT_ICON = "Accent Icon"
private const val ACCENT_TEXT = "Accent Text"
private const val SUBTITLE = "Subtitle"
private const val ACTION_BUTTON = "Action Button"
private const val DISMISS_BUTTON = "Dismiss Button"
class CardNudgeMetaData(
val message: String,
@ -89,206 +86,192 @@ fun CardNudge(
val token = cardNudgeTokens
?: FluentTheme.controlTokens.tokens[ControlTokens.ControlType.CardNudge] as CardNudgeTokens
CompositionLocalProvider(
LocalCardNudgeTokens provides token,
LocalCardNudgeInfo provides CardNudgeInfo()
) {
val animationSpec = TweenSpec<Float>(durationMillis = 300)
val state = rememberSwipeableState(initialValue = SwipeGesture.NONE, animationSpec) { true }
val cardNudgeInfo = CardNudgeInfo()
val animationSpec = TweenSpec<Float>(durationMillis = 300)
val state = rememberSwipeableState(initialValue = SwipeGesture.NONE, animationSpec) { true }
BoxWithConstraints {
val maxWidth = constraints.maxWidth.toFloat()
val shape = RoundedCornerShape(12.dp)
BoxWithConstraints {
val maxWidth = constraints.maxWidth.toFloat()
val shape = RoundedCornerShape(12.dp)
Row(
modifier
.fillMaxWidth()
.offset { IntOffset(state.offset.value.roundToInt(), 0) }
.background(
getCardNudgeTokens().backgroundColor(getCardNudgeInfo()),
shape
)
.then(
if (outlineMode)
Modifier.border(
getCardNudgeTokens().borderSize(getCardNudgeInfo()),
getCardNudgeTokens().borderStrokeColor(getCardNudgeInfo()),
shape
)
else
Modifier
)
.padding(horizontal = 16.dp, vertical = 12.dp)
.swipeable(
state,
anchors = mapOf(
-maxWidth to SwipeGesture.LEFT,
0F to SwipeGesture.NONE,
maxWidth to SwipeGesture.RIGHT
),
thresholds = { _, _ -> FractionalThreshold(0.3F) },
orientation = Orientation.Horizontal,
)
.testTag(CARDNUDGE),
verticalAlignment = Alignment.CenterVertically
) {
LaunchedEffect(state.currentValue) {
if (state.currentValue == SwipeGesture.RIGHT) {
metadata.rightSwipeGesture?.invoke()
state.animateTo(SwipeGesture.NONE)
} else if (state.currentValue == SwipeGesture.LEFT) {
metadata.leftSwipeGesture?.invoke()
state.animateTo(SwipeGesture.NONE)
}
}
if (metadata.icon != null && metadata.icon.isIconAvailable()) {
Box(
modifier = Modifier
.size(getCardNudgeTokens().leftIconBackgroundSize(getCardNudgeInfo()))
.background(
getCardNudgeTokens().iconBackgroundColor(getCardNudgeInfo()),
CircleShape
)
.then(
if (metadata.icon.onClick != null) {
Modifier.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(),
enabled = true,
role = Role.Image,
onClick = metadata.icon.onClick!!
)
} else Modifier
)
.testTag(ICON),
contentAlignment = Alignment.Center
) {
Icon(
metadata.icon,
modifier = Modifier
.size(getCardNudgeTokens().leftIconSize(getCardNudgeInfo())),
tint = getCardNudgeTokens().iconColor(getCardNudgeInfo())
Row(
modifier
.fillMaxWidth()
.offset { IntOffset(state.offset.value.roundToInt(), 0) }
.background(
token.backgroundColor(cardNudgeInfo),
shape
)
.then(
if (outlineMode)
Modifier.border(
token.borderSize(cardNudgeInfo),
token.borderStrokeColor(cardNudgeInfo),
shape
)
}
else
Modifier
)
.padding(horizontal = 16.dp, vertical = 12.dp)
.swipeable(
state,
anchors = mapOf(
-maxWidth to SwipeGesture.LEFT,
0F to SwipeGesture.NONE,
maxWidth to SwipeGesture.RIGHT
),
thresholds = { _, _ -> FractionalThreshold(0.3F) },
orientation = Orientation.Horizontal,
)
.testTag(CARD_NUDGE),
verticalAlignment = Alignment.CenterVertically
) {
LaunchedEffect(state.currentValue) {
if (state.currentValue == SwipeGesture.RIGHT) {
metadata.rightSwipeGesture?.invoke()
state.animateTo(SwipeGesture.NONE)
} else if (state.currentValue == SwipeGesture.LEFT) {
metadata.leftSwipeGesture?.invoke()
state.animateTo(SwipeGesture.NONE)
}
}
Column(
if (metadata.icon != null && metadata.icon.isIconAvailable()) {
Box(
modifier = Modifier
.weight(1F)
.padding(start = 16.dp)
) {
Text(
metadata.message,
style = getCardNudgeTokens().titleTypography(getCardNudgeInfo()),
)
if (!metadata.subTitle.isNullOrBlank() || !metadata.accentText.isNullOrBlank() || metadata.accentIcon != null) {
Row(
horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalAlignment = Alignment.CenterVertically
) {
if (metadata.accentIcon != null) {
Box(
modifier = Modifier
.size(12.dp)
.testTag(ACCENT_ICON),
contentAlignment = Alignment.Center
) {
Icon(
metadata.accentIcon,
tint = getCardNudgeTokens().accentColor(getCardNudgeInfo())
)
}
}
if (!metadata.accentText.isNullOrBlank()) {
Text(
metadata.accentText,
style = getCardNudgeTokens().accentTypography(getCardNudgeInfo()),
modifier = Modifier.testTag(ACCENT_TEXT)
)
}
if (!metadata.subTitle.isNullOrBlank()) {
Text(
metadata.subTitle,
style = getCardNudgeTokens().subtitleTypography(getCardNudgeInfo()),
modifier = Modifier.testTag(SUBTITLE)
)
}
}
}
Spacer(modifier = Modifier.width(4.dp))
}
if (metadata.actionMetaData != null) {
PillButton(
metadata.actionMetaData,
modifier = Modifier.testTag(ACTION_BUTTON),
pillButtonTokens = object : PillButtonTokens() {
@Composable
override fun backgroundColor(pillButtonInfo: PillButtonInfo): StateColor {
return StateColor(
rest = aliasTokens.brandBackgroundColor[AliasTokens.BrandBackgroundColorTokens.BrandBackgroundTint].value(),
pressed = aliasTokens.brandBackgroundColor[AliasTokens.BrandBackgroundColorTokens.BrandBackgroundTint].value(),
focused = aliasTokens.brandBackgroundColor[AliasTokens.BrandBackgroundColorTokens.BrandBackgroundTint].value()
)
}
@Composable
override fun textColor(pillButtonInfo: PillButtonInfo): StateColor {
return StateColor(
rest = aliasTokens.brandForegroundColor[AliasTokens.BrandForegroundColorTokens.BrandForegroundTint].value(),
pressed = aliasTokens.brandForegroundColor[AliasTokens.BrandForegroundColorTokens.BrandForegroundTint].value(),
focused = aliasTokens.brandForegroundColor[AliasTokens.BrandForegroundColorTokens.BrandForegroundTint].value()
)
}
@Composable
override fun typography(pillButtonInfo: PillButtonInfo): TextStyle {
return aliasTokens.typography[AliasTokens.TypographyTokens.Body2Strong]
}
}
)
Spacer(modifier = Modifier.width(12.dp))
}
if (metadata.dismissOnClick != null) {
Box(
modifier = Modifier
.size(20.dp)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(),
enabled = true,
role = Role.Image,
onClickLabel = "Dismiss",
onClick = metadata.dismissOnClick
)
.testTag(DISMISS_BUTTON),
contentAlignment = Alignment.Center
) {
MaterialIcon(
Icons.Filled.Close,
LocalContext.current.resources.getString(R.string.fluentui_dismiss_button),
modifier = Modifier
.size(getCardNudgeTokens().dismissIconSize(getCardNudgeInfo())),
tint = getCardNudgeTokens().iconColor(getCardNudgeInfo())
.size(token.leftIconBackgroundSize(cardNudgeInfo))
.background(
token.iconBackgroundColor(cardNudgeInfo),
CircleShape
)
.then(
if (metadata.icon.onClick != null) {
Modifier.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(),
enabled = true,
role = Role.Image,
onClick = metadata.icon.onClick!!
)
} else Modifier
)
.testTag(ICON),
contentAlignment = Alignment.Center
) {
Icon(
metadata.icon,
modifier = Modifier
.size(token.leftIconSize(cardNudgeInfo)),
tint = token.iconColor(cardNudgeInfo)
)
}
}
Column(
modifier = Modifier
.weight(1F)
.padding(start = 16.dp)
) {
Text(
metadata.message,
style = token.titleTypography(cardNudgeInfo),
)
if (!metadata.subTitle.isNullOrBlank() || !metadata.accentText.isNullOrBlank() || metadata.accentIcon != null) {
Row(
horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalAlignment = Alignment.CenterVertically
) {
if (metadata.accentIcon != null) {
Box(
modifier = Modifier
.size(12.dp)
.testTag(ACCENT_ICON),
contentAlignment = Alignment.Center
) {
Icon(
metadata.accentIcon,
tint = token.accentColor(cardNudgeInfo)
)
}
}
if (!metadata.accentText.isNullOrBlank()) {
Text(
metadata.accentText,
style = token.accentTypography(cardNudgeInfo),
modifier = Modifier.testTag(ACCENT_TEXT)
)
}
if (!metadata.subTitle.isNullOrBlank()) {
Text(
metadata.subTitle,
style = token.subtitleTypography(cardNudgeInfo),
modifier = Modifier.testTag(SUBTITLE)
)
}
}
}
Spacer(modifier = Modifier.width(4.dp))
}
if (metadata.actionMetaData != null) {
PillButton(
metadata.actionMetaData,
modifier = Modifier.testTag(ACTION_BUTTON),
pillButtonTokens = object : PillButtonTokens() {
@Composable
override fun backgroundColor(pillButtonInfo: PillButtonInfo): StateColor {
return StateColor(
rest = aliasTokens.brandBackgroundColor[AliasTokens.BrandBackgroundColorTokens.BrandBackgroundTint].value(),
pressed = aliasTokens.brandBackgroundColor[AliasTokens.BrandBackgroundColorTokens.BrandBackgroundTint].value(),
focused = aliasTokens.brandBackgroundColor[AliasTokens.BrandBackgroundColorTokens.BrandBackgroundTint].value()
)
}
@Composable
override fun textColor(pillButtonInfo: PillButtonInfo): StateColor {
return StateColor(
rest = aliasTokens.brandForegroundColor[AliasTokens.BrandForegroundColorTokens.BrandForegroundTint].value(),
pressed = aliasTokens.brandForegroundColor[AliasTokens.BrandForegroundColorTokens.BrandForegroundTint].value(),
focused = aliasTokens.brandForegroundColor[AliasTokens.BrandForegroundColorTokens.BrandForegroundTint].value()
)
}
@Composable
override fun typography(pillButtonInfo: PillButtonInfo): TextStyle {
return aliasTokens.typography[AliasTokens.TypographyTokens.Body2Strong]
}
}
)
Spacer(modifier = Modifier.width(12.dp))
}
if (metadata.dismissOnClick != null) {
Box(
modifier = Modifier
.size(20.dp)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(),
enabled = true,
role = Role.Image,
onClickLabel = "Dismiss",
onClick = metadata.dismissOnClick
)
.testTag(DISMISS_BUTTON),
contentAlignment = Alignment.Center
) {
MaterialIcon(
Icons.Filled.Close,
LocalContext.current.resources.getString(R.string.fluentui_dismiss_button),
modifier = Modifier
.size(token.dismissIconSize(cardNudgeInfo)),
tint = token.iconColor(cardNudgeInfo)
)
}
}
}
}
}
@Composable
private fun getCardNudgeTokens(): CardNudgeTokens {
return LocalCardNudgeTokens.current
}
@Composable
private fun getCardNudgeInfo(): CardNudgeInfo {
return LocalCardNudgeInfo.current
}

Просмотреть файл

@ -32,15 +32,12 @@ import kotlinx.coroutines.sync.withLock
import kotlin.coroutines.resume
import androidx.compose.material.Icon as MaterialIcon
private val LocalSnackBarTokens = compositionLocalOf { SnackBarTokens() }
private val LocalSnackBarInfo = compositionLocalOf { SnackBarInfo(SnackbarStyle.Neutral) }
// TAGS FOR TESTING
private val SNACKBAR = "Snackbar"
private val ICON = "ICON"
private val SUBTITLE = "Subtitle"
private val ACTION_BUTTON = "Action Button"
private val DISMISS_BUTTON = "Dismiss Button"
private const val SNACKBAR = "Snackbar"
private const val ICON = "ICON"
private const val SUBTITLE = "Subtitle"
private const val ACTION_BUTTON = "Action Button"
private const val DISMISS_BUTTON = "Dismiss Button"
class SnackbarMetadata(
val message: String,
@ -123,140 +120,126 @@ fun Snackbar(
val token = snackbarTokens
?: FluentTheme.controlTokens.tokens[ControlTokens.ControlType.Snackbar] as SnackBarTokens
CompositionLocalProvider(
LocalSnackBarTokens provides token,
LocalSnackBarInfo provides SnackBarInfo(metadata.style, !metadata.subTitle.isNullOrBlank())
) {
NotificationContainer(
notificationMetadata = metadata,
hasIcon = metadata.icon != null,
hasAction = metadata.actionText != null,
duration = metadata.duration
) { alpha, scale ->
Row(
modifier
.graphicsLayer(scaleX = scale.value, scaleY = scale.value, alpha = alpha.value)
.padding(horizontal = 16.dp)
.defaultMinSize(minHeight = 52.dp)
.fillMaxWidth()
.clip(RoundedCornerShape(8.dp))
.background(getSnackBarTokens().backgroundColor(getSnackBarInfo()))
.testTag(SNACKBAR),
verticalAlignment = Alignment.CenterVertically
) {
if (metadata.icon != null && metadata.icon.isIconAvailable()) {
Box(
modifier = Modifier
.testTag(ICON)
.then(
if (metadata.icon.onClick != null) {
Modifier.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(),
enabled = true,
role = Role.Image,
onClick = metadata.icon.onClick!!
)
} else Modifier
)
) {
Icon(
metadata.icon,
modifier = Modifier
.padding(start = 16.dp, top = 12.dp, bottom = 12.dp)
.size(getSnackBarTokens().leftIconSize(getSnackBarInfo())),
tint = getSnackBarTokens().iconColor(getSnackBarInfo())
val snackBarInfo = SnackBarInfo(metadata.style, !metadata.subTitle.isNullOrBlank())
NotificationContainer(
notificationMetadata = metadata,
hasIcon = metadata.icon != null,
hasAction = metadata.actionText != null,
duration = metadata.duration
) { alpha, scale ->
Row(
modifier
.graphicsLayer(scaleX = scale.value, scaleY = scale.value, alpha = alpha.value)
.padding(horizontal = 16.dp)
.defaultMinSize(minHeight = 52.dp)
.fillMaxWidth()
.clip(RoundedCornerShape(8.dp))
.background(token.backgroundColor(snackBarInfo))
.testTag(SNACKBAR),
verticalAlignment = Alignment.CenterVertically
) {
if (metadata.icon != null && metadata.icon.isIconAvailable()) {
Box(
modifier = Modifier
.testTag(ICON)
.then(
if (metadata.icon.onClick != null) {
Modifier.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(),
enabled = true,
role = Role.Image,
onClick = metadata.icon.onClick!!
)
} else Modifier
)
}
) {
Icon(
metadata.icon,
modifier = Modifier
.padding(start = 16.dp, top = 12.dp, bottom = 12.dp)
.size(token.leftIconSize(snackBarInfo)),
tint = token.iconColor(snackBarInfo)
)
}
}
if (metadata.subTitle.isNullOrBlank()) {
if (metadata.subTitle.isNullOrBlank()) {
Text(
text = metadata.message,
modifier = Modifier
.weight(1F)
.padding(start = 16.dp, top = 12.dp, bottom = 12.dp),
style = token.titleTypography(snackBarInfo)
)
} else {
Column(
Modifier
.weight(1F)
.padding(start = 16.dp, top = 12.dp, bottom = 12.dp)
) {
Text(
text = metadata.message,
modifier = Modifier
.weight(1F)
.padding(start = 16.dp, top = 12.dp, bottom = 12.dp),
style = getSnackBarTokens().titleTypography(getSnackBarInfo())
style = token.titleTypography(snackBarInfo)
)
} else {
Column(
Modifier
.weight(1F)
.padding(start = 16.dp, top = 12.dp, bottom = 12.dp)
) {
Text(
text = metadata.message,
style = getSnackBarTokens().titleTypography(getSnackBarInfo())
)
Text(
text = metadata.subTitle,
style = getSnackBarTokens().subtitleTypography(getSnackBarInfo()),
modifier = Modifier.testTag(SUBTITLE)
)
}
}
if (metadata.actionText != null) {
Button(
onClick = { metadata.clicked() },
modifier = Modifier
.testTag(ACTION_BUTTON)
.then(
if (!metadata.enableDismiss)
Modifier.padding(horizontal = 16.dp, vertical = 12.dp)
else
Modifier.padding(start = 16.dp, top = 12.dp, bottom = 12.dp)
),
text = metadata.actionText,
style = ButtonStyle.TextButton,
size = ButtonSize.Small,
buttonTokens = object : ButtonTokens() {
@Composable
override fun textColor(buttonInfo: ButtonInfo): StateColor {
return StateColor(
rest = getSnackBarTokens().iconColor(getSnackBarInfo()),
pressed = getSnackBarTokens().iconColor(getSnackBarInfo()),
focused = getSnackBarTokens().iconColor(getSnackBarInfo()),
)
}
}
Text(
text = metadata.subTitle,
style = token.subtitleTypography(snackBarInfo),
modifier = Modifier.testTag(SUBTITLE)
)
}
}
if (metadata.enableDismiss) {
Box(
modifier = Modifier
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(),
enabled = true,
role = Role.Image,
onClickLabel = "Dismiss",
onClick = { metadata.dismiss() }
if (metadata.actionText != null) {
Button(
onClick = { metadata.clicked() },
modifier = Modifier
.testTag(ACTION_BUTTON)
.then(
if (!metadata.enableDismiss)
Modifier.padding(horizontal = 16.dp, vertical = 12.dp)
else
Modifier.padding(start = 16.dp, top = 12.dp, bottom = 12.dp)
),
text = metadata.actionText,
style = ButtonStyle.TextButton,
size = ButtonSize.Small,
buttonTokens = object : ButtonTokens() {
@Composable
override fun textColor(buttonInfo: ButtonInfo): StateColor {
return StateColor(
rest = token.iconColor(snackBarInfo),
pressed = token.iconColor(snackBarInfo),
focused = token.iconColor(snackBarInfo),
)
.testTag(DISMISS_BUTTON)
) {
MaterialIcon(
Icons.Filled.Close,
"Dismiss",
modifier = Modifier
.padding(start = 12.dp, top = 12.dp, bottom = 12.dp, end = 16.dp)
.size(getSnackBarTokens().dismissIconSize(getSnackBarInfo())),
tint = getSnackBarTokens().iconColor(getSnackBarInfo())
)
}
}
)
}
if (metadata.enableDismiss) {
Box(
modifier = Modifier
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(),
enabled = true,
role = Role.Image,
onClickLabel = "Dismiss",
onClick = { metadata.dismiss() }
)
.testTag(DISMISS_BUTTON)
) {
MaterialIcon(
Icons.Filled.Close,
"Dismiss",
modifier = Modifier
.padding(start = 12.dp, top = 12.dp, bottom = 12.dp, end = 16.dp)
.size(token.dismissIconSize(snackBarInfo)),
tint = token.iconColor(snackBarInfo)
)
}
}
}
}
}
@Composable
private fun getSnackBarTokens(): SnackBarTokens {
return LocalSnackBarTokens.current
}
@Composable
private fun getSnackBarInfo(): SnackBarInfo {
return LocalSnackBarInfo.current
}

Просмотреть файл

@ -8,8 +8,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@ -30,9 +28,6 @@ import com.microsoft.fluentui.theme.token.ControlTokens
import com.microsoft.fluentui.theme.token.FluentIcon
import com.microsoft.fluentui.theme.token.controlTokens.*
val LocalAvatarTokens = compositionLocalOf { AvatarTokens() }
val LocalAvatarInfo = compositionLocalOf { AvatarInfo() }
const val IMAGE_TEST_TAG = "Image"
const val ICON_TEST_TAG = "Icon"
@ -70,144 +65,139 @@ fun Avatar(
?: FluentTheme.controlTokens.tokens[ControlTokens.ControlType.Avatar] as AvatarTokens
val personInitials = person.getInitials()
val avatarInfo = AvatarInfo(
size, AvatarType.Person, person.isActive,
CompositionLocalProvider(
LocalAvatarTokens provides token,
LocalAvatarInfo provides AvatarInfo(
size, AvatarType.Person, person.isActive,
person.status, person.isOOO, person.isImageAvailable(),
personInitials.isNotEmpty(), person.getName(), cutoutStyle
)
val avatarSize = token.avatarSize(avatarInfo)
val backgroundColor = token.backgroundColor(avatarInfo)
val foregroundColor = token.foregroundColor(avatarInfo)
val borders = token.borderStroke(avatarInfo)
val fontTextStyle = token.fontTypography(avatarInfo)
val cutoutCornerRadius = token.cutoutCornerRadius(avatarInfo)
val cutoutBackgroundColor =
token.cutoutBackgroundColor(avatarInfo = avatarInfo)
val cutoutBorderColor = token.cutoutBorderColor(avatarInfo = avatarInfo)
val cutoutIconSize = token.cutoutIconSize(avatarInfo = avatarInfo)
val isCutoutEnabled = (cutoutIconDrawable != null || cutoutIconImageVector != null)
var isImageOrInitialsAvailable = true
person.status, person.isOOO, person.isImageAvailable(),
personInitials.isNotEmpty(), person.getName(), cutoutStyle
)
Box(modifier = Modifier
.semantics(mergeDescendants = true) {
contentDescription = "${person.getName()}. " +
"${if (enablePresence) "Status, ${person.status}," else ""} " +
"${if (enablePresence && person.isOOO) "Out Of Office," else ""} " +
if (enableActivityRings) {
if (person.isActive) "Active" else "Inactive"
} else ""
}
) {
val avatarSize = getAvatarTokens().avatarSize(getAvatarInfo())
val backgroundColor = getAvatarTokens().backgroundColor(getAvatarInfo())
val foregroundColor = getAvatarTokens().foregroundColor(getAvatarInfo())
val borders = getAvatarTokens().borderStroke(getAvatarInfo())
val fontTextStyle = getAvatarTokens().fontTypography(getAvatarInfo())
val cutoutCornerRadius = getAvatarTokens().cutoutCornerRadius(getAvatarInfo())
val cutoutBackgroundColor =
getAvatarTokens().cutoutBackgroundColor(avatarInfo = getAvatarInfo())
val cutoutBorderColor = getAvatarTokens().cutoutBorderColor(avatarInfo = getAvatarInfo())
val cutoutIconSize = getAvatarTokens().cutoutIconSize(avatarInfo = getAvatarInfo())
val isCutoutEnabled = (cutoutIconDrawable != null || cutoutIconImageVector != null)
var isImageOrInitialsAvailable = true
Box(modifier = Modifier
.semantics(mergeDescendants = true) {
contentDescription = "${person.getName()}. " +
"${if (enablePresence) "Status, ${person.status}," else ""} " +
"${if (enablePresence && person.isOOO) "Out Of Office," else ""} " +
if (enableActivityRings) {
if (person.isActive) "Active" else "Inactive"
} else ""
}
Box(
Modifier
.then(modifier)
.requiredSize(avatarSize)
.background(backgroundColor, CircleShape), contentAlignment = Alignment.Center
) {
Box(
Modifier
.then(modifier)
.requiredSize(avatarSize)
.background(backgroundColor, CircleShape), contentAlignment = Alignment.Center
) {
when {
person.image != null -> {
Image(
painter = painterResource(person.image), null,
modifier = Modifier
.size(avatarSize)
.clip(CircleShape)
.semantics {
testTag = IMAGE_TEST_TAG
}
)
}
person.imageBitmap != null -> {
Image(
bitmap = person.imageBitmap, null,
modifier = Modifier
.size(avatarSize)
.clip(CircleShape)
.semantics {
testTag = IMAGE_TEST_TAG
}
)
}
personInitials.isNotEmpty() -> {
Text(personInitials,
style = fontTextStyle,
color = foregroundColor,
modifier = Modifier
.clearAndSetSemantics { })
}
else -> {
isImageOrInitialsAvailable = false
Icon(
getAvatarTokens().icon(getAvatarInfo()),
null,
modifier = Modifier
.background(backgroundColor, CircleShape)
.semantics {
testTag = ICON_TEST_TAG
},
tint = foregroundColor,
)
}
}
if (enableActivityRings)
ActivityRing(radius = avatarSize / 2, borders)
if (isCutoutEnabled && isImageOrInitialsAvailable && cutoutIconSize > 0.dp) {
Box(
modifier = Modifier
.offset(6.dp, 6.dp)
.align(Alignment.BottomEnd)
.clip(shape = RoundedCornerShape(size = cutoutCornerRadius))
) {
if (cutoutIconDrawable != null) {
Image(
painter = painterResource(cutoutIconDrawable),
modifier = Modifier
.background(cutoutBackgroundColor)
.border(
2.dp,
cutoutBorderColor,
RoundedCornerShape(cutoutCornerRadius)
)
.padding(4.dp)
.size(cutoutIconSize),
contentDescription = cutoutContentDescription
)
} else if (cutoutIconImageVector != null) {
Image(
imageVector = cutoutIconImageVector,
modifier = Modifier
.background(cutoutBackgroundColor)
.border(
2.dp,
cutoutBorderColor,
RoundedCornerShape(cutoutCornerRadius)
)
.padding(4.dp)
.size(cutoutIconSize),
contentDescription = cutoutContentDescription
)
}
}
}
if (!isCutoutEnabled && enablePresence) {
val presenceOffset: DpOffset = getAvatarTokens().presenceOffset(getAvatarInfo())
val image: FluentIcon = getAvatarTokens().presenceIcon(getAvatarInfo())
when {
person.image != null -> {
Image(
image.value(themeMode),
null,
Modifier
.align(Alignment.BottomEnd)
// Adding 2.dp to both side to incorporate border which is an image in Fluent Android.
.offset(presenceOffset.x + 2.dp, -presenceOffset.y + 2.dp)
painter = painterResource(person.image), null,
modifier = Modifier
.size(avatarSize)
.clip(CircleShape)
.semantics {
testTag = IMAGE_TEST_TAG
}
)
}
person.imageBitmap != null -> {
Image(
bitmap = person.imageBitmap, null,
modifier = Modifier
.size(avatarSize)
.clip(CircleShape)
.semantics {
testTag = IMAGE_TEST_TAG
}
)
}
personInitials.isNotEmpty() -> {
Text(personInitials,
style = fontTextStyle,
color = foregroundColor,
modifier = Modifier
.clearAndSetSemantics { })
}
else -> {
isImageOrInitialsAvailable = false
Icon(
token.icon(avatarInfo),
null,
modifier = Modifier
.background(backgroundColor, CircleShape)
.semantics {
testTag = ICON_TEST_TAG
},
tint = foregroundColor,
)
}
}
if (enableActivityRings)
ActivityRing(radius = avatarSize / 2, borders)
if (isCutoutEnabled && isImageOrInitialsAvailable && cutoutIconSize > 0.dp) {
Box(
modifier = Modifier
.offset(6.dp, 6.dp)
.align(Alignment.BottomEnd)
.clip(shape = RoundedCornerShape(size = cutoutCornerRadius))
) {
if (cutoutIconDrawable != null) {
Image(
painter = painterResource(cutoutIconDrawable),
modifier = Modifier
.background(cutoutBackgroundColor)
.border(
2.dp,
cutoutBorderColor,
RoundedCornerShape(cutoutCornerRadius)
)
.padding(4.dp)
.size(cutoutIconSize),
contentDescription = cutoutContentDescription
)
} else if (cutoutIconImageVector != null) {
Image(
imageVector = cutoutIconImageVector,
modifier = Modifier
.background(cutoutBackgroundColor)
.border(
2.dp,
cutoutBorderColor,
RoundedCornerShape(cutoutCornerRadius)
)
.padding(4.dp)
.size(cutoutIconSize),
contentDescription = cutoutContentDescription
)
}
}
}
if (!isCutoutEnabled && enablePresence) {
val presenceOffset: DpOffset = token.presenceOffset(avatarInfo)
val image: FluentIcon = token.presenceIcon(avatarInfo)
Image(
image.value(themeMode),
null,
Modifier
.align(Alignment.BottomEnd)
// Adding 2.dp to both side to incorporate border which is an image in Fluent Android.
.offset(presenceOffset.x + 2.dp, -presenceOffset.y + 2.dp)
)
}
}
}
@ -234,77 +224,73 @@ fun Avatar(
val token = avatarToken
?: FluentTheme.controlTokens.tokens[ControlTokens.ControlType.Avatar] as AvatarTokens
CompositionLocalProvider(
LocalAvatarTokens provides token,
LocalAvatarInfo provides AvatarInfo(
size, AvatarType.Group,
isImageAvailable = group.isImageAvailable(),
hasValidInitials = group.getInitials().isNotEmpty(),
calculatedColorKey = group.groupName
)
val avatarInfo = AvatarInfo(
size, AvatarType.Group,
isImageAvailable = group.isImageAvailable(),
hasValidInitials = group.getInitials().isNotEmpty(),
calculatedColorKey = group.groupName
)
val avatarSize = token.avatarSize(avatarInfo)
val cornerRadius = token.cornerRadius(avatarInfo)
val fontTextStyle = token.fontTypography(avatarInfo)
val backgroundColor = token.backgroundColor(avatarInfo)
val foregroundColor = token.foregroundColor(avatarInfo)
var membersList = ""
for (person in group.members)
membersList += (person.firstName + person.lastName + "\n")
Box(
modifier
.requiredSize(avatarSize)
.semantics(mergeDescendants = false) {
contentDescription =
"Group Name ${group.getName()} ${group.members.size} members. $membersList"
}, contentAlignment = Alignment.Center
) {
val avatarSize = getAvatarTokens().avatarSize(getAvatarInfo())
val cornerRadius = getAvatarTokens().cornerRadius(getAvatarInfo())
val fontTextStyle = getAvatarTokens().fontTypography(getAvatarInfo())
val backgroundColor = getAvatarTokens().backgroundColor(getAvatarInfo())
val foregroundColor = getAvatarTokens().foregroundColor(getAvatarInfo())
var membersList = ""
for (person in group.members)
membersList += (person.firstName + person.lastName + "\n")
Box(
modifier
.requiredSize(avatarSize)
.semantics(mergeDescendants = false) {
contentDescription =
"Group Name ${group.getName()} ${group.members.size} members. $membersList"
}, contentAlignment = Alignment.Center
Modifier
.clip(RoundedCornerShape(cornerRadius))
.background(backgroundColor)
.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Box(
Modifier
.clip(RoundedCornerShape(cornerRadius))
.background(backgroundColor)
.fillMaxSize(),
contentAlignment = Alignment.Center
) {
if (group.image != null) {
Image(
painter = painterResource(group.image),
contentDescription = null,
modifier = Modifier
.size(avatarSize)
.clip(RoundedCornerShape(cornerRadius))
.semantics {
testTag = IMAGE_TEST_TAG
}
)
} else if (group.imageBitmap != null) {
Image(
bitmap = group.imageBitmap,
contentDescription = null,
modifier = Modifier
.size(avatarSize)
.clip(RoundedCornerShape(cornerRadius))
.semantics {
testTag = IMAGE_TEST_TAG
}
)
} else if (group.groupName.isNotEmpty()) {
Text(group.getInitials(),
style = fontTextStyle,
color = foregroundColor,
modifier = Modifier.clearAndSetSemantics { })
} else {
Icon(
getAvatarTokens().icon(getAvatarInfo()),
null,
modifier = Modifier.semantics {
testTag = ICON_TEST_TAG
},
tint = foregroundColor
)
}
if (group.image != null) {
Image(
painter = painterResource(group.image),
contentDescription = null,
modifier = Modifier
.size(avatarSize)
.clip(RoundedCornerShape(cornerRadius))
.semantics {
testTag = IMAGE_TEST_TAG
}
)
} else if (group.imageBitmap != null) {
Image(
bitmap = group.imageBitmap,
contentDescription = null,
modifier = Modifier
.size(avatarSize)
.clip(RoundedCornerShape(cornerRadius))
.semantics {
testTag = IMAGE_TEST_TAG
}
)
} else if (group.groupName.isNotEmpty()) {
Text(group.getInitials(),
style = fontTextStyle,
color = foregroundColor,
modifier = Modifier.clearAndSetSemantics { })
} else {
Icon(
token.icon(avatarInfo),
null,
modifier = Modifier.semantics {
testTag = ICON_TEST_TAG
},
tint = foregroundColor
)
}
}
}
@ -330,50 +316,36 @@ fun Avatar(
val token = avatarToken
?: FluentTheme.controlTokens.tokens[ControlTokens.ControlType.Avatar] as AvatarTokens
CompositionLocalProvider(
LocalAvatarTokens provides token,
LocalAvatarInfo provides AvatarInfo(size, AvatarType.Overflow)
val avatarInfo = AvatarInfo(size, AvatarType.Overflow)
val avatarSize = token.avatarSize(avatarInfo)
val borders = token.borderStroke(avatarInfo)
val fontTextStyle = token.fontTypography(avatarInfo)
Box(
modifier
.requiredSize(avatarSize)
.semantics(mergeDescendants = false) {
contentDescription = "+ $overflowCount Avatar More"
}, contentAlignment = Alignment.Center
) {
val avatarSize = getAvatarTokens().avatarSize(getAvatarInfo())
val borders = getAvatarTokens().borderStroke(getAvatarInfo())
val fontTextStyle = getAvatarTokens().fontTypography(getAvatarInfo())
Box(
modifier
.requiredSize(avatarSize)
.semantics(mergeDescendants = false) {
contentDescription = "+ $overflowCount Avatar More"
}, contentAlignment = Alignment.Center
Modifier
.clip(CircleShape)
.background(token.backgroundColor(avatarInfo))
.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Box(
Modifier
.clip(CircleShape)
.background(getAvatarTokens().backgroundColor(getAvatarInfo()))
.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Text("+${overflowCount}",
style = fontTextStyle,
color = getAvatarTokens().foregroundColor(getAvatarInfo()),
modifier = Modifier.clearAndSetSemantics { })
}
if (enableActivityRings)
ActivityRing(radius = avatarSize / 2, borders)
Text("+${overflowCount}",
style = fontTextStyle,
color = token.foregroundColor(avatarInfo),
modifier = Modifier.clearAndSetSemantics { })
}
if (enableActivityRings)
ActivityRing(radius = avatarSize / 2, borders)
}
}
@Composable
fun getAvatarTokens(): AvatarTokens {
return LocalAvatarTokens.current
}
@Composable
fun getAvatarInfo(): AvatarInfo {
return LocalAvatarInfo.current
}
@Composable
fun ActivityRing(radius: Dp, borders: List<BorderStroke>) {
val firstBorderMid = with(LocalDensity.current) { borders[0].width.toPx() / 2 }

Просмотреть файл

@ -13,7 +13,9 @@ import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.Text
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.runtime.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
@ -35,19 +37,6 @@ import com.microsoft.fluentui.util.getStringResource
import kotlinx.coroutines.launch
import kotlin.math.max
val LocalAvatarCarouselTokens = compositionLocalOf { AvatarCarouselTokens() }
val LocalAvatarCarouselInfo = compositionLocalOf { AvatarCarouselInfo() }
@Composable
fun getAvatarCarouselTokens(): AvatarCarouselTokens {
return LocalAvatarCarouselTokens.current
}
@Composable
fun getAvatarCarouselInfo(): AvatarCarouselInfo {
return LocalAvatarCarouselInfo.current
}
/**
* Generate an AvatarCarousel. This is a horizontally scrollable bar which is made up of [AvatarCarouselItem].
* Avatar Carousel internally is a group of [AvatarCarouselItem] which can be used to create onClick based Avatar buttons.
@ -70,29 +59,27 @@ fun AvatarCarousel(
) {
val token = avatarCarouselTokens
?: FluentTheme.controlTokens.tokens[ControlTokens.ControlType.AvatarCarousel] as AvatarCarouselTokens
CompositionLocalProvider(
LocalAvatarCarouselTokens provides token,
LocalAvatarCarouselInfo provides AvatarCarouselInfo(size)
) {
val statusString = getStringResource(R.string.Status)
val outOfOfficeString = getStringResource(R.string.Out_Of_Office)
val activeString = getStringResource(R.string.Active)
val inActiveString = getStringResource(R.string.Inactive)
val scope = rememberCoroutineScope()
val lazyListState = rememberLazyListState()
val avatarSize = getAvatarCarouselTokens().avatarSize(getAvatarCarouselInfo())
val textStyle =
getAvatarCarouselTokens().textTypography(getAvatarCarouselInfo())
val subTextStyle =
getAvatarCarouselTokens().subTextTypography(getAvatarCarouselInfo())
val avatarTextPadding = getAvatarCarouselTokens().padding(getAvatarCarouselInfo())
val bottomPadding = if (size == AvatarCarouselSize.Small) 8.dp else 0.dp
val textColor = getAvatarCarouselTokens().textColor(getAvatarCarouselInfo())
val subTextColor =
getAvatarCarouselTokens().subTextColor(getAvatarCarouselInfo())
val avatarCarouselInfo = AvatarCarouselInfo(size)
val statusString = getStringResource(R.string.Status)
val outOfOfficeString = getStringResource(R.string.Out_Of_Office)
val activeString = getStringResource(R.string.Active)
val inActiveString = getStringResource(R.string.Inactive)
val scope = rememberCoroutineScope()
val lazyListState = rememberLazyListState()
val avatarSize = token.avatarSize(avatarCarouselInfo)
val textStyle =
token.textTypography(avatarCarouselInfo)
val subTextStyle =
token.subTextTypography(avatarCarouselInfo)
val avatarTextPadding = token.padding(avatarCarouselInfo)
val bottomPadding = if (size == AvatarCarouselSize.Small) 8.dp else 0.dp
val textColor = token.textColor(avatarCarouselInfo)
val subTextColor =
token.subTextColor(avatarCarouselInfo)
LazyRow(state = lazyListState,
LazyRow(
state = lazyListState,
modifier = modifier.draggable(
orientation = Orientation.Horizontal,
state = rememberDraggableState { delta ->
@ -100,100 +87,101 @@ fun AvatarCarousel(
lazyListState.scrollBy(-delta)
}
},
)) {
itemsIndexed(avatarList) { index, item ->
val backgroundColor =
getAvatarCarouselTokens().backgroundColor(getAvatarCarouselInfo())
.getColorByState(
enabled = item.enabled,
selected = false,
interactionSource = remember { MutableInteractionSource() }
)
val nameString =
if (size == AvatarCarouselSize.Large) "${item.person.getName()}. " else "${item.person.firstName}. "
Column(
Modifier
.onFocusEvent { focusState ->
if (focusState.isFocused) {
scope.launch {
lazyListState.animateScrollToItem(
max(
0,
index - 2
)
)
) {
itemsIndexed(avatarList) { index, item ->
val backgroundColor =
token.backgroundColor(avatarCarouselInfo)
.getColorByState(
enabled = item.enabled,
selected = false,
interactionSource = remember { MutableInteractionSource() }
)
val nameString =
if (size == AvatarCarouselSize.Large) "${item.person.getName()}. " else "${item.person.firstName}. "
Column(
Modifier
.onFocusEvent { focusState ->
if (focusState.isFocused) {
scope.launch {
lazyListState.animateScrollToItem(
max(
0,
index - 2
)
}
)
}
}
.background(backgroundColor)
.requiredWidth(88.dp)
.alpha(if (item.enabled) 1f else 0.7f)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(),
onClickLabel = null,
enabled = item.enabled,
onClick = item.onItemClick ?: {},
role = Role.Button
).testTag("item $index")
.clearAndSetSemantics {
contentDescription =
nameString + "${if (enablePresence) "${statusString}, ${item.person.status}," else ""} " +
"${if (enablePresence && item.person.isOOO) "${outOfOfficeString}," else ""} " +
if (item.enableActivityRing) {
if (item.person.isActive) activeString else inActiveString
} else ""
},
horizontalAlignment = Alignment.CenterHorizontally
) {
Avatar(
modifier = Modifier
.padding(top = 8.dp),
person = item.person,
size = avatarSize,
avatarToken = avatarTokens,
enablePresence = enablePresence,
enableActivityRings = item.enableActivityRing
}
.background(backgroundColor)
.requiredWidth(88.dp)
.alpha(if (item.enabled) 1f else 0.7f)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(),
onClickLabel = null,
enabled = item.enabled,
onClick = item.onItemClick ?: {},
role = Role.Button
)
.testTag("item $index")
.clearAndSetSemantics {
contentDescription =
nameString + "${if (enablePresence) "${statusString}, ${item.person.status}," else ""} " +
"${if (enablePresence && item.person.isOOO) "${outOfOfficeString}," else ""} " +
if (item.enableActivityRing) {
if (item.person.isActive) activeString else inActiveString
} else ""
},
horizontalAlignment = Alignment.CenterHorizontally
) {
Avatar(
modifier = Modifier
.padding(top = 8.dp),
person = item.person,
size = avatarSize,
avatarToken = avatarTokens,
enablePresence = enablePresence,
enableActivityRings = item.enableActivityRing
)
Row(
Modifier
.padding(
start = 2.dp,
end = 2.dp,
top = avatarTextPadding,
bottom = bottomPadding
)
.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
Text(
modifier = Modifier.clearAndSetSemantics { },
text = item.person.firstName,
color = if (item.enabled) textColor.rest else textColor.disabled,
style = textStyle,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
if (size == AvatarCarouselSize.Large) {
Row(
Modifier
.padding(
start = 2.dp,
end = 2.dp,
top = avatarTextPadding,
bottom = bottomPadding
)
.fillMaxWidth(),
.fillMaxWidth()
.padding(start = 2.dp, end = 2.dp, bottom = 8.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
Text(
modifier = Modifier.clearAndSetSemantics { },
text = item.person.firstName,
color = if (item.enabled) textColor.rest else textColor.disabled,
style = textStyle,
text = item.person.lastName,
color = if (item.enabled) subTextColor.rest else subTextColor.disabled,
style = subTextStyle,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
if (size == AvatarCarouselSize.Large) {
Row(
Modifier
.fillMaxWidth()
.padding(start = 2.dp, end = 2.dp, bottom = 8.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
Text(
modifier = Modifier.clearAndSetSemantics { },
text = item.person.lastName,
color = if (item.enabled) subTextColor.rest else subTextColor.disabled,
style = subTextStyle,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
}
}
}

Просмотреть файл

@ -2,8 +2,6 @@ package com.microsoft.fluentui.tokenized.persona
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.platform.LocalDensity
@ -15,9 +13,6 @@ import com.microsoft.fluentui.theme.token.ControlTokens
import com.microsoft.fluentui.theme.token.controlTokens.*
import java.lang.Math.max
val LocalAvatarGroupTokens = compositionLocalOf { AvatarGroupTokens() }
val LocalAvatarGroupInfo = compositionLocalOf { AvatarGroupInfo() }
const val DEFAULT_MAX_AVATAR = 5
/**
@ -59,89 +54,75 @@ fun AvatarGroup(
if (style == AvatarGroupStyle.Stack)
enablePresence = false
CompositionLocalProvider(
LocalAvatarGroupTokens provides token,
LocalAvatarGroupInfo provides AvatarGroupInfo(size, style)
) {
val spacing: MutableList<Int> = mutableListOf()
for (i in 0 until visibleAvatar) {
val person = group.members[i]
if (i != 0) {
spacing.add(with(LocalDensity.current) {
getAvatarGroupTokens().spacing(getAvatarGroupInfo(), person.isActive)
.roundToPx()
})
}
}
if (group.members.size > visibleAvatar || group.members.isEmpty()) {
val avatarGroupInfo = AvatarGroupInfo(size, style)
val spacing: MutableList<Int> = mutableListOf()
for (i in 0 until visibleAvatar) {
val person = group.members[i]
if (i != 0) {
spacing.add(with(LocalDensity.current) {
getAvatarGroupTokens().spacing(getAvatarGroupInfo(), false).roundToPx()
token.spacing(avatarGroupInfo, person.isActive)
.roundToPx()
})
}
}
if (group.members.size > visibleAvatar || group.members.isEmpty()) {
spacing.add(with(LocalDensity.current) {
token.spacing(avatarGroupInfo, false).roundToPx()
})
}
val semanticModifier: Modifier = Modifier.semantics(true) {
contentDescription =
"Group Name: ${group.groupName}. Total ${group.members.size} members. "
val semanticModifier: Modifier = Modifier.semantics(true) {
contentDescription =
"Group Name: ${group.groupName}. Total ${group.members.size} members. "
}
Layout(modifier = modifier
.padding(8.dp)
.then(semanticModifier), content = {
for (i in 0 until visibleAvatar) {
val person = group.members[i]
var paddingModifier: Modifier = Modifier
if (style == AvatarGroupStyle.Pile && person.isActive) {
val padding = token.pilePadding(avatarGroupInfo)
paddingModifier = paddingModifier.padding(start = padding, end = padding)
}
Avatar(
person,
modifier = paddingModifier,
size = size,
enableActivityRings = true,
enablePresence = enablePresence,
avatarToken = avatarToken
)
}
if (group.members.size > visibleAvatar || group.members.isEmpty()) {
Avatar(
group.members.size - visibleAvatar, size = size,
enableActivityRings = true, avatarToken = avatarToken
)
}
}) { measurables, constraints ->
val placeables = measurables.map { measurable ->
measurable.measure(constraints)
}
Layout(modifier = modifier
.padding(8.dp)
.then(semanticModifier), content = {
for (i in 0 until visibleAvatar) {
val person = group.members[i]
var layoutHeight = 0
var layoutWidth = 0
placeables.forEach {
layoutHeight = max(layoutHeight, it.height)
layoutWidth += it.width
}
layoutWidth += spacing.sum()
var paddingModifier: Modifier = Modifier
if (style == AvatarGroupStyle.Pile && person.isActive) {
val padding = getAvatarGroupTokens().pilePadding(getAvatarGroupInfo())
paddingModifier = paddingModifier.padding(start = padding, end = padding)
}
Avatar(
person,
modifier = paddingModifier,
size = size,
enableActivityRings = true,
enablePresence = enablePresence,
avatarToken = avatarToken
)
}
if (group.members.size > visibleAvatar || group.members.isEmpty()) {
Avatar(
group.members.size - visibleAvatar, size = size,
enableActivityRings = true, avatarToken = avatarToken
)
}
}) { measurables, constraints ->
val placeables = measurables.map { measurable ->
measurable.measure(constraints)
}
var layoutHeight = 0
var layoutWidth = 0
placeables.forEach {
layoutHeight = max(layoutHeight, it.height)
layoutWidth += it.width
}
layoutWidth += spacing.sum()
layout(layoutWidth, layoutHeight) {
var xPosition = 0
placeables.forEach { placeable ->
placeable.placeRelative(y = 0, x = xPosition)
if (placeable != placeables.last())
xPosition += placeable.width + spacing[placeables.indexOf(placeable)]
}
layout(layoutWidth, layoutHeight) {
var xPosition = 0
placeables.forEach { placeable ->
placeable.placeRelative(y = 0, x = xPosition)
if (placeable != placeables.last())
xPosition += placeable.width + spacing[placeables.indexOf(placeable)]
}
}
}
}
@Composable
fun getAvatarGroupTokens(): AvatarGroupTokens {
return LocalAvatarGroupTokens.current
}
@Composable
fun getAvatarGroupInfo(): AvatarGroupInfo {
return LocalAvatarGroupInfo.current
}

Просмотреть файл

@ -11,8 +11,6 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -29,9 +27,6 @@ import com.microsoft.fluentui.theme.token.controlTokens.PersonaChipSize.Medium
import com.microsoft.fluentui.theme.token.controlTokens.PersonaChipStyle
import com.microsoft.fluentui.theme.token.controlTokens.PersonaChipTokens
private val LocalPersonaChipTokens = compositionLocalOf { PersonaChipTokens() }
private val LocalPersonaChipInfo = compositionLocalOf { PersonaChipInfo() }
/**
* [PersonaChip] is a compact representations of entities(most commonly, people)that can be types in, deleted or dragged easily
*
@ -61,89 +56,75 @@ fun PersonaChip(
) {
val token = personaChipTokens
?: FluentTheme.controlTokens.tokens[ControlTokens.ControlType.PersonaChip] as PersonaChipTokens
CompositionLocalProvider(
LocalPersonaChipTokens provides token,
LocalPersonaChipInfo provides PersonaChipInfo(
style,
enabled,
size
)
) {
val backgroundColor =
getPersonaChipTokens().backgroundColor(personaChipInfo = getPersonaChipInfo())
.getColorByState(
enabled = enabled, selected = selected, interactionSource = interactionSource
)
val textColor = getPersonaChipTokens().textColor(personaChipInfo = getPersonaChipInfo())
val personaChipInfo = PersonaChipInfo(
style,
enabled,
size
)
val backgroundColor =
token.backgroundColor(personaChipInfo = personaChipInfo)
.getColorByState(
enabled = enabled, selected = selected, interactionSource = interactionSource
)
val typography = getPersonaChipTokens().typography(personaChipInfo = getPersonaChipInfo())
val avatarSize = getPersonaChipTokens().avatarSize(personaChipInfo = getPersonaChipInfo())
val verticalPadding =
getPersonaChipTokens().verticalPadding(personaChipInfo = getPersonaChipInfo())
val horizontalPadding =
getPersonaChipTokens().horizontalPadding(personaChipInfo = getPersonaChipInfo())
val avatarToTextSpacing =
getPersonaChipTokens().avatarToTextSpacing(personaChipInfo = getPersonaChipInfo())
val cornerRadius =
getPersonaChipTokens().cornerRadius(personaChipInfo = getPersonaChipInfo())
Box(
modifier = modifier
.clip(RoundedCornerShape(cornerRadius))
.background(backgroundColor)
.clickable(
enabled = enabled,
onClick = onClick ?: {},
interactionSource = interactionSource,
indication = rememberRipple()
)
val textColor = token.textColor(personaChipInfo = personaChipInfo)
.getColorByState(
enabled = enabled, selected = selected, interactionSource = interactionSource
)
{
Row(
Modifier
.padding(
horizontal = horizontalPadding,
vertical = verticalPadding
),
horizontalArrangement = Arrangement.spacedBy(avatarToTextSpacing),
verticalAlignment = Alignment.CenterVertically
) {
if (size == Medium) {
if (onCloseClick != null && selected) {
Icon(
Icons.Filled.Close,
modifier = Modifier
.size(16.dp)
.clickable(
enabled = true,
onClick = onCloseClick,
role = Role.Button
),
contentDescription = LocalContext.current.resources.getString(R.string.fluentui_close),
tint = textColor
)
} else {
Avatar(person = person, size = avatarSize)
}
val typography = token.typography(personaChipInfo = personaChipInfo)
val avatarSize = token.avatarSize(personaChipInfo = personaChipInfo)
val verticalPadding =
token.verticalPadding(personaChipInfo = personaChipInfo)
val horizontalPadding =
token.horizontalPadding(personaChipInfo = personaChipInfo)
val avatarToTextSpacing =
token.avatarToTextSpacing(personaChipInfo = personaChipInfo)
val cornerRadius =
token.cornerRadius(personaChipInfo = personaChipInfo)
Box(
modifier = modifier
.clip(RoundedCornerShape(cornerRadius))
.background(backgroundColor)
.clickable(
enabled = enabled,
onClick = onClick ?: {},
interactionSource = interactionSource,
indication = rememberRipple()
)
)
{
Row(
Modifier
.padding(
horizontal = horizontalPadding,
vertical = verticalPadding
),
horizontalArrangement = Arrangement.spacedBy(avatarToTextSpacing),
verticalAlignment = Alignment.CenterVertically
) {
if (size == Medium) {
if (onCloseClick != null && selected) {
Icon(
Icons.Filled.Close,
modifier = Modifier
.size(16.dp)
.clickable(
enabled = true,
onClick = onCloseClick,
role = Role.Button
),
contentDescription = LocalContext.current.resources.getString(R.string.fluentui_close),
tint = textColor
)
} else {
Avatar(person = person, size = avatarSize)
}
Text(
text = person.getLabel(),
color = textColor,
style = typography
)
}
Text(
text = person.getLabel(),
color = textColor,
style = typography
)
}
}
}
@Composable
private fun getPersonaChipTokens(): PersonaChipTokens {
return LocalPersonaChipTokens.current
}
@Composable
private fun getPersonaChipInfo(): PersonaChipInfo {
return LocalPersonaChipInfo.current
}

Просмотреть файл

@ -11,8 +11,6 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -28,9 +26,6 @@ import com.microsoft.fluentui.theme.token.controlTokens.PersonaChipSize
import com.microsoft.fluentui.theme.token.controlTokens.SearchBarPersonaChipInfo
import com.microsoft.fluentui.theme.token.controlTokens.SearchBarPersonaChipTokens
private val LocalSearchBarPersonaChipTokens = compositionLocalOf { SearchBarPersonaChipTokens() }
private val LocalSearchBarPersonaChipInfo = compositionLocalOf { SearchBarPersonaChipInfo() }
/**
* [SearchBarPersonaChip] is a compact representations of entities(most commonly, people)that can be types in, deleted or dragged easily
*
@ -60,93 +55,79 @@ fun SearchBarPersonaChip(
) {
val token = searchbarPersonaChipTokens
?: FluentTheme.controlTokens.tokens[ControlTokens.ControlType.SearchBarPersonaChip] as SearchBarPersonaChipTokens
CompositionLocalProvider(
LocalSearchBarPersonaChipTokens provides token,
LocalSearchBarPersonaChipInfo provides SearchBarPersonaChipInfo(
style,
enabled,
size = size
)
) {
val backgroundColor =
getSearchBarPersonaChipTokens().backgroundColor(searchBarPersonaChipInfo = getSearchBarPersonaChipInfo())
.getColorByState(
enabled = enabled, selected = selected, interactionSource = interactionSource
)
val textColor =
getSearchBarPersonaChipTokens().textColor(searchBarPersonaChipInfo = getSearchBarPersonaChipInfo())
.getColorByState(
enabled = enabled, selected = selected, interactionSource = interactionSource
)
val fontStyle =
getSearchBarPersonaChipTokens().typography(personaChipInfo = getSearchBarPersonaChipInfo())
val avatarSize =
getSearchBarPersonaChipTokens().avatarSize(personaChipInfo = getSearchBarPersonaChipInfo())
val verticalPadding =
getSearchBarPersonaChipTokens().verticalPadding(personaChipInfo = getSearchBarPersonaChipInfo())
val horizontalPadding =
getSearchBarPersonaChipTokens().horizontalPadding(personaChipInfo = getSearchBarPersonaChipInfo())
val avatarToTextSpacing =
getSearchBarPersonaChipTokens().avatarToTextSpacing(personaChipInfo = getSearchBarPersonaChipInfo())
val cornerRadius =
getSearchBarPersonaChipTokens().cornerRadius(personaChipInfo = getSearchBarPersonaChipInfo())
val searchBarPersonaChipInfo = SearchBarPersonaChipInfo(
style,
enabled,
size = size
)
val backgroundColor =
token.backgroundColor(searchBarPersonaChipInfo = searchBarPersonaChipInfo)
.getColorByState(
enabled = enabled, selected = selected, interactionSource = interactionSource
)
val textColor =
token.textColor(searchBarPersonaChipInfo = searchBarPersonaChipInfo)
.getColorByState(
enabled = enabled, selected = selected, interactionSource = interactionSource
)
val fontStyle =
token.typography(personaChipInfo = searchBarPersonaChipInfo)
val avatarSize =
token.avatarSize(personaChipInfo = searchBarPersonaChipInfo)
val verticalPadding =
token.verticalPadding(personaChipInfo = searchBarPersonaChipInfo)
val horizontalPadding =
token.horizontalPadding(personaChipInfo = searchBarPersonaChipInfo)
val avatarToTextSpacing =
token.avatarToTextSpacing(personaChipInfo = searchBarPersonaChipInfo)
val cornerRadius =
token.cornerRadius(personaChipInfo = searchBarPersonaChipInfo)
Box(
modifier = modifier
.clip(RoundedCornerShape(cornerRadius))
.background(backgroundColor)
.clickable(
enabled = enabled,
onClick = onClick ?: {},
interactionSource = interactionSource,
indication = rememberRipple()
)
)
{
Row(
Modifier
.padding(
horizontal = horizontalPadding,
vertical = verticalPadding
),
horizontalArrangement = Arrangement.spacedBy(avatarToTextSpacing),
verticalAlignment = Alignment.CenterVertically
) {
if (size == PersonaChipSize.Medium) {
if (onCloseClick != null && selected) {
Icon(
Icons.Filled.Close,
modifier = Modifier
.size(16.dp)
.clickable(
enabled = true,
onClick = onCloseClick,
role = Role.Button
),
contentDescription = LocalContext.current.resources.getString(R.string.fluentui_close),
tint = textColor
)
} else {
Avatar(person = person, size = avatarSize)
}
Box(
modifier = modifier
.clip(RoundedCornerShape(cornerRadius))
.background(backgroundColor)
.clickable(
enabled = enabled,
onClick = onClick ?: {},
interactionSource = interactionSource,
indication = rememberRipple()
)
)
{
Row(
Modifier
.padding(
horizontal = horizontalPadding,
vertical = verticalPadding
),
horizontalArrangement = Arrangement.spacedBy(avatarToTextSpacing),
verticalAlignment = Alignment.CenterVertically
) {
if (size == PersonaChipSize.Medium) {
if (onCloseClick != null && selected) {
Icon(
Icons.Filled.Close,
modifier = Modifier
.size(16.dp)
.clickable(
enabled = true,
onClick = onCloseClick,
role = Role.Button
),
contentDescription = LocalContext.current.resources.getString(R.string.fluentui_close),
tint = textColor
)
} else {
Avatar(person = person, size = avatarSize)
}
Text(
modifier = Modifier.padding(bottom = 2.dp),//Vertically center align text
text = person.getLabel(),
color = textColor,
style = fontStyle
)
}
Text(
modifier = Modifier.padding(bottom = 2.dp),//Vertically center align text
text = person.getLabel(),
color = textColor,
style = fontStyle
)
}
}
}
@Composable
private fun getSearchBarPersonaChipTokens(): SearchBarPersonaChipTokens {
return LocalSearchBarPersonaChipTokens.current
}
@Composable
private fun getSearchBarPersonaChipInfo(): SearchBarPersonaChipInfo {
return LocalSearchBarPersonaChipInfo.current
}

Просмотреть файл

@ -2,15 +2,8 @@ package com.microsoft.fluentui.tokenized.persona
import android.os.Parcelable
import androidx.annotation.DrawableRes
import androidx.compose.foundation.interaction.InteractionSource
import androidx.compose.foundation.interaction.collectIsFocusedAsState
import androidx.compose.foundation.interaction.collectIsHoveredAsState
import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap
import com.microsoft.fluentui.theme.token.StateColor
import com.microsoft.fluentui.theme.token.controlTokens.AvatarSize
import com.microsoft.fluentui.theme.token.controlTokens.AvatarStatus
import kotlinx.parcelize.Parcelize

Просмотреть файл

@ -5,8 +5,6 @@ import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.progressSemantics
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
@ -24,19 +22,6 @@ import com.microsoft.fluentui.theme.token.controlTokens.CircularProgressIndicato
import com.microsoft.fluentui.theme.token.controlTokens.CircularProgressIndicatorTokens
import com.microsoft.fluentui.util.dpToPx
val LocalCircularProgressIndicatorTokens = compositionLocalOf { CircularProgressIndicatorTokens() }
val LocalCircularProgressIndicatorInfo = compositionLocalOf { CircularProgressIndicatorInfo() }
@Composable
fun getCircularProgressIndicatorTokens(): CircularProgressIndicatorTokens {
return LocalCircularProgressIndicatorTokens.current
}
@Composable
fun getCircularProgressIndicatorInfo(): CircularProgressIndicatorInfo {
return LocalCircularProgressIndicatorInfo.current
}
/**
* Create a Determinate Circular Progress Indicator
*
@ -57,51 +42,47 @@ fun CircularProgressIndicator(
) {
val tokens = circularProgressIndicatorTokens
?: FluentTheme.controlTokens.tokens[ControlTokens.ControlType.CircularProgressIndicator] as CircularProgressIndicatorTokens
CompositionLocalProvider(
LocalCircularProgressIndicatorTokens provides tokens,
LocalCircularProgressIndicatorInfo provides CircularProgressIndicatorInfo(
circularProgressIndicatorSize = size,
style = style
val circularProgressIndicatorInfo = CircularProgressIndicatorInfo(
circularProgressIndicatorSize = size,
style = style
)
val currentProgress = animateFloatAsState(
targetValue = progress.coerceIn(0f..1f),
animationSpec = tween(
delayMillis = 0,
durationMillis = 750,
easing = LinearOutSlowInEasing
)
)
val circularProgressIndicatorColor =
tokens.color(
circularProgressIndicatorInfo
)
val circularProgressIndicatorSize =
tokens.size(
circularProgressIndicatorInfo
)
val circularProgressIndicatorStrokeWidth =
tokens.strokeWidth(
circularProgressIndicatorInfo
)
val indicatorSizeInPx = dpToPx(circularProgressIndicatorSize)
Canvas(
modifier = modifier
.requiredSize(circularProgressIndicatorSize)
.progressSemantics(progress)
) {
val currentProgress = animateFloatAsState(
targetValue = progress.coerceIn(0f..1f),
animationSpec = tween(
delayMillis = 0,
durationMillis = 750,
easing = LinearOutSlowInEasing
)
drawArc(
circularProgressIndicatorColor,
-90f,
currentProgress.value * 360,
false,
size = Size(
indicatorSizeInPx,
indicatorSizeInPx
),
style = Stroke(dpToPx(circularProgressIndicatorStrokeWidth), cap = StrokeCap.Round)
)
val circularProgressIndicatorColor =
getCircularProgressIndicatorTokens().color(
getCircularProgressIndicatorInfo()
)
val circularProgressIndicatorSize =
getCircularProgressIndicatorTokens().size(
getCircularProgressIndicatorInfo()
)
val circularProgressIndicatorStrokeWidth =
getCircularProgressIndicatorTokens().strokeWidth(
getCircularProgressIndicatorInfo()
)
val indicatorSizeInPx = dpToPx(circularProgressIndicatorSize)
Canvas(
modifier = modifier
.requiredSize(circularProgressIndicatorSize)
.progressSemantics(progress)
) {
drawArc(
circularProgressIndicatorColor,
-90f,
currentProgress.value * 360,
false,
size = Size(
indicatorSizeInPx,
indicatorSizeInPx
),
style = Stroke(dpToPx(circularProgressIndicatorStrokeWidth), cap = StrokeCap.Round)
)
}
}
}
@ -123,61 +104,57 @@ fun CircularProgressIndicator(
) {
val tokens = circularProgressIndicatorTokens
?: FluentTheme.controlTokens.tokens[ControlTokens.ControlType.CircularProgressIndicator] as CircularProgressIndicatorTokens
CompositionLocalProvider(
LocalCircularProgressIndicatorTokens provides tokens,
LocalCircularProgressIndicatorInfo provides CircularProgressIndicatorInfo(
circularProgressIndicatorSize = size,
style = style
val circularProgressIndicatorInfo = CircularProgressIndicatorInfo(
circularProgressIndicatorSize = size,
style = style
)
val circularProgressIndicatorColor =
tokens.color(
circularProgressIndicatorInfo
)
val circularProgressIndicatorSize =
tokens.size(
circularProgressIndicatorInfo
)
val circularProgressIndicatorStrokeWidth =
tokens.strokeWidth(
circularProgressIndicatorInfo
)
val infiniteTransition = rememberInfiniteTransition()
val startAngle by infiniteTransition.animateFloat(
0f,
360f,
infiniteRepeatable(
animation = tween(
durationMillis = 1000,
easing = LinearEasing
)
)
)
val indicatorSizeInPx = dpToPx(circularProgressIndicatorSize)
Canvas(
modifier = modifier
.requiredSize(circularProgressIndicatorSize)
.progressSemantics()
.rotate(startAngle)
) {
val circularProgressIndicatorColor =
getCircularProgressIndicatorTokens().color(
getCircularProgressIndicatorInfo()
)
val circularProgressIndicatorSize =
getCircularProgressIndicatorTokens().size(
getCircularProgressIndicatorInfo()
)
val circularProgressIndicatorStrokeWidth =
getCircularProgressIndicatorTokens().strokeWidth(
getCircularProgressIndicatorInfo()
)
val infiniteTransition = rememberInfiniteTransition()
val startAngle by infiniteTransition.animateFloat(
drawArc(
Brush.sweepGradient(
0f to Color.Transparent,
0.6f to circularProgressIndicatorColor
),
0f,
360f,
infiniteRepeatable(
animation = tween(
durationMillis = 1000,
easing = LinearEasing
)
)
270f,
false,
size = Size(
indicatorSizeInPx, indicatorSizeInPx
),
style = Stroke(dpToPx(circularProgressIndicatorStrokeWidth))
)
drawCircle(
color = circularProgressIndicatorColor,
radius = dpToPx(circularProgressIndicatorStrokeWidth) / 2,
center = Offset(indicatorSizeInPx / 2, 0f)
)
val indicatorSizeInPx = dpToPx(circularProgressIndicatorSize)
Canvas(
modifier = modifier
.requiredSize(circularProgressIndicatorSize)
.progressSemantics()
.rotate(startAngle)
) {
drawArc(
Brush.sweepGradient(
0f to Color.Transparent,
0.6f to circularProgressIndicatorColor
),
0f,
270f,
false,
size = Size(
indicatorSizeInPx, indicatorSizeInPx
),
style = Stroke(dpToPx(circularProgressIndicatorStrokeWidth))
)
drawCircle(
color = circularProgressIndicatorColor,
radius = dpToPx(circularProgressIndicatorStrokeWidth) / 2,
center = Offset(indicatorSizeInPx / 2, 0f)
)
}
}
}

Просмотреть файл

@ -6,8 +6,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.progressSemantics
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
@ -20,19 +18,6 @@ import com.microsoft.fluentui.theme.token.controlTokens.LinearProgressIndicatorI
import com.microsoft.fluentui.theme.token.controlTokens.LinearProgressIndicatorTokens
import com.microsoft.fluentui.util.dpToPx
val LocalLinearProgressIndicatorTokens = compositionLocalOf { LinearProgressIndicatorTokens() }
val LocalLinearProgressIndicatorInfo = compositionLocalOf { LinearProgressIndicatorInfo() }
@Composable
fun getLinearProgressIndicatorTokens(): LinearProgressIndicatorTokens {
return LocalLinearProgressIndicatorTokens.current
}
@Composable
fun getLinearProgressIndicatorInfo(): LinearProgressIndicatorInfo {
return LocalLinearProgressIndicatorInfo.current
}
/**
* Create a Determinate Linear Progress Indicator
*
@ -45,65 +30,56 @@ fun getLinearProgressIndicatorInfo(): LinearProgressIndicatorInfo {
@Composable
fun LinearProgressIndicator(
progress: Float,
linearProgressIndicatorHeight: LinearProgressIndicatorHeight = LinearProgressIndicatorHeight.XXXSmall,
modifier: Modifier = Modifier,
linearProgressIndicatorHeight: LinearProgressIndicatorHeight = LinearProgressIndicatorHeight.XXXSmall,
linearProgressIndicatorTokens: LinearProgressIndicatorTokens? = null
) {
val tokens = linearProgressIndicatorTokens
?: FluentTheme.controlTokens.tokens[ControlTokens.ControlType.LinearProgressIndicator] as LinearProgressIndicatorTokens
CompositionLocalProvider(
LocalLinearProgressIndicatorTokens provides tokens,
LocalLinearProgressIndicatorInfo provides LinearProgressIndicatorInfo(
linearProgressIndicatorHeight = linearProgressIndicatorHeight
val linearProgressIndicatorInfo = LinearProgressIndicatorInfo(
linearProgressIndicatorHeight = linearProgressIndicatorHeight
)
val currentProgress = animateFloatAsState(
targetValue = progress.coerceIn(0f..1f), animationSpec = tween(
delayMillis = 0, durationMillis = 1000, easing = LinearOutSlowInEasing
)
)
val linearProgressIndicatorStrokeWidth = tokens.strokeWidth(
linearProgressIndicatorInfo
)
val linearProgressIndicatorBackgroundColor =
tokens.backgroundColor(
linearProgressIndicatorInfo
)
val linearProgressIndicatorColor = tokens.color(
linearProgressIndicatorInfo
)
Canvas(
modifier = modifier
.fillMaxWidth()
.requiredHeight(linearProgressIndicatorStrokeWidth)
.progressSemantics(progress)
) {
val currentProgress = animateFloatAsState(
targetValue = progress.coerceIn(0f..1f),
animationSpec = tween(
delayMillis = 0,
durationMillis = 1000,
easing = LinearOutSlowInEasing
)
val strokeWidth = dpToPx(linearProgressIndicatorStrokeWidth)
val yOffset = strokeWidth / 2
val isLtr = layoutDirection == LayoutDirection.Ltr
val barStart = (if (isLtr) 0f else size.width)
val barEnd = (if (isLtr) size.width else 0f)
drawLine(
linearProgressIndicatorBackgroundColor,
Offset(barStart, yOffset),
Offset(barEnd, yOffset),
strokeWidth
)
val progressIndicatorWidth = currentProgress.value * size.width
val indicatorLineEnd =
if (isLtr) progressIndicatorWidth else size.width - progressIndicatorWidth
drawLine(
linearProgressIndicatorColor,
Offset(barStart, yOffset),
Offset(indicatorLineEnd, yOffset),
strokeWidth
)
val linearProgressIndicatorHeight =
getLinearProgressIndicatorTokens().strokeWidth(
getLinearProgressIndicatorInfo()
)
val linearProgressIndicatorBackgroundColor =
getLinearProgressIndicatorTokens().backgroundColor(
getLinearProgressIndicatorInfo()
)
val linearProgressIndicatorColor =
getLinearProgressIndicatorTokens().color(
getLinearProgressIndicatorInfo()
)
Canvas(
modifier = modifier
.fillMaxWidth()
.requiredHeight(linearProgressIndicatorHeight)
.progressSemantics(progress)
) {
val strokeWidth = dpToPx(linearProgressIndicatorHeight)
val yOffset = strokeWidth / 2
val isLtr = layoutDirection == LayoutDirection.Ltr
val barStart = (if (isLtr) 0f else size.width)
val barEnd = (if (isLtr) size.width else 0f)
drawLine(
linearProgressIndicatorBackgroundColor,
Offset(barStart, yOffset),
Offset(barEnd, yOffset),
strokeWidth
)
val progressIndicatorWidth = currentProgress.value * size.width
val indicatorLineEnd =
if (isLtr) progressIndicatorWidth else size.width - progressIndicatorWidth
drawLine(
linearProgressIndicatorColor,
Offset(barStart, yOffset),
Offset(indicatorLineEnd, yOffset),
strokeWidth
)
}
}
}
@ -117,89 +93,74 @@ fun LinearProgressIndicator(
*/
@Composable
fun LinearProgressIndicator(
linearProgressIndicatorHeight: LinearProgressIndicatorHeight = LinearProgressIndicatorHeight.XXXSmall,
modifier: Modifier = Modifier,
linearProgressIndicatorHeight: LinearProgressIndicatorHeight = LinearProgressIndicatorHeight.XXXSmall,
linearProgressIndicatorTokens: LinearProgressIndicatorTokens? = null
) {
val tokens = linearProgressIndicatorTokens
?: FluentTheme.controlTokens.tokens[ControlTokens.ControlType.LinearProgressIndicator] as LinearProgressIndicatorTokens
CompositionLocalProvider(
LocalLinearProgressIndicatorTokens provides tokens,
LocalLinearProgressIndicatorInfo provides LinearProgressIndicatorInfo(
linearProgressIndicatorHeight = linearProgressIndicatorHeight
)
val linearProgressIndicatorInfo = LinearProgressIndicatorInfo(
linearProgressIndicatorHeight = linearProgressIndicatorHeight
)
val linearProgressIndicatorStrokeWidth = tokens.strokeWidth(
linearProgressIndicatorInfo
)
val linearProgressIndicatorBackgroundColor = tokens.backgroundColor(
linearProgressIndicatorInfo
)
val linearProgressIndicatorColor = tokens.color(
linearProgressIndicatorInfo
)
val infiniteTransition = rememberInfiniteTransition()
val animationDuration = (1750 * 0.5f).toInt()
val headAnimationDelay = 0
val tailAnimationDelay = (animationDuration * 0.5f).toInt()
val indicatorHead by infiniteTransition.animateFloat(
0f, 1f, infiniteRepeatable(animation = keyframes {
durationMillis = animationDuration + 500
0f at headAnimationDelay with FastOutSlowInEasing
1f at animationDuration + headAnimationDelay
})
)
val indicatorTail by infiniteTransition.animateFloat(
0f, 1f, infiniteRepeatable(animation = keyframes {
durationMillis = animationDuration + 500
0f at tailAnimationDelay with FastOutSlowInEasing
1f at animationDuration + tailAnimationDelay
})
)
Canvas(
modifier = modifier
.fillMaxWidth()
.progressSemantics()
.requiredHeight(linearProgressIndicatorStrokeWidth)
) {
val linearProgressIndicatorHeight =
getLinearProgressIndicatorTokens().strokeWidth(
getLinearProgressIndicatorInfo()
)
val linearProgressIndicatorBackgroundColor =
getLinearProgressIndicatorTokens().backgroundColor(
getLinearProgressIndicatorInfo()
)
val linearProgressIndicatorColor =
getLinearProgressIndicatorTokens().color(
getLinearProgressIndicatorInfo()
)
val infiniteTransition = rememberInfiniteTransition()
val animationDuration = (1750 * 0.5f).toInt()
val headAnimationDelay = 0
val tailAnimationDelay = (animationDuration * 0.5f).toInt()
val indicatorHead by infiniteTransition.animateFloat(
0f,
1f,
infiniteRepeatable(
animation = keyframes {
durationMillis = animationDuration + 500
0f at headAnimationDelay with FastOutSlowInEasing
1f at animationDuration + headAnimationDelay
}
)
val strokeWidth = dpToPx(linearProgressIndicatorStrokeWidth)
val yOffset = strokeWidth / 2
val isLtr = layoutDirection == LayoutDirection.Ltr
val barStart = (if (isLtr) 0f else size.width)
val barEnd = (if (isLtr) size.width else 0f)
drawLine(
linearProgressIndicatorBackgroundColor,
Offset(barStart, yOffset),
Offset(barEnd, yOffset),
strokeWidth
)
val indicatorTail by infiniteTransition.animateFloat(
0f,
1f,
infiniteRepeatable(
animation = keyframes {
durationMillis = animationDuration + 500
0f at tailAnimationDelay with FastOutSlowInEasing
1f at animationDuration + tailAnimationDelay
}
)
val progressIndicatorStart =
(if (isLtr) indicatorHead * size.width else (size.width) - indicatorHead * size.width)
val progressIndicatorEnd =
(if (isLtr) indicatorTail * (size.width) else (size.width) - indicatorTail * (size.width))
drawLine(
Brush.linearGradient(
0f to linearProgressIndicatorBackgroundColor,
0.5f to linearProgressIndicatorColor,
1.0f to linearProgressIndicatorBackgroundColor,
start = Offset(progressIndicatorStart, yOffset),
end = Offset(progressIndicatorEnd, yOffset)
),
Offset(progressIndicatorStart, yOffset),
Offset(progressIndicatorEnd, yOffset),
strokeWidth
)
Canvas(
modifier = modifier
.fillMaxWidth()
.progressSemantics()
.requiredHeight(linearProgressIndicatorHeight)
) {
val strokeWidth = dpToPx(linearProgressIndicatorHeight)
val yOffset = strokeWidth / 2
val isLtr = layoutDirection == LayoutDirection.Ltr
val barStart = (if (isLtr) 0f else size.width)
val barEnd = (if (isLtr) size.width else 0f)
drawLine(
linearProgressIndicatorBackgroundColor,
Offset(barStart, yOffset),
Offset(barEnd, yOffset),
strokeWidth
)
val progressIndicatorStart =
(if (isLtr) indicatorHead * size.width else (size.width) - indicatorHead * size.width)
val progressIndicatorEnd =
(if (isLtr) indicatorTail * (size.width) else (size.width) - indicatorTail * (size.width))
drawLine(
Brush.linearGradient(
0f to linearProgressIndicatorBackgroundColor,
0.5f to linearProgressIndicatorColor,
1.0f to linearProgressIndicatorBackgroundColor,
start = Offset(progressIndicatorStart, yOffset),
end = Offset(progressIndicatorEnd, yOffset)
),
Offset(progressIndicatorStart, yOffset),
Offset(progressIndicatorEnd, yOffset),
strokeWidth
)
}
}
}

Просмотреть файл

@ -1,4 +1,4 @@
package com.microsoft.fluentui.tokenized.progress
package com.microsoft.fluentui.tokenized.shimmer
import androidx.compose.animation.core.*
import androidx.compose.foundation.background
@ -9,8 +9,6 @@ import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@ -22,17 +20,12 @@ import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import com.microsoft.fluentui.theme.FluentTheme
import com.microsoft.fluentui.theme.token.ControlTokens
import com.microsoft.fluentui.theme.token.controlTokens.ShimmerInfo
import com.microsoft.fluentui.theme.token.controlTokens.ShimmerShape
import com.microsoft.fluentui.theme.token.controlTokens.ShimmerTokens
import com.microsoft.fluentui.util.dpToPx
import kotlin.math.absoluteValue
val LocalShimmerTokens = compositionLocalOf { ShimmerTokens() }
@Composable
fun getShimmerTokens(): ShimmerTokens {
return LocalShimmerTokens.current
}
import kotlin.math.sqrt
/**
* Create a Shimmer effect
@ -44,8 +37,8 @@ fun getShimmerTokens(): ShimmerTokens {
*/
@Composable
fun Shimmer(
shape: ShimmerShape = ShimmerShape.Box,
modifier: Modifier = Modifier,
shape: ShimmerShape = ShimmerShape.Box,
shimmerTokens: ShimmerTokens? = null
) {
val tokens = shimmerTokens
@ -54,51 +47,48 @@ fun Shimmer(
val screenHeight = dpToPx(configuration.screenHeightDp.dp)
val screenWidth = dpToPx(configuration.screenWidthDp.dp)
val diagonal =
Math.sqrt((screenHeight * screenHeight + screenWidth * screenWidth).toDouble()).toFloat()
CompositionLocalProvider(
LocalShimmerTokens provides tokens
) {
val shimmerBackgroundColor =
getShimmerTokens().color()
val shimmerKnockoutEffectColor = getShimmerTokens().knockoutEffectColor()
val cornerRadius =
dpToPx(getShimmerTokens().cornerRadius())
val infiniteTransition = rememberInfiniteTransition()
val isLtr = LocalLayoutDirection.current == LayoutDirection.Ltr
val initialValue = if(isLtr) 0f else screenWidth
val targetValue = if(isLtr) diagonal else 0f
val shimmerEffect by infiniteTransition.animateFloat(
initialValue,
targetValue,
infiniteRepeatable(
animation = tween(
durationMillis = 1000,
easing = LinearEasing
)
sqrt((screenHeight * screenHeight + screenWidth * screenWidth).toDouble()).toFloat()
val shimmerInfo = ShimmerInfo(shape = shape)
val shimmerBackgroundColor =
tokens.color(shimmerInfo)
val shimmerKnockoutEffectColor = tokens.knockoutEffectColor(shimmerInfo)
val cornerRadius =
dpToPx(tokens.cornerRadius(shimmerInfo))
val infiniteTransition = rememberInfiniteTransition()
val isLtr = LocalLayoutDirection.current == LayoutDirection.Ltr
val initialValue = if (isLtr) 0f else screenWidth
val targetValue = if (isLtr) diagonal else 0f
val shimmerEffect by infiniteTransition.animateFloat(
initialValue,
targetValue,
infiniteRepeatable(
animation = tween(
durationMillis = 1000,
easing = LinearEasing
)
)
val gradientColor = Brush.linearGradient(
0f to shimmerBackgroundColor,
0.5f to shimmerKnockoutEffectColor,
1.0f to shimmerBackgroundColor,
start = Offset.Zero,
end = Offset(shimmerEffect.absoluteValue, shimmerEffect.absoluteValue)
)
val gradientColor = Brush.linearGradient(
0f to shimmerBackgroundColor,
0.5f to shimmerKnockoutEffectColor,
1.0f to shimmerBackgroundColor,
start = Offset.Zero,
end = Offset(shimmerEffect.absoluteValue, shimmerEffect.absoluteValue)
)
if (shape == ShimmerShape.Box) {
Spacer(
modifier = modifier
.width(240.dp)
.height(12.dp)
.clip(RoundedCornerShape(cornerRadius))
.background(gradientColor)
)
} else {
Spacer(
modifier = modifier
.size(60.dp)
.clip(CircleShape)
.background(gradientColor)
)
if (shape == ShimmerShape.Box) {
Spacer(
modifier = modifier
.width(240.dp)
.height(12.dp)
.clip(RoundedCornerShape(cornerRadius))
.background(gradientColor)
)
} else {
Spacer(
modifier = modifier
.size(60.dp)
.clip(CircleShape)
.background(gradientColor)
)
}
}
}

Просмотреть файл

@ -3,8 +3,6 @@ package com.microsoft.fluentui.tokenized.navigation
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
@ -24,19 +22,6 @@ data class TabData(
var badge: @Composable (() -> Unit)? = null
)
private val LocalTabBarInfo = compositionLocalOf { TabBarInfo() }
private val LocalTabBarToken = compositionLocalOf { TabBarTokens() }
@Composable
private fun getTabBarInfo(): TabBarInfo {
return LocalTabBarInfo.current
}
@Composable
private fun getTabBarToken(): TabBarTokens {
return LocalTabBarToken.current
}
/**
* TabBar displays tabs that are arranged horizontally.
*
@ -44,7 +29,7 @@ private fun getTabBarToken(): TabBarTokens {
* @param modifier the [Modifier] to be applied to this item
* @param selectedIndex Index of selected tax. This should be updated onClick of TabData & provide back to TabBar.
* @param tabTextAlignment Placement of text in Tab
* @param tabItemTokens [TabBarTabItemsTokens] to apply on tabs.
* @param tabItemTokens [TabItemTokens] to apply on tabs.
* @param tabBarTokens provide appearance values. If not provided then tokens will be picked from AppThemeController
*
*/
@ -60,38 +45,34 @@ fun TabBar(
val token = tabBarTokens
?: FluentTheme.controlTokens.tokens[ControlTokens.ControlType.TabBar] as TabBarTokens
CompositionLocalProvider(
LocalTabBarToken provides token
) {
Column {
Box(
modifier
.fillMaxWidth()
.height(getTabBarToken().topBorderWidth(tabBarInfo = getTabBarInfo()))
.background(color = getTabBarToken().topBorderColor(tabBarInfo = getTabBarInfo()))
)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
) {
tabDataList.forEachIndexed { index, tabData ->
tabData.selected = index == selectedIndex
TabItem(
title = tabData.title,
modifier = Modifier
.fillMaxWidth()
.weight(1F),
icon = if (tabData.selected) tabData.selectedIcon else tabData.icon,
textAlignment = tabTextAlignment,
selected = tabData.selected,
onClick = tabData.onClick,
accessory = tabData.badge,
tabItemTokens = tabItemTokens
?: FluentTheme.controlTokens.tokens[ControlTokens.ControlType.TabItem] as TabItemTokens,
style = if (tabData.selected) FluentStyle.Brand else FluentStyle.Neutral
)
}
Column {
Box(
modifier
.fillMaxWidth()
.height(token.topBorderWidth(tabBarInfo = TabBarInfo()))
.background(color = token.topBorderColor(tabBarInfo = TabBarInfo()))
)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
) {
tabDataList.forEachIndexed { index, tabData ->
tabData.selected = index == selectedIndex
TabItem(
title = tabData.title,
modifier = Modifier
.fillMaxWidth()
.weight(1F),
icon = if (tabData.selected) tabData.selectedIcon else tabData.icon,
textAlignment = tabTextAlignment,
selected = tabData.selected,
onClick = tabData.onClick,
accessory = tabData.badge,
tabItemTokens = tabItemTokens
?: FluentTheme.controlTokens.tokens[ControlTokens.ControlType.TabItem] as TabItemTokens,
style = if (tabData.selected) FluentStyle.Brand else FluentStyle.Neutral
)
}
}
}

Просмотреть файл

@ -54,11 +54,6 @@ data class PillMetaData(
var notificationDot: Boolean = false,
)
val LocalPillButtonTokens = compositionLocalOf { PillButtonTokens() }
val LocalPillButtonInfo = compositionLocalOf { PillButtonInfo() }
val LocalPillBarTokens = compositionLocalOf { PillBarTokens() }
val LocalPillBarInfo = compositionLocalOf { PillBarInfo() }
/**
* API to create Pill shaped Button which will further be used in tabs and bars.
*
@ -78,154 +73,148 @@ fun PillButton(
) {
val token = pillButtonTokens
?: FluentTheme.controlTokens.tokens[ControlTokens.ControlType.PillButton] as PillButtonTokens
val pillButtonInfo = PillButtonInfo(
style,
pillMetaData.enabled,
pillMetaData.selected
)
val shape = RoundedCornerShape(50)
val scaleBox = remember { Animatable(1.0F) }
CompositionLocalProvider(
LocalPillButtonTokens provides token,
LocalPillButtonInfo provides PillButtonInfo(
style,
pillMetaData.enabled,
pillMetaData.selected
)
) {
val shape = RoundedCornerShape(50)
val scaleBox = remember { Animatable(1.0F) }
LaunchedEffect(key1 = pillMetaData.selected) {
if (pillMetaData.selected) {
launch {
scaleBox.animateTo(
targetValue = 0.95F,
animationSpec = tween(
durationMillis = 50
)
LaunchedEffect(key1 = pillMetaData.selected) {
if (pillMetaData.selected) {
launch {
scaleBox.animateTo(
targetValue = 0.95F,
animationSpec = tween(
durationMillis = 50
)
scaleBox.animateTo(
targetValue = 1.0F,
animationSpec = spring(
dampingRatio = Spring.DampingRatioLowBouncy,
stiffness = Spring.StiffnessLow
)
)
scaleBox.animateTo(
targetValue = 1.0F,
animationSpec = spring(
dampingRatio = Spring.DampingRatioLowBouncy,
stiffness = Spring.StiffnessLow
)
}
)
}
}
}
val backgroundColor by animateColorAsState(
targetValue = getPillButtonTokens().backgroundColor(pillButtonInfo = getPillButtonInfo())
.getColorByState(
enabled = pillMetaData.enabled,
selected = pillMetaData.selected,
interactionSource = interactionSource
),
animationSpec = tween(200)
)
val iconColor =
getPillButtonTokens().iconColor(pillButtonInfo = getPillButtonInfo()).getColorByState(
val backgroundColor by animateColorAsState(
targetValue = token.backgroundColor(pillButtonInfo = pillButtonInfo)
.getColorByState(
enabled = pillMetaData.enabled,
selected = pillMetaData.selected,
interactionSource = interactionSource
)
val textColor =
getPillButtonTokens().textColor(pillButtonInfo = getPillButtonInfo()).getColorByState(
enabled = pillMetaData.enabled,
selected = pillMetaData.selected,
interactionSource = interactionSource
)
val fontStyle = getPillButtonTokens().typography(getPillButtonInfo())
val focusStroke = getPillButtonTokens().focusStroke(getPillButtonInfo())
var focusedBorderModifier: Modifier = Modifier
for (borderStroke in focusStroke) {
focusedBorderModifier =
focusedBorderModifier.border(borderStroke, shape)
}
val clickAndSemanticsModifier = modifier.clickable(
interactionSource = interactionSource,
indication = rememberRipple(),
),
animationSpec = tween(200)
)
val iconColor =
token.iconColor(pillButtonInfo = pillButtonInfo).getColorByState(
enabled = pillMetaData.enabled,
onClickLabel = null,
role = Role.Button,
onClick = pillMetaData.onClick
selected = pillMetaData.selected,
interactionSource = interactionSource
)
val textColor =
token.textColor(pillButtonInfo = pillButtonInfo).getColorByState(
enabled = pillMetaData.enabled,
selected = pillMetaData.selected,
interactionSource = interactionSource
)
val selectedString = if (pillMetaData.selected)
LocalContext.current.resources.getString(R.string.fluentui_selected)
else
LocalContext.current.resources.getString(R.string.fluentui_not_selected)
val fontStyle = token.typography(pillButtonInfo)
val enabledString = if (pillMetaData.enabled)
LocalContext.current.resources.getString(R.string.fluentui_enabled)
else
LocalContext.current.resources.getString(R.string.fluentui_disabled)
val focusStroke = token.focusStroke(pillButtonInfo)
var focusedBorderModifier: Modifier = Modifier
for (borderStroke in focusStroke) {
focusedBorderModifier =
focusedBorderModifier.border(borderStroke, shape)
}
Box(
modifier
.scale(scaleBox.value)
.defaultMinSize(minHeight = getPillButtonTokens().minHeight(getPillButtonInfo()))
.clip(shape)
.background(backgroundColor, shape)
.padding(vertical = getPillButtonTokens().verticalPadding(getPillButtonInfo()))
.then(clickAndSemanticsModifier)
.then(if (interactionSource.collectIsFocusedAsState().value || interactionSource.collectIsHoveredAsState().value) focusedBorderModifier else Modifier)
.semantics(true) {
contentDescription =
"${pillMetaData.text} $selectedString $enabledString"
},
contentAlignment = Alignment.Center
) {
Row(Modifier.width(IntrinsicSize.Max)) {
if (pillMetaData.icon != null) {
Spacer(Modifier.requiredWidth(GlobalTokens.size(GlobalTokens.SizeTokens.Size180)))
Icon(
pillMetaData.icon!!,
pillMetaData.text,
modifier = Modifier
.size(getPillButtonTokens().iconSize(getPillButtonInfo()))
.clearAndSetSemantics { },
tint = iconColor
)
} else {
Spacer(Modifier.requiredWidth(GlobalTokens.size(GlobalTokens.SizeTokens.Size160)))
Text(
pillMetaData.text,
modifier = Modifier
.weight(1F)
.clearAndSetSemantics { },
color = textColor,
style = fontStyle,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
val clickAndSemanticsModifier = modifier.clickable(
interactionSource = interactionSource,
indication = rememberRipple(),
enabled = pillMetaData.enabled,
onClickLabel = null,
role = Role.Button,
onClick = pillMetaData.onClick
)
if (pillMetaData.notificationDot) {
val notificationDotColor: Color =
getPillButtonTokens().notificationDotColor(getPillButtonInfo())
.getColorByState(
enabled = pillMetaData.enabled,
selected = pillMetaData.selected,
interactionSource = interactionSource
)
Spacer(Modifier.requiredWidth(GlobalTokens.size(GlobalTokens.SizeTokens.Size20)))
Canvas(
modifier = Modifier
.padding(top = 2.dp, bottom = 12.dp)
.sizeIn(minWidth = 6.dp, minHeight = 6.dp)
) {
drawCircle(
color = notificationDotColor, style = Fill, radius = dpToPx(3.dp)
val selectedString = if (pillMetaData.selected)
LocalContext.current.resources.getString(R.string.fluentui_selected)
else
LocalContext.current.resources.getString(R.string.fluentui_not_selected)
val enabledString = if (pillMetaData.enabled)
LocalContext.current.resources.getString(R.string.fluentui_enabled)
else
LocalContext.current.resources.getString(R.string.fluentui_disabled)
Box(
modifier
.scale(scaleBox.value)
.defaultMinSize(minHeight = token.minHeight(pillButtonInfo))
.clip(shape)
.background(backgroundColor, shape)
.padding(vertical = token.verticalPadding(pillButtonInfo))
.then(clickAndSemanticsModifier)
.then(if (interactionSource.collectIsFocusedAsState().value || interactionSource.collectIsHoveredAsState().value) focusedBorderModifier else Modifier)
.semantics(true) {
contentDescription =
"${pillMetaData.text} $selectedString $enabledString"
},
contentAlignment = Alignment.Center
) {
Row(Modifier.width(IntrinsicSize.Max)) {
if (pillMetaData.icon != null) {
Spacer(Modifier.requiredWidth(GlobalTokens.size(GlobalTokens.SizeTokens.Size180)))
Icon(
pillMetaData.icon!!,
pillMetaData.text,
modifier = Modifier
.size(token.iconSize(pillButtonInfo))
.clearAndSetSemantics { },
tint = iconColor
)
} else {
Spacer(Modifier.requiredWidth(GlobalTokens.size(GlobalTokens.SizeTokens.Size160)))
Text(
pillMetaData.text,
modifier = Modifier
.weight(1F)
.clearAndSetSemantics { },
color = textColor,
style = fontStyle,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
if (pillMetaData.notificationDot) {
val notificationDotColor: Color =
token.notificationDotColor(pillButtonInfo)
.getColorByState(
enabled = pillMetaData.enabled,
selected = pillMetaData.selected,
interactionSource = interactionSource
)
}
if (pillMetaData.icon != null)
Spacer(Modifier.requiredWidth(GlobalTokens.size(GlobalTokens.SizeTokens.Size100)))
else
Spacer(Modifier.requiredWidth(GlobalTokens.size(GlobalTokens.SizeTokens.Size80)))
} else {
Spacer(Modifier.requiredWidth(GlobalTokens.size(GlobalTokens.SizeTokens.Size160)))
Spacer(Modifier.requiredWidth(GlobalTokens.size(GlobalTokens.SizeTokens.Size20)))
Canvas(
modifier = Modifier
.padding(top = 2.dp, bottom = 12.dp)
.sizeIn(minWidth = 6.dp, minHeight = 6.dp)
) {
drawCircle(
color = notificationDotColor, style = Fill, radius = dpToPx(3.dp)
)
}
if (pillMetaData.icon != null)
Spacer(Modifier.requiredWidth(GlobalTokens.size(GlobalTokens.SizeTokens.Size100)))
else
Spacer(Modifier.requiredWidth(GlobalTokens.size(GlobalTokens.SizeTokens.Size80)))
} else {
Spacer(Modifier.requiredWidth(GlobalTokens.size(GlobalTokens.SizeTokens.Size160)))
}
}
}
@ -257,58 +246,34 @@ fun PillBar(
val token = pillBarTokens
?: FluentTheme.controlTokens.tokens[ControlTokens.ControlType.PillBar] as PillBarTokens
CompositionLocalProvider(
LocalPillBarTokens provides token,
LocalPillBarInfo provides PillBarInfo(style)
) {
val lazyListState = rememberLazyListState()
val scope = rememberCoroutineScope()
val pillBarInfo = PillBarInfo(style)
val lazyListState = rememberLazyListState()
val scope = rememberCoroutineScope()
LazyRow(
modifier = modifier
.fillMaxWidth()
.background(if (showBackground) getPillBarTokens().background(getPillBarInfo()) else Color.Unspecified)
.focusable(enabled = false),
contentPadding = PaddingValues(horizontal = 16.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally),
state = lazyListState
) {
metadataList.forEachIndexed { index, pillMetadata ->
item(index.toString()) {
PillButton(
pillMetadata,
modifier = Modifier.onFocusEvent { focusState ->
if (focusState.isFocused) {
scope.launch {
lazyListState.animateScrollToItem(
max(0, index - 2)
)
}
LazyRow(
modifier = modifier
.fillMaxWidth()
.background(if (showBackground) token.background(pillBarInfo) else Color.Unspecified)
.focusable(enabled = false),
contentPadding = PaddingValues(horizontal = 16.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally),
state = lazyListState
) {
metadataList.forEachIndexed { index, pillMetadata ->
item(index.toString()) {
PillButton(
pillMetadata,
modifier = Modifier.onFocusEvent { focusState ->
if (focusState.isFocused) {
scope.launch {
lazyListState.animateScrollToItem(
max(0, index - 2)
)
}
}, style = style, pillButtonTokens = pillButtonTokens
)
}
}
}, style = style, pillButtonTokens = pillButtonTokens
)
}
}
}
}
@Composable
fun getPillButtonTokens(): PillButtonTokens {
return LocalPillButtonTokens.current
}
@Composable
fun getPillButtonInfo(): PillButtonInfo {
return LocalPillButtonInfo.current
}
@Composable
fun getPillBarTokens(): PillBarTokens {
return LocalPillBarTokens.current
}
@Composable
fun getPillBarInfo(): PillBarInfo {
return LocalPillBarInfo.current
}

Просмотреть файл

@ -9,8 +9,6 @@ import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@ -26,9 +24,6 @@ import com.microsoft.fluentui.theme.token.controlTokens.PillSwitchTokens
import kotlinx.coroutines.launch
import kotlin.math.max
val LocalPillSwitchTokens = compositionLocalOf { PillSwitchTokens() }
val LocalPillSwitchInfo = compositionLocalOf { PillSwitchInfo() }
/**
* API to create PillSwitches. The PillSwitch control is a linear set of two or more PillButton, each of which functions as a mutually exclusive button.
* PillSwitches are used to toggling between two views.
@ -55,63 +50,49 @@ fun PillSwitch(
val token = pillSwitchTokens
?: FluentTheme.controlTokens.tokens[ControlTokens.ControlType.PillSwitch] as PillSwitchTokens
CompositionLocalProvider(
LocalPillSwitchTokens provides token,
LocalPillSwitchInfo provides PillSwitchInfo(style)
val pillSwitchInfo = PillSwitchInfo(style)
val shape = RoundedCornerShape(50)
val lazyListState = rememberLazyListState()
val scope = rememberCoroutineScope()
Row(
modifier = Modifier
.wrapContentWidth()
.padding(horizontal = 16.dp)
.background(Color.Transparent)
.focusable(false)
) {
val shape = RoundedCornerShape(50)
val lazyListState = rememberLazyListState()
val scope = rememberCoroutineScope()
Row(
modifier = Modifier
LazyRow(
modifier = modifier
.wrapContentWidth()
.padding(horizontal = 16.dp)
.background(Color.Transparent)
.focusable(false)
.focusable(enabled = false)
.clip(shape)
.background(token.background(pillSwitchInfo), shape),
state = lazyListState
) {
LazyRow(
modifier = modifier
.wrapContentWidth()
.padding(horizontal = 16.dp)
.focusable(enabled = false)
.clip(shape)
.background(getPillSwitchTokens().background(getPillSwitchInfo()), shape),
state = lazyListState
) {
metadataList.forEachIndexed { index, pillMetadata ->
item(index.toString()) {
pillMetadata.selected = (selectedIndex == index)
PillButton(
pillMetadata,
modifier = Modifier
.wrapContentWidth()
.onFocusEvent { focusState ->
if (focusState.isFocused) {
scope.launch {
lazyListState.animateScrollToItem(
max(0, index - 2)
)
}
metadataList.forEachIndexed { index, pillMetadata ->
item(index.toString()) {
pillMetadata.selected = (selectedIndex == index)
PillButton(
pillMetadata,
modifier = Modifier
.wrapContentWidth()
.onFocusEvent { focusState ->
if (focusState.isFocused) {
scope.launch {
lazyListState.animateScrollToItem(
max(0, index - 2)
)
}
},
style = style,
pillButtonTokens = pillButtonTokens
)
}
}
},
style = style,
pillButtonTokens = pillButtonTokens
)
}
}
}
}
}
@Composable
fun getPillSwitchTokens(): PillSwitchTokens {
return LocalPillSwitchTokens.current
}
@Composable
fun getPillSwitchInfo(): PillSwitchInfo {
return LocalPillSwitchInfo.current
}

Просмотреть файл

@ -2,12 +2,9 @@ package com.microsoft.fluentui.tokenized.segmentedcontrols
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp
@ -18,9 +15,6 @@ import com.microsoft.fluentui.theme.token.controlTokens.PillButtonTokens
import com.microsoft.fluentui.theme.token.controlTokens.PillTabsInfo
import com.microsoft.fluentui.theme.token.controlTokens.PillTabsTokens
val LocalPillTabsTokens = compositionLocalOf { PillTabsTokens() }
val LocalPillTabsInfo = compositionLocalOf { PillTabsInfo() }
/**
* API to create PillTabs. The PillTabs control is a linear set of two or more PillButton, each of which functions as a mutually exclusive button.
* Within the control, all PillButton are equal in width.
@ -53,52 +47,38 @@ fun PillTabs(
tabsTokens
?: FluentTheme.controlTokens.tokens[ControlTokens.ControlType.PillTabs] as PillTabsTokens
CompositionLocalProvider(
LocalPillTabsTokens provides token,
LocalPillTabsInfo provides PillTabsInfo(style)
) {
val shape = RoundedCornerShape(50)
val pillTabsInfo = PillTabsInfo(style)
val shape = RoundedCornerShape(50)
if (scrollable && metadataList.size > 4) {
metadataList.forEachIndexed { index, pillMetaData ->
pillMetaData.selected = index == selectedIndex
}
PillBar(
metadataList,
modifier = modifier,
style = style,
showBackground = false,
pillButtonTokens = pillButtonTokens,
pillBarTokens = tabsTokens
)
} else {
Row(
modifier = modifier
.clip(shape)
.padding(horizontal = 16.dp)
.background(getPillTabsTokens().trackBackground(getPillTabsInfo()), shape)
) {
metadataList.forEachIndexed { index, pillMetadata ->
pillMetadata.selected = (selectedIndex == index)
PillButton(
pillMetadata,
modifier = Modifier
.weight(1F),
style = style,
pillButtonTokens = pillButtonTokens
)
}
if (scrollable && metadataList.size > 4) {
metadataList.forEachIndexed { index, pillMetaData ->
pillMetaData.selected = index == selectedIndex
}
PillBar(
metadataList,
modifier = modifier,
style = style,
showBackground = false,
pillButtonTokens = pillButtonTokens,
pillBarTokens = tabsTokens
)
} else {
Row(
modifier = modifier
.clip(shape)
.padding(horizontal = 16.dp)
.background(token.trackBackground(pillTabsInfo), shape)
) {
metadataList.forEachIndexed { index, pillMetadata ->
pillMetadata.selected = (selectedIndex == index)
PillButton(
pillMetadata,
modifier = Modifier
.weight(1F),
style = style,
pillButtonTokens = pillButtonTokens
)
}
}
}
}
@Composable
fun getPillTabsTokens(): PillTabsTokens {
return LocalPillTabsTokens.current
}
@Composable
fun getPillTabsInfo(): PillTabsInfo {
return LocalPillTabsInfo.current
}

Просмотреть файл

@ -7,8 +7,6 @@ import androidx.compose.foundation.layout.*
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
@ -31,9 +29,6 @@ import com.microsoft.fluentui.theme.token.controlTokens.AppBarInfo
import com.microsoft.fluentui.theme.token.controlTokens.AppBarSize
import com.microsoft.fluentui.theme.token.controlTokens.AppBarTokens
private val LocalAppBarTokens = compositionLocalOf { AppBarTokens() }
private val LocalAppBarInfo = compositionLocalOf { AppBarInfo(FluentStyle.Neutral) }
/**
* An app bar appears at the top of an app screen, below the status bar,
* and enables navigation through a series of hierarchical screens.
@ -90,207 +85,193 @@ fun AppBar(
val token = appBarTokens
?: FluentTheme.controlTokens.tokens[ControlTokens.ControlType.AppBar] as AppBarTokens
CompositionLocalProvider(
LocalAppBarTokens provides token,
LocalAppBarInfo provides AppBarInfo(style, appBarSize)
val appBarInfo = AppBarInfo(style, appBarSize)
Surface(
modifier = modifier
.fillMaxWidth()
) {
Surface(
modifier = modifier
Column(
modifier = Modifier
.fillMaxWidth()
.background(token.backgroundColor(appBarInfo))
) {
Column(
modifier = Modifier
Row(
Modifier
.requiredHeight(56.dp * appTitleDelta)
.animateContentSize()
.fillMaxWidth()
.background(getAppBarTokens().backgroundColor(getAppBarInfo()))
.scale(scaleX = 1.0F, scaleY = appTitleDelta)
.alpha(if (appTitleDelta != 1.0F) appTitleDelta / 3 else 1.0F),
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically
) {
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()) {
Box(
modifier = Modifier
.then(
if (navigationIcon.onClick != null) {
Modifier.clickable(
role = Role.Button,
onClick = navigationIcon.onClick!!
)
} else
Modifier
), contentAlignment = Alignment.Center
) {
Icon(
navigationIcon,
modifier = Modifier
.padding(getAppBarTokens().navigationIconPadding(getAppBarInfo()))
.size(getAppBarTokens().leftIconSize(getAppBarInfo())),
tint = getAppBarTokens().navigationIconColor(getAppBarInfo())
)
}
}
if (appBarSize != AppBarSize.Medium) {
Box(
modifier = Modifier
.then(
if (appBarSize == AppBarSize.Large)
Modifier.padding(start = 16.dp)
else
Modifier
)
) {
logo?.invoke()
}
}
val titleTextStyle = getAppBarTokens().titleTypography(getAppBarInfo())
val subtitleTextStyle = getAppBarTokens().subtitleTypography(getAppBarInfo())
if (appBarSize != AppBarSize.Large && !subTitle.isNullOrBlank()) {
Column(
modifier = Modifier
.weight(1F)
.padding(getAppBarTokens().textPadding(getAppBarInfo()))
) {
Row(
modifier = Modifier
.then(
if (postTitleIcon.onClick != null && appBarSize == AppBarSize.Small)
Modifier.clickable(onClick = postTitleIcon.onClick!!)
else
Modifier
), verticalAlignment = Alignment.CenterVertically
) {
Text(
text = title,
color = getAppBarTokens().titleTextColor(getAppBarInfo()),
style = titleTextStyle.merge(
TextStyle(
platformStyle = PlatformTextStyle(includeFontPadding = true)
)
),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
if (postTitleIcon.isIconAvailable() && appBarSize == AppBarSize.Small)
Icon(
postTitleIcon,
modifier = Modifier
.size(getAppBarTokens().titleIconSize(getAppBarInfo())),
tint = getAppBarTokens().titleIconColor(getAppBarInfo())
if (appBarSize != AppBarSize.Large && navigationIcon.isIconAvailable()) {
Box(
modifier = Modifier
.then(
if (navigationIcon.onClick != null) {
Modifier.clickable(
role = Role.Button,
onClick = navigationIcon.onClick!!
)
}
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(
getAppBarTokens().subtitleIconSize(
getAppBarInfo()
)
),
tint = getAppBarTokens().subtitleIconColor(getAppBarInfo())
)
Text(
subTitle,
color = getAppBarTokens().subtitleTextColor(
getAppBarInfo()
),
style = subtitleTextStyle.merge(
TextStyle(
platformStyle = PlatformTextStyle(includeFontPadding = true)
)
),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
if (postSubtitleIcon.isIconAvailable())
Icon(
postSubtitleIcon,
modifier = Modifier
.size(
getAppBarTokens().subtitleIconSize(
getAppBarInfo()
)
),
tint = getAppBarTokens().subtitleIconColor(getAppBarInfo())
)
}
}
} else {
Text(
text = title,
} else
Modifier
), contentAlignment = Alignment.Center
) {
Icon(
navigationIcon,
modifier = Modifier
.padding(getAppBarTokens().textPadding(getAppBarInfo()))
.weight(1F),
color = getAppBarTokens().titleTextColor(getAppBarInfo()),
style = titleTextStyle.merge(
TextStyle(
platformStyle = PlatformTextStyle(
includeFontPadding = true
)
)
),
maxLines = 1,
overflow = TextOverflow.Ellipsis
.padding(token.navigationIconPadding(appBarInfo))
.size(token.leftIconSize(appBarInfo)),
tint = token.navigationIconColor(appBarInfo)
)
}
}
if (rightAccessoryView != null) {
rightAccessoryView()
if (appBarSize != AppBarSize.Medium) {
Box(
modifier = Modifier
.then(
if (appBarSize == AppBarSize.Large)
Modifier.padding(start = 16.dp)
else
Modifier
)
) {
logo?.invoke()
}
}
if (searchBar != null) {
Row(
modifier
.animateContentSize()
.fillMaxWidth()
.then(if (!searchMode) Modifier.height(56.dp * accessoryDelta) else Modifier)
.padding(vertical = 8.dp),
horizontalArrangement = Arrangement.Center
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))
) {
searchBar()
Row(
modifier = Modifier
.then(
if (postTitleIcon.onClick != null && appBarSize == AppBarSize.Small)
Modifier.clickable(onClick = postTitleIcon.onClick!!)
else
Modifier
), verticalAlignment = Alignment.CenterVertically
) {
Text(
text = title,
color = token.titleTextColor(appBarInfo),
style = titleTextStyle.merge(
TextStyle(
platformStyle = PlatformTextStyle(includeFontPadding = true)
)
),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
if (postTitleIcon.isIconAvailable() && appBarSize == AppBarSize.Small)
Icon(
postTitleIcon,
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)
)
Text(
subTitle,
color = token.subtitleTextColor(
appBarInfo
),
style = subtitleTextStyle.merge(
TextStyle(
platformStyle = PlatformTextStyle(includeFontPadding = true)
)
),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
if (postSubtitleIcon.isIconAvailable())
Icon(
postSubtitleIcon,
modifier = Modifier
.size(
token.subtitleIconSize(
appBarInfo
)
),
tint = token.subtitleIconColor(appBarInfo)
)
}
}
} else {
Text(
text = title,
modifier = Modifier
.padding(token.textPadding(appBarInfo))
.weight(1F),
color = token.titleTextColor(appBarInfo),
style = titleTextStyle.merge(
TextStyle(
platformStyle = PlatformTextStyle(
includeFontPadding = true
)
)
),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
if (bottomBar != null && (searchMode || searchBar == null)) {
Row(
Modifier
.animateContentSize()
.fillMaxWidth()
.then(if (!searchMode) Modifier.height(48.dp * accessoryDelta) else Modifier)
.padding(vertical = 8.dp)
) {
bottomBar()
}
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),
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)
) {
bottomBar()
}
}
}
}
}
@Composable
private fun getAppBarTokens(): AppBarTokens {
return LocalAppBarTokens.current
}
@Composable
private fun getAppBarInfo(): AppBarInfo {
return LocalAppBarInfo.current
}

Просмотреть файл

@ -52,9 +52,6 @@ import com.microsoft.fluentui.tokenized.persona.SearchBarPersonaChip
import com.microsoft.fluentui.tokenized.progress.CircularProgressIndicator
import com.microsoft.fluentui.topappbars.R
private val LocalSearchBarTokens = compositionLocalOf { SearchBarTokens() }
private val LocalSearchBarInfo = compositionLocalOf { SearchBarInfo(FluentStyle.Neutral) }
/**
* API to create a searchbar. This control takes input from user's keyboard and runs it against a lambda
* function provided by user to generate results. It allows user to select a person and display in the form
@ -99,326 +96,311 @@ fun SearchBar(
val token = searchBarTokens
?: FluentTheme.controlTokens.tokens[ControlTokens.ControlType.SearchBar] as SearchBarTokens
val searchBarInfo = SearchBarInfo(style)
val focusManager = LocalFocusManager.current
val focusRequester = remember { FocusRequester() }
CompositionLocalProvider(
LocalSearchBarTokens provides token,
LocalSearchBarInfo provides SearchBarInfo(style)
var queryText by rememberSaveable { mutableStateOf("") }
var searchHasFocus by rememberSaveable { mutableStateOf(false) }
var personaChipSelected by rememberSaveable { mutableStateOf(false) }
var selectedPerson: Person? = selectedPerson
val scope = rememberCoroutineScope()
Row(
modifier = modifier
.background(token.backgroundColor(searchBarInfo))
.padding(token.searchBarPadding(searchBarInfo))
) {
val focusManager = LocalFocusManager.current
val focusRequester = remember { FocusRequester() }
var queryText by rememberSaveable { mutableStateOf("") }
var searchHasFocus by rememberSaveable { mutableStateOf(false) }
var personaChipSelected by rememberSaveable { mutableStateOf(false) }
var selectedPerson: Person? = selectedPerson
val scope = rememberCoroutineScope()
Row(
modifier = modifier
.background(getSearchBarTokens().backgroundColor(getSearchBarInfo()))
.padding(getSearchBarTokens().searchBarPadding(getSearchBarInfo()))
Modifier
.requiredHeight(token.height(searchBarInfo))
.fillMaxWidth()
.clip(RoundedCornerShape(8.dp))
.background(
token.inputBackgroundColor(searchBarInfo),
RoundedCornerShape(8.dp)
),
verticalAlignment = Alignment.CenterVertically
) {
Row(
Modifier
.requiredHeight(getSearchBarTokens().height(getSearchBarInfo()))
.fillMaxWidth()
.clip(RoundedCornerShape(8.dp))
.background(
getSearchBarTokens().inputBackgroundColor(getSearchBarInfo()),
RoundedCornerShape(8.dp)
),
verticalAlignment = Alignment.CenterVertically
) {
//Left Section
AnimatedContent(searchHasFocus) {
var onClick: (() -> Unit)? = null
var icon: ImageVector? = null
var contentDescription: String? = null
//Left Section
AnimatedContent(searchHasFocus) {
var onClick: (() -> Unit)? = null
var icon: ImageVector? = null
var contentDescription: String? = null
var mirrorImage = false
var mirrorImage = false
when (it) {
true -> {
onClick = {
queryText = ""
selectedPerson = null
onValueChange(queryText, selectedPerson)
focusManager.clearFocus()
searchHasFocus = false
navigationIconCallback?.invoke()
}
icon = SearchBarIcons.Arrowback
contentDescription =
LocalContext.current.resources.getString(R.string.fluentui_back)
if (LocalLayoutDirection.current == LayoutDirection.Rtl)
mirrorImage = true
}
false -> {
onClick = {
focusRequester.requestFocus()
}
icon = SearchBarIcons.Search
contentDescription =
LocalContext.current.resources.getString(R.string.fluentui_search)
mirrorImage = false
}
}
Box(
modifier = Modifier
.size(44.dp, 40.dp)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(),
enabled = enabled,
onClick = onClick,
role = Role.Button
),
contentAlignment = Alignment.Center
) {
Icon(
FluentIcon(
icon,
contentDescription = contentDescription,
tint = getSearchBarTokens().leftIconColor(getSearchBarInfo()),
flipOnRtl = mirrorImage
),
modifier = Modifier
.size(getSearchBarTokens().leftIconSize(getSearchBarInfo())),
)
}
}
//Center Section
Row(
modifier = Modifier
.height(24.dp)
.weight(1F)
.onKeyEvent {
if (it.key == Key.Backspace) {
if (personaChipSelected) {
selectedPerson = null
personaChipSelected = false
onValueChange(queryText, selectedPerson)
} else {
personaChipSelected = true
}
}
true
},
verticalAlignment = Alignment.CenterVertically
) {
LaunchedEffect(selectedPerson) {
queryText = ""
if (personaChipSelected)
personaChipSelected = false
onValueChange(queryText, selectedPerson)
}
if (selectedPerson != null) {
SearchBarPersonaChip(
person = selectedPerson!!,
modifier = Modifier.padding(end = 8.dp),
style = style,
enabled = enabled,
selected = personaChipSelected,
onClick = {
personaChipSelected = !personaChipSelected
personaChipOnClick?.invoke()
},
onCloseClick = {
selectedPerson = null
onValueChange(queryText, selectedPerson)
}
)
}
BasicTextField(
value = queryText,
onValueChange = {
queryText = it
personaChipSelected = false
when (it) {
true -> {
onClick = {
queryText = ""
selectedPerson = null
onValueChange(queryText, selectedPerson)
},
singleLine = true,
keyboardOptions = keyboardOptions,
keyboardActions = keyboardActions,
modifier = Modifier
.weight(1F)
.focusRequester(focusRequester)
.onFocusChanged { focusState ->
when {
focusState.isFocused ->
searchHasFocus = true
}
}
.padding(horizontal = 8.dp)
.semantics {contentDescription = searchHint},
textStyle = getSearchBarTokens().typography(getSearchBarInfo()).merge(
TextStyle(
color = getSearchBarTokens().textColor(getSearchBarInfo()),
textDirection = TextDirection.ContentOrLtr
)
),
decorationBox = @Composable { innerTextField ->
Box(
Modifier.fillMaxWidth(),
contentAlignment = if (LocalLayoutDirection.current == LayoutDirection.Rtl)
Alignment.CenterEnd
else
Alignment.CenterStart
) {
if (queryText.isEmpty()) {
Text(
searchHint,
style = getSearchBarTokens().typography(getSearchBarInfo()),
color = getSearchBarTokens().textColor(getSearchBarInfo())
)
}
}
innerTextField()
},
cursorBrush = getSearchBarTokens().cursorColor(getSearchBarInfo())
)
}
LaunchedEffect(Unit) {
if (focusByDefault)
focusRequester.requestFocus()
focusManager.clearFocus()
searchHasFocus = false
navigationIconCallback?.invoke()
}
icon = SearchBarIcons.Arrowback
contentDescription =
LocalContext.current.resources.getString(R.string.fluentui_back)
if (LocalLayoutDirection.current == LayoutDirection.Rtl)
mirrorImage = true
}
false -> {
onClick = {
focusRequester.requestFocus()
}
icon = SearchBarIcons.Search
contentDescription =
LocalContext.current.resources.getString(R.string.fluentui_search)
mirrorImage = false
}
}
//Right Section
AnimatedContent((queryText.isBlank() && selectedPerson == null)) {
when (it) {
true ->
if (microphoneCallback != null) {
Box(
modifier = Modifier
.size(44.dp, 40.dp)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(),
enabled = enabled,
onClick = microphoneCallback,
role = Role.Button
),
contentAlignment = Alignment.Center
) {
Icon(
FluentIcon(
SearchBarIcons.Microphone,
contentDescription = LocalContext.current.resources.getString(
R.string.fluentui_microphone
),
tint = getSearchBarTokens().rightIconColor(
getSearchBarInfo()
)
),
modifier = Modifier
.size(
getSearchBarTokens().rightIconSize(
getSearchBarInfo()
)
)
)
}
Box(
modifier = Modifier
.size(44.dp, 40.dp)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(),
enabled = enabled,
onClick = onClick,
role = Role.Button
),
contentAlignment = Alignment.Center
) {
Icon(
FluentIcon(
icon,
contentDescription = contentDescription,
tint = token.leftIconColor(searchBarInfo),
flipOnRtl = mirrorImage
),
modifier = Modifier
.size(token.leftIconSize(searchBarInfo)),
)
}
}
//Center Section
Row(
modifier = Modifier
.height(24.dp)
.weight(1F)
.onKeyEvent {
if (it.key == Key.Backspace) {
if (personaChipSelected) {
selectedPerson = null
personaChipSelected = false
onValueChange(queryText, selectedPerson)
} else {
personaChipSelected = true
}
false ->
}
true
},
verticalAlignment = Alignment.CenterVertically
) {
LaunchedEffect(selectedPerson) {
queryText = ""
if (personaChipSelected)
personaChipSelected = false
onValueChange(queryText, selectedPerson)
}
if (selectedPerson != null) {
SearchBarPersonaChip(
person = selectedPerson!!,
modifier = Modifier.padding(end = 8.dp),
style = style,
enabled = enabled,
selected = personaChipSelected,
onClick = {
personaChipSelected = !personaChipSelected
personaChipOnClick?.invoke()
},
onCloseClick = {
selectedPerson = null
onValueChange(queryText, selectedPerson)
}
)
}
BasicTextField(
value = queryText,
onValueChange = {
queryText = it
personaChipSelected = false
onValueChange(queryText, selectedPerson)
},
singleLine = true,
keyboardOptions = keyboardOptions,
keyboardActions = keyboardActions,
modifier = Modifier
.weight(1F)
.focusRequester(focusRequester)
.onFocusChanged { focusState ->
when {
focusState.isFocused ->
searchHasFocus = true
}
}
.padding(horizontal = 8.dp)
.semantics {contentDescription = searchHint},
textStyle = token.typography(searchBarInfo).merge(
TextStyle(
color = token.textColor(searchBarInfo),
textDirection = TextDirection.ContentOrLtr
)
),
decorationBox = @Composable { innerTextField ->
Box(
Modifier.fillMaxWidth(),
contentAlignment = if (LocalLayoutDirection.current == LayoutDirection.Rtl)
Alignment.CenterEnd
else
Alignment.CenterStart
) {
if (queryText.isEmpty()) {
Text(
searchHint,
style = token.typography(searchBarInfo),
color = token.textColor(searchBarInfo)
)
}
}
innerTextField()
},
cursorBrush = token.cursorColor(searchBarInfo)
)
}
LaunchedEffect(Unit) {
if (focusByDefault)
focusRequester.requestFocus()
}
//Right Section
AnimatedContent((queryText.isBlank() && selectedPerson == null)) {
when (it) {
true ->
if (microphoneCallback != null) {
Box(
modifier = Modifier
.size(44.dp, 40.dp)
.padding(
getSearchBarTokens().progressIndicatorRightPadding(
getSearchBarInfo()
)
)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(),
enabled = enabled,
onClick = {
queryText = ""
selectedPerson = null
onValueChange(queryText, selectedPerson)
},
onClick = microphoneCallback,
role = Role.Button
),
contentAlignment = Alignment.Center
) {
if (loading) {
CircularProgressIndicator(
size = getSearchBarTokens().circularProgressIndicatorSize(
getSearchBarInfo()
)
)
}
Icon(
FluentIcon(
SearchBarIcons.Dismisscircle,
SearchBarIcons.Microphone,
contentDescription = LocalContext.current.resources.getString(
R.string.fluentui_clear_text
R.string.fluentui_microphone
),
tint = getSearchBarTokens().rightIconColor(getSearchBarInfo())
tint = token.rightIconColor(
searchBarInfo
)
),
modifier = Modifier
.size(getSearchBarTokens().rightIconSize(getSearchBarInfo()))
.size(
token.rightIconSize(
searchBarInfo
)
)
)
}
}
}
if (rightAccessoryIcon?.isIconAvailable() == true && rightAccessoryIcon.onClick != null) {
Row(
modifier = Modifier
.size(44.dp, 40.dp)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(),
enabled = enabled,
onClick = rightAccessoryIcon.onClick!!,
role = Role.Button
),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
Icon(
rightAccessoryIcon,
}
false ->
Box(
modifier = Modifier
.size(getSearchBarTokens().rightIconSize(getSearchBarInfo())),
tint = getSearchBarTokens().rightIconColor(getSearchBarInfo())
)
Icon(
FluentIcon(
ListItemIcons.Chevron,
contentDescription = LocalContext.current.resources.getString(R.string.fluentui_chevron),
tint = getSearchBarTokens().rightIconColor(getSearchBarInfo())
),
Modifier.rotate(90F)
)
}
.size(44.dp, 40.dp)
.padding(
token.progressIndicatorRightPadding(
searchBarInfo
)
)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(),
enabled = enabled,
onClick = {
queryText = ""
selectedPerson = null
onValueChange(queryText, selectedPerson)
},
role = Role.Button
),
contentAlignment = Alignment.Center
) {
if (loading) {
CircularProgressIndicator(
size = token.circularProgressIndicatorSize(
searchBarInfo
)
)
}
Icon(
FluentIcon(
SearchBarIcons.Dismisscircle,
contentDescription = LocalContext.current.resources.getString(
R.string.fluentui_clear_text
),
tint = token.rightIconColor(searchBarInfo)
),
modifier = Modifier
.size(token.rightIconSize(searchBarInfo))
)
}
}
}
if (rightAccessoryIcon?.isIconAvailable() == true && rightAccessoryIcon.onClick != null) {
Row(
modifier = Modifier
.size(44.dp, 40.dp)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(),
enabled = enabled,
onClick = rightAccessoryIcon.onClick!!,
role = Role.Button
),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
Icon(
rightAccessoryIcon,
modifier = Modifier
.size(token.rightIconSize(searchBarInfo)),
tint = token.rightIconColor(searchBarInfo)
)
Icon(
FluentIcon(
ListItemIcons.Chevron,
contentDescription = LocalContext.current.resources.getString(R.string.fluentui_chevron),
tint = token.rightIconColor(searchBarInfo)
),
Modifier.rotate(90F)
)
}
}
}
}
}
@Composable
private fun getSearchBarTokens(): SearchBarTokens {
return LocalSearchBarTokens.current
}
@Composable
private fun getSearchBarInfo(): SearchBarInfo {
return LocalSearchBarInfo.current
}