* Created controls: TabItem, Header, Divider. Implemented ItemListContentBuilder api to create horizontal list, vertical grid, vertical list

* Renaming + some fix

* Update Divider token + Fix accessibility in Header control + renaming

* Optimized code

* Added missing parameter for function
This commit is contained in:
mishramayank1 2022-12-06 17:10:59 +05:30 коммит произвёл GitHub
Родитель 3c357ba000
Коммит 34e52d1aeb
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
11 изменённых файлов: 1092 добавлений и 53 удалений

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

@ -4,9 +4,12 @@ import android.content.Context
import android.os.Bundle
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -15,18 +18,25 @@ import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import com.microsoft.fluentui.persona.PersonaListView
import com.microsoft.fluentui.theme.AppThemeController
import com.microsoft.fluentui.theme.FluentTheme
import com.microsoft.fluentui.theme.ThemeMode
import com.microsoft.fluentui.theme.token.AliasTokens
import com.microsoft.fluentui.theme.token.controlTokens.ButtonSize
import com.microsoft.fluentui.theme.token.controlTokens.ButtonStyle
import com.microsoft.fluentui.tokenized.bottomsheet.BottomSheet
import com.microsoft.fluentui.tokenized.bottomsheet.BottomSheetState
import com.microsoft.fluentui.tokenized.bottomsheet.BottomSheetValue
import com.microsoft.fluentui.tokenized.bottomsheet.rememberBottomSheetState
import com.microsoft.fluentui.tokenized.contentBuilder.ItemData
import com.microsoft.fluentui.tokenized.contentBuilder.ListContentBuilder
import com.microsoft.fluentui.tokenized.controls.Button
import com.microsoft.fluentui.tokenized.controls.RadioButton
import com.microsoft.fluentui.tokenized.controls.ToggleSwitch
import com.microsoft.fluentui.util.activity
import com.microsoft.fluentuidemo.DemoActivity
@ -62,8 +72,6 @@ private fun CreateActivityUI() {
var peekHeightState by remember { mutableStateOf(110.dp) }
var toggleBottomSheetContent by remember { mutableStateOf(true) }
var hidden by remember { mutableStateOf(true) }
val bottomSheetState = rememberBottomSheetState(BottomSheetValue.Shown)
@ -72,7 +80,34 @@ private fun CreateActivityUI() {
hidden = !bottomSheetState.isVisible
var sheetContentState by remember { mutableStateOf(content1) }
val context = LocalContext.current
val contentByListContentBuilder = ListContentBuilder()
.addHorizontalList(getSingleLineList(context), "Default: Wrapped")
.addDivider()
.addHorizontalList(getSingleLineList(context), "Fixed width", fixedWidth = true)
.addDivider()
.addVerticalGrid(
getSingleLineList(context),
"Vertical Grid",
3
)
.addDivider()
.addVerticalGrid(
getSingleLineList(context),
"Vertical Grid: Equidistant",
3,
true
)
.addVerticalList(getDoubleLineList(context), "Double Line List")
.addVerticalList(
getSingleLineList(context),
"Single Line List"
)
.getContent()
var sheetContentState by remember { mutableStateOf(contentByListContentBuilder) }
val content = listOf(0, 1, 2)
val selectedOption = remember { mutableStateOf(content[0]) }
BottomSheet(
sheetContent = sheetContentState,
expandable = expandableState,
@ -89,7 +124,7 @@ private fun CreateActivityUI() {
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
com.microsoft.fluentui.tokenized.controls.Button(
Button(
style = ButtonStyle.OutlinedButton,
size = ButtonSize.Medium,
text = "Show",
@ -100,7 +135,7 @@ private fun CreateActivityUI() {
}
)
com.microsoft.fluentui.tokenized.controls.Button(
Button(
style = ButtonStyle.OutlinedButton,
size = ButtonSize.Medium,
text = "Jump Show",
@ -128,7 +163,7 @@ private fun CreateActivityUI() {
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
com.microsoft.fluentui.tokenized.controls.Button(
Button(
style = ButtonStyle.OutlinedButton,
size = ButtonSize.Medium,
text = "Hide",
@ -139,7 +174,7 @@ private fun CreateActivityUI() {
}
)
com.microsoft.fluentui.tokenized.controls.Button(
Button(
style = ButtonStyle.OutlinedButton,
size = ButtonSize.Medium,
text = "Expand",
@ -207,14 +242,14 @@ private fun CreateActivityUI() {
themeMode = ThemeMode.Auto
)
)
com.microsoft.fluentui.tokenized.controls.Button(
Button(
style = ButtonStyle.Button,
size = ButtonSize.Medium,
text = "+ 8 dp",
enabled = !hidden,
onClick = { peekHeightState += 8.dp })
com.microsoft.fluentui.tokenized.controls.Button(
Button(
style = ButtonStyle.Button,
size = ButtonSize.Medium,
text = "- 8 dp",
@ -222,21 +257,70 @@ private fun CreateActivityUI() {
onClick = { peekHeightState -= 8.dp })
}
com.microsoft.fluentui.tokenized.controls.Button(
style = ButtonStyle.OutlinedButton,
size = ButtonSize.Medium,
text = "Toggle BottomSheet Content",
onClick = {
toggleBottomSheetContent = !toggleBottomSheetContent
sheetContentState = if (toggleBottomSheetContent) {
content1
} else {
content2
}
}
)
Row {
Text(
text = "Select SheetContent",
fontWeight = FontWeight.Bold,
modifier = Modifier.weight(1F),
color = AppThemeController.aliasTokens.value!!.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.Foreground1].value(
themeMode = ThemeMode.Auto
)
)
}
com.microsoft.fluentui.tokenized.controls.Button(
Row {
Text(
text = "From ItemListContentBuilder",
modifier = Modifier.weight(1F),
color = AppThemeController.aliasTokens.value!!.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.Foreground1].value(
themeMode = ThemeMode.Auto
)
)
RadioButton(
selected = (selectedOption.value == content[0]),
onClick = {
selectedOption.value = content[0]
sheetContentState = contentByListContentBuilder
}
)
}
Row {
Text(
text = "Using AndroidView",
modifier = Modifier.weight(1F),
color = AppThemeController.aliasTokens.value!!.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.Foreground1].value(
themeMode = ThemeMode.Auto
)
)
RadioButton(
selected = (selectedOption.value == content[1]),
onClick = {
selectedOption.value = content[1]
sheetContentState = content1(bottomSheetState)
}
)
}
Row {
Text(
text = "Compose Content",
modifier = Modifier.weight(1F),
color = AppThemeController.aliasTokens.value!!.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.Foreground1].value(
themeMode = ThemeMode.Auto
)
)
RadioButton(
selected = (selectedOption.value == content[2]),
onClick = {
selectedOption.value = content[2]
sheetContentState = content2(bottomSheetState)
}
)
}
Button(
style = ButtonStyle.OutlinedButton,
size = ButtonSize.Medium,
enabled = !hidden,
@ -283,18 +367,25 @@ private fun CreateActivityUI() {
)
{
Text(text = stringResource(R.string.large_scrollable_text))
Text(
text = context.resources.getString(R.string.large_scrollable_text),
color = FluentTheme.aliasTokens.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.Foreground1].value(
themeMode = ThemeMode.Auto
)
)
}
}
}
}
val content1: @Composable () -> Unit = {
fun content1(bottomSheetState: BottomSheetState): @Composable () -> Unit = {
lateinit var context: Context
val scope = rememberCoroutineScope()
val state = rememberScrollState()
AndroidView(
modifier = Modifier
.fillMaxWidth()
.verticalScroll(rememberScrollState()),
.verticalScroll(state),
factory = {
context = it
val view = it.activity!!.layoutInflater.inflate(
@ -305,21 +396,29 @@ val content1: @Composable () -> Unit = {
(view as PersonaListView).personas = personaList
view
}
) {}
) {
if (bottomSheetState.currentValue == BottomSheetValue.Shown) {
scope.launch {
state.animateScrollTo(0)
}
}
}
}
val content2: @Composable () -> Unit = {
fun content2(bottomSheetState: BottomSheetState): @Composable () -> Unit = {
val no = remember { mutableStateOf(0) }
val lazyListState = rememberLazyListState()
LazyColumn(
state = lazyListState,
horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier
.fillMaxWidth()
) {
item {
com.microsoft.fluentui.tokenized.controls.Button(
Button(
style = ButtonStyle.Button,
size = ButtonSize.Medium,
text = "Click to create random size list",
onClick = { no.value = (20 * Math.random()).toInt() })
onClick = { no.value = (40 * Math.random()).toInt() })
}
repeat(no.value) {
@ -329,5 +428,67 @@ val content2: @Composable () -> Unit = {
}
}
}
val scope = rememberCoroutineScope()
LaunchedEffect(key1 = bottomSheetState.currentValue) {
if (bottomSheetState.currentValue == BottomSheetValue.Shown) {
scope.launch {
lazyListState.animateScrollToItem(0)
}
}
}
}
fun getSingleLineList(context: Context): List<ItemData> {
return arrayListOf(
ItemData(icon = Icons.Outlined.Email, title = "Email", onClick = {}),
ItemData(
icon = Icons.Outlined.ArrowBack,
title = context.resources.getString(R.string.bottom_sheet_item_reply_title),
onClick = {}
),
ItemData(
icon = Icons.Outlined.ArrowForward,
title = context.resources.getString(R.string.bottom_sheet_item_forward_title),
onClick = {},
enabled = false
),
ItemData(icon = Icons.Outlined.Favorite, title = "Favorite", onClick = {}, enabled = false),
ItemData(icon = Icons.Outlined.Info, title = "Long Info text", onClick = {}),
ItemData(icon = Icons.Outlined.Menu, title = "Menu", onClick = {}),
ItemData(icon = Icons.Outlined.Share, title = "Share", onClick = {}),
ItemData(
icon = Icons.Outlined.Delete,
title = context.resources.getString(R.string.bottom_sheet_item_delete_title),
onClick = {})
)
}
fun getDoubleLineList(context: Context): List<ItemData> {
return arrayListOf(
ItemData(
icon = Icons.Outlined.Person,
title = context.resources.getString(R.string.bottom_sheet_item_camera_title),
subTitle = context.resources.getString(R.string.bottom_sheet_item_camera_subtitle),
onClick = {}),
ItemData(
icon = Icons.Outlined.List,
title = context.resources.getString(R.string.bottom_sheet_item_gallery_title),
subTitle = context.resources.getString(R.string.bottom_sheet_item_gallery_subtitle),
onClick = {},
enabled = false
),
ItemData(
icon = Icons.Outlined.Settings,
title = context.resources.getString(R.string.bottom_sheet_item_manage_title),
subTitle = context.resources.getString(R.string.bottom_sheet_item_manage_subtitle),
onClick = {}),
ItemData(
icon = Icons.Outlined.Face,
title = context.resources.getString(R.string.bottom_sheet_item_videos_title),
subTitle = context.resources.getString(R.string.bottom_sheet_item_videos_subtitle),
onClick = {},
enabled = false
)
)
}

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

@ -239,6 +239,26 @@ fun CreateListActivityUI() {
}
}
item {
Text(
modifier = Modifier.padding(start = 16.dp, top = 24.dp, bottom = 8.dp),
text = "Header",
color = Color(0xFF2886DE)
)
ListItem.Header(
title = "Heading",
accessoryTextTitle = "Action",
accessoryTextOnClick = {},
trailingAccessoryView = { RightViewThreeButton() },
border = BorderType.Bottom
)
ListItem.Header(
title = "Heading",
accessoryTextTitle = "Action",
accessoryTextOnClick = {},
style = Subtle
)
}
item {
Text(
modifier = Modifier.padding(start = 16.dp, top = 24.dp, bottom = 8.dp),

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

@ -24,6 +24,7 @@ class ControlTokens {
CircularProgressIndicator,
ContextualCommandBar,
Drawer,
Divider,
FloatingActionButton,
LinearProgressIndicator,
ListItem,
@ -32,6 +33,7 @@ class ControlTokens {
RadioButton,
PillSwitch,
PillTabs,
TabItem,
Shimmer,
ToggleSwitch
}
@ -48,6 +50,7 @@ class ControlTokens {
ControlType.CircularProgressIndicator -> CircularProgressIndicatorTokens()
ControlType.ContextualCommandBar -> ContextualCommandBarTokens()
ControlType.Drawer -> DrawerTokens()
ControlType.Divider -> DividerTokens()
ControlType.FloatingActionButton -> FABTokens()
ControlType.LinearProgressIndicator -> LinearProgressIndicatorTokens()
ControlType.ListItem -> ListItemTokens()
@ -56,6 +59,7 @@ class ControlTokens {
ControlType.RadioButton -> RadioButtonTokens()
ControlType.PillSwitch -> PillSwitchTokens()
ControlType.PillTabs -> PillTabsTokens()
ControlType.TabItem -> TabItemTokens()
ControlType.Shimmer -> ShimmerTokens()
ControlType.ToggleSwitch -> ToggleSwitchTokens()
}

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

@ -6,10 +6,13 @@ 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
class BottomSheetInfo() : ControlInfo
@Parcelize
open class BottomSheetTokens : ControlToken, Parcelable {
companion object {
@ -17,30 +20,30 @@ open class BottomSheetTokens : ControlToken, Parcelable {
}
@Composable
open fun backgroundColor(): Color =
open fun backgroundColor(bottomSheetInfo: BottomSheetInfo): Color =
FluentTheme.aliasTokens.neutralBackgroundColor[AliasTokens.NeutralBackgroundColorTokens.Background2].value(
themeMode = FluentTheme.themeMode
)
@Composable
open fun handleColor(): Color =
open fun handleColor(bottomSheetInfo: BottomSheetInfo): Color =
FluentTheme.aliasTokens.neutralStrokeColor[AliasTokens.NeutralStrokeColorTokens.Stroke1].value(
themeMode = FluentTheme.themeMode
)
@Composable
open fun elevation(): Dp =
open fun elevation(bottomSheetInfo: BottomSheetInfo): Dp =
GlobalTokens.elevation(GlobalTokens.ShadowTokens.Shadow28)
@Composable
open fun borderRadius(): Dp =
open fun borderRadius(bottomSheetInfo: BottomSheetInfo): Dp =
GlobalTokens.borderRadius(GlobalTokens.BorderRadiusTokens.XLarge)
@Composable
open fun scrimColor(): Color =
open fun scrimColor(bottomSheetInfo: BottomSheetInfo): Color =
GlobalTokens.neutralColor(GlobalTokens.NeutralColorTokens.Black)
@Composable
open fun scrimOpacity(): Float =
open fun scrimOpacity(bottomSheetInfo: BottomSheetInfo): Float =
GlobalTokens.opacity(GlobalTokens.OpacityTokens.Opacity32)
}

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

@ -0,0 +1,29 @@
package com.microsoft.fluentui.theme.token.controlTokens
import android.os.Parcelable
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
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 kotlinx.parcelize.Parcelize
class DividerInfo : ControlInfo
@Parcelize
open class DividerTokens : ControlToken, Parcelable {
@Composable
open fun background(dividerInfo: DividerInfo): Color {
return FluentTheme.aliasTokens.neutralBackgroundColor[AliasTokens.NeutralBackgroundColorTokens.Background1].value(
FluentTheme.themeMode
)
}
@Composable
open fun dividerColor(dividerInfo: DividerInfo): Color {
return FluentTheme.aliasTokens.neutralStrokeColor[AliasTokens.NeutralStrokeColorTokens.Stroke2].value(
themeMode = FluentTheme.themeMode
)
}
}

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

@ -0,0 +1,139 @@
package com.microsoft.fluentui.theme.token.controlTokens
import android.os.Parcelable
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.microsoft.fluentui.theme.FluentTheme
import com.microsoft.fluentui.theme.ThemeMode
import com.microsoft.fluentui.theme.token.*
import kotlinx.parcelize.Parcelize
enum class TabTextAlignment {
VERTICAL,
HORIZONTAL,
NO_TEXT
}
class TabItemInfo(
val tabTextAlignment: TabTextAlignment = TabTextAlignment.VERTICAL,
val fluentStyle: FluentStyle = FluentStyle.Neutral
) : ControlInfo
@Parcelize
open class TabItemTokens : ControlToken, Parcelable {
companion object {
const val Type: String = "TabItem"
}
@Composable
open fun width(tabItemInfo: TabItemInfo): Dp {
return 64.dp
}
@Composable
open fun background(tabItemInfo: TabItemInfo): Color {
return FluentTheme.aliasTokens.neutralBackgroundColor[AliasTokens.NeutralBackgroundColorTokens.Background1].value(
FluentTheme.themeMode
)
}
@Composable
open fun rippleColor(tabItemInfo: TabItemInfo): Color {
return FluentColor(
light = GlobalTokens.neutralColor(GlobalTokens.NeutralColorTokens.Black),
dark = GlobalTokens.neutralColor(GlobalTokens.NeutralColorTokens.White)
).value(
FluentTheme.themeMode
)
}
@Composable
open fun iconColor(tabItemInfo: TabItemInfo): StateColor {
return when (tabItemInfo.fluentStyle) {
FluentStyle.Neutral -> StateColor(
rest = FluentTheme.aliasTokens.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.Foreground3].value(
themeMode = FluentTheme.themeMode
),
pressed = FluentTheme.aliasTokens.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.Foreground3].value(
themeMode = FluentTheme.themeMode
),
disabled = FluentTheme.aliasTokens.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.ForegroundDisable1].value(
themeMode = FluentTheme.themeMode
)
)
FluentStyle.Brand -> StateColor(
rest = FluentColor(
light = FluentTheme.aliasTokens.brandForegroundColor[AliasTokens.BrandForegroundColorTokens.BrandForeground1].value(
ThemeMode.Light
),
dark = FluentTheme.aliasTokens.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.Foreground1].value(
ThemeMode.Dark
)
).value(FluentTheme.themeMode),
pressed = FluentColor(
light = FluentTheme.aliasTokens.brandForegroundColor[AliasTokens.BrandForegroundColorTokens.BrandForeground1Pressed].value(
ThemeMode.Light
),
dark = FluentTheme.aliasTokens.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.Foreground1].value(
ThemeMode.Dark
)
).value(FluentTheme.themeMode),
disabled = FluentColor(
light = FluentTheme.aliasTokens.brandForegroundColor[AliasTokens.BrandForegroundColorTokens.BrandForegroundDisabled2].value(
ThemeMode.Light
),
dark = FluentTheme.aliasTokens.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.ForegroundDisable1].value(
ThemeMode.Dark
)
).value(FluentTheme.themeMode)
)
}
}
@Composable
open fun textColor(tabItemInfo: TabItemInfo): StateColor {
return when (tabItemInfo.fluentStyle) {
FluentStyle.Neutral -> StateColor(
rest = FluentTheme.aliasTokens.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.Foreground2].value(
themeMode = FluentTheme.themeMode
),
pressed = FluentTheme.aliasTokens.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.Foreground1].value(
themeMode = FluentTheme.themeMode
),
disabled = FluentTheme.aliasTokens.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.ForegroundDisable1].value(
themeMode = FluentTheme.themeMode
)
)
FluentStyle.Brand -> StateColor(
rest = FluentColor(
light = FluentTheme.aliasTokens.brandForegroundColor[AliasTokens.BrandForegroundColorTokens.BrandForeground1].value(
ThemeMode.Light
),
dark = FluentTheme.aliasTokens.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.Foreground1].value(
ThemeMode.Dark
)
).value(FluentTheme.themeMode),
pressed = FluentColor(
light = FluentTheme.aliasTokens.brandForegroundColor[AliasTokens.BrandForegroundColorTokens.BrandForeground1Pressed].value(
ThemeMode.Light
),
dark = FluentTheme.aliasTokens.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.Foreground1].value(
ThemeMode.Dark
)
).value(FluentTheme.themeMode),
disabled = FluentColor(
light = FluentTheme.aliasTokens.brandForegroundColor[AliasTokens.BrandForegroundColorTokens.BrandForegroundDisabled2].value(
ThemeMode.Light
),
dark = FluentTheme.aliasTokens.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.ForegroundDisable1].value(
ThemeMode.Dark
)
).value(FluentTheme.themeMode)
)
}
}
}

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

@ -37,6 +37,7 @@ import com.microsoft.fluentui.compose.SwipeableDefaults
import com.microsoft.fluentui.compose.SwipeableState
import com.microsoft.fluentui.theme.FluentTheme
import com.microsoft.fluentui.theme.token.ControlTokens
import com.microsoft.fluentui.theme.token.controlTokens.BottomSheetInfo
import com.microsoft.fluentui.theme.token.controlTokens.BottomSheetTokens
import com.microsoft.fluentui.tokenized.calculateFraction
import com.microsoft.fluentui.util.dpToPx
@ -183,12 +184,18 @@ 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
@ -229,18 +236,22 @@ fun BottomSheet(
val tokens = bottomSheetTokens
?: FluentTheme.controlTokens.tokens[ControlTokens.ControlType.BottomSheet] as BottomSheetTokens
CompositionLocalProvider(LocalBottomSheetTokens provides tokens) {
CompositionLocalProvider(
LocalBottomSheetTokens provides tokens,
LocalBottomSheetInfo provides BottomSheetInfo()
) {
val sheetShape: Shape = RoundedCornerShape(
topStart = getDrawerTokens().borderRadius(),
topEnd = getDrawerTokens().borderRadius()
topStart = getDrawerTokens().borderRadius(getBottomSheetInfo()),
topEnd = getDrawerTokens().borderRadius(getBottomSheetInfo())
)
val sheetElevation: Dp = getDrawerTokens().elevation()
val sheetBackgroundColor: Color = getDrawerTokens().backgroundColor()
val sheetElevation: Dp = getDrawerTokens().elevation(getBottomSheetInfo())
val sheetBackgroundColor: Color = getDrawerTokens().backgroundColor(getBottomSheetInfo())
val sheetContentColor: Color = Color.Transparent
val sheetHandleColor: Color = getDrawerTokens().handleColor()
val scrimOpacity: Float = getDrawerTokens().scrimOpacity()
val scrimColor: Color = getDrawerTokens().scrimColor().copy(alpha = scrimOpacity)
val sheetHandleColor: Color = getDrawerTokens().handleColor(getBottomSheetInfo())
val scrimOpacity: Float = getDrawerTokens().scrimOpacity(getBottomSheetInfo())
val scrimColor: Color =
getDrawerTokens().scrimColor(getBottomSheetInfo()).copy(alpha = scrimOpacity)
val scope = rememberCoroutineScope()
@ -458,8 +469,6 @@ private fun Modifier.sheetHeight(expandable: Boolean, fullHeight: Float, peekHei
return this.then(modifier)
}
//TODO : Revisit Scrim usage across module to check single scrim implementation across module.
@Composable
private fun Scrim(

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

@ -0,0 +1,333 @@
package com.microsoft.fluentui.tokenized.contentBuilder
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.*
import androidx.compose.material.Icon
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.onFocusEvent
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.unit.Dp
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.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.tabItem.TabItem
import kotlinx.coroutines.launch
import java.lang.Integer.max
import java.lang.Integer.min
import kotlin.math.ceil
data class ItemData(
var title: String,
var subTitle: String? = null,
var enabled: Boolean = true,
var onClick: () -> Unit,
var accessory: @Composable (() -> Unit)? = null,
var icon: ImageVector? = null
)
// marker interface
internal interface ContentData
// data class for list of Items to be placed horizontally
internal data class HorizontalListContentData(
val itemDataList: List<ItemData>,
val header: String? = null,
val fixedWidth: Boolean = false,
val tabItemTokens: TabItemTokens? = null
) : ContentData
// data class for list of Items to be placed vertically
internal data class VerticalListContentData(
val itemDataList: List<ItemData>,
val header: String? = null,
val listItemTokens: ListItemTokens? = null
) : ContentData
// data class for list of Items to be placed in vertical grid
internal data class VerticalGridContentData(
val itemDataList: List<ItemData>,
val header: String? = null,
val maxItemInRow: Int,
val equidistant: Boolean = false,
val tabItemTokens: TabItemTokens? = null
) : ContentData
//data class for divider
internal data class DividerContentData(val heightDp: Dp, val dividerToken: DividerTokens?) :
ContentData
/*
* Builder to create list of list (vertical, horizontal), grid.
* */
class ListContentBuilder {
private val listOfContentData: ArrayList<ContentData> = ArrayList()
private fun add(contentData: ContentData) {
listOfContentData.add(contentData)
}
fun addHorizontalList(
itemDataList: List<ItemData>,
header: String? = null,
fixedWidth: Boolean = false,
tabItemTokens: TabItemTokens? = null
): ListContentBuilder {
add(HorizontalListContentData(itemDataList, header, fixedWidth, tabItemTokens))
return this
}
fun addVerticalList(
itemDataList: List<ItemData>,
header: String? = null,
listItemTokens: ListItemTokens? = null
): ListContentBuilder {
add(VerticalListContentData(itemDataList, header, listItemTokens))
return this
}
fun addVerticalGrid(
itemDataList: List<ItemData>,
header: String? = null,
maxItemInRow: Int = 4,
equidistant: Boolean = false,
tabItemTokens: TabItemTokens? = null
): ListContentBuilder {
add(VerticalGridContentData(itemDataList, header, maxItemInRow, equidistant, tabItemTokens))
return this
}
fun addDivider(
heightDp: Dp = 1.dp,
dividerToken: DividerTokens? = null
): ListContentBuilder {
add(DividerContentData(heightDp, dividerToken))
return this
}
fun getContent(): @Composable () -> Unit {
//TODO As per the thread https://issuetracker.google.com/issues/184670295#comment34 focus
// navigation issues should resolve with compose version 1.3.0.
return {
LazyColumn {
for (contentData in listOfContentData) {
when (contentData) {
is HorizontalListContentData -> createHorizontalList(
contentData.itemDataList,
contentData.header,
contentData.fixedWidth,
contentData.tabItemTokens
)()
is VerticalListContentData -> createVerticalList(
contentData.itemDataList,
contentData.header,
contentData.listItemTokens
)()
is VerticalGridContentData -> createVerticalGrid(
contentData.itemDataList,
contentData.header,
contentData.maxItemInRow,
contentData.equidistant,
contentData.tabItemTokens
)()
is DividerContentData -> createDivider(
contentData.heightDp,
contentData.dividerToken
)()
}
}
}
}
}
private fun createVerticalGrid(
itemDataList: List<ItemData>,
header: String?,
maxItemsInRow: Int,
equidistant: Boolean,
tabItemTokens: TabItemTokens? = null
): LazyListScope.() -> Unit {
return {
if (header != null) {
item {
ListItem.Header(title = header)
}
}
val size = itemDataList.size
val itemsInRow = min(size, maxItemsInRow)
items(ceil(size * 1.0 / itemsInRow).toInt()) { row ->
val token =
tabItemTokens
?: FluentTheme.controlTokens.tokens[ControlTokens.ControlType.TabItem] as TabItemTokens
Layout(
modifier = Modifier
.background(
token.background(
TabItemInfo(
TabTextAlignment.VERTICAL,
FluentStyle.Brand
)
)
)
.padding(start = 16.dp, end = 16.dp),
content = {
var col = 0
val widthRatio = if ((row + 1) * itemsInRow <= size || !equidistant)
1.0f / itemsInRow
else
1.0f / min(itemsInRow, (size - (row * itemsInRow)))
while (col < itemsInRow && (row * itemsInRow + col) < size) {
TabItem(
title = itemDataList[row * itemsInRow + col].title,
enabled = itemDataList[row * itemsInRow + col].enabled,
onClick = itemDataList[row * itemsInRow + col].onClick,
accessory = itemDataList[row * itemsInRow + col].accessory,
icon = itemDataList[row * itemsInRow + col].icon,
modifier = Modifier.fillMaxWidth(widthRatio),
tabItemTokens = tabItemTokens
)
col++
}
}) { measurables, constraints ->
val count = measurables.size
val placeables = measurables.map { measurable ->
measurable.measure(constraints)
}
var layoutHeight = 0
placeables.forEach {
layoutHeight = layoutHeight.coerceAtLeast(it.height)
}
layout(constraints.maxWidth, layoutHeight) {
var xPosition = 0
val width = if (equidistant)
constraints.maxWidth / count
else
constraints.maxWidth / maxItemsInRow
placeables.forEach { placeable ->
placeable.placeRelative(y = 0, x = xPosition)
if (placeable != placeables.last())
xPosition += width
}
}
}
}
}
}
private fun createHorizontalList(
itemDataList: List<ItemData>,
header: String?,
fixedWidth: Boolean = false,
tabItemTokens: TabItemTokens? = null
): LazyListScope.() -> Unit {
return {
if (header != null) {
item {
ListItem.Header(title = header)
}
}
item {
val rowScope = rememberCoroutineScope()
val rowLazyListState = rememberLazyListState()
val token =
tabItemTokens
?: FluentTheme.controlTokens.tokens[ControlTokens.ControlType.TabItem] as TabItemTokens
LazyRow(
state = rowLazyListState, modifier = Modifier
.background(
token.background(
TabItemInfo(
TabTextAlignment.VERTICAL,
FluentStyle.Brand
)
)
)
.padding(start = 16.dp)
.fillMaxWidth()
) {
itemsIndexed(itemDataList) { index, item ->
TabItem(
title = item.title,
enabled = item.enabled,
fixedWidth = fixedWidth,
onClick = item.onClick,
accessory = item.accessory,
icon = item.icon,
modifier = Modifier
.onFocusEvent { focusState ->
if (focusState.isFocused) {
rowScope.launch {
rowLazyListState.animateScrollToItem(
max(0, index - 2)
)
}
}
},
tabItemTokens = tabItemTokens
)
}
}
}
}
}
private fun createVerticalList(
itemDataList: List<ItemData>,
header: String?,
listItemTokens: ListItemTokens? = null
): LazyListScope.() -> Unit {
return {
if (header != null) {
item {
ListItem.Header(title = header)
}
}
items(itemDataList) { item ->
val token = listItemTokens
?: FluentTheme.controlTokens.tokens[ControlTokens.ControlType.ListItem] as ListItemTokens
ListItem.Item(
text = item.title,
subText = item.subTitle,
leadingAccessoryView = {
if (item.icon != null) {
Icon(item.icon!!, null, tint = token.iconColor().rest)
}
},
trailingAccessoryView = item.accessory,
enabled = item.enabled,
onClick = item.onClick,
listItemTokens = listItemTokens
)
}
}
}
private fun createDivider(
heightDp: Dp,
dividerToken: DividerTokens?
): LazyListScope.() -> Unit {
return {
item {
Divider(
heightDp, dividerToken
)
}
}
}
}

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

@ -0,0 +1,57 @@
package com.microsoft.fluentui.tokenized.divider
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
import com.microsoft.fluentui.theme.FluentTheme
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,
dividerToken: DividerTokens? = null
) {
val token =
dividerToken
?: FluentTheme.controlTokens.tokens[ControlTokens.ControlType.Divider] as DividerTokens
CompositionLocalProvider(
LocalDividerTokens provides token,
LocalDividerInfo provides DividerInfo()
) {
Column(
Modifier
.fillMaxWidth()
.background(getDividerTokens().background(dividerInfo = getDividerInfo()))
.focusable(false)
.padding(vertical = 8.dp)
) {
Box(
Modifier
.fillMaxWidth()
.requiredHeight(height)
.background(getDividerTokens().dividerColor(dividerInfo = getDividerInfo()))
)
}
}
}

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

@ -4,11 +4,11 @@ import androidx.compose.animation.*
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.focusable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.InlineTextContent
import androidx.compose.foundation.text.appendInlineContent
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.Icon
import androidx.compose.material.Surface
import androidx.compose.material.Text
@ -24,7 +24,7 @@ import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.*
import androidx.compose.ui.text.*
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
@ -404,7 +404,7 @@ object ListItem {
* @param content Composable content to appear or disappear on clicking the list item
*
*/
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun SectionHeader(
title: String,
@ -621,10 +621,13 @@ object ListItem {
interactionSource = interactionSource
)
val horizontalPadding = getListItemTokens().padding(Medium)
val leadPadding = if(leadingAccessoryView == null){
val leadPadding = if (leadingAccessoryView == null) {
PaddingValues(start = horizontalPadding, end = horizontalPadding)
}else {
PaddingValues(start = getListItemTokens().padding(size = GlobalTokens.SpacingTokens.None), end = horizontalPadding)
} else {
PaddingValues(
start = getListItemTokens().padding(size = GlobalTokens.SpacingTokens.None),
end = horizontalPadding
)
}
val borderSize = getListItemTokens().borderSize().value
val borderInsetToPx =
@ -698,4 +701,135 @@ object ListItem {
}
}
}
/**
* Create a Header. Headers are list tiles that delineates heading of a list or grid list
*
* @param modifier Optional modifier for List item.
* @param title Section header title.
* @param titleMaxLines Optional max visible lines for title.
* @param accessoryTextTitle Optional accessory text.
* @param accessoryTextOnClick Optional onClick action for accessory text.
* @param enabled Optional enable/disable List item
* @param style [SectionHeaderStyle] Section header style.
* @param border [BorderType] Optional border for the list item.
* @param borderInset [BorderInset] Optional borderInset for list item.
* @param listItemTokens Optional list item tokens for list item appearance.If not provided then list tokens will be picked from [AppThemeController]
* @param trailingAccessoryView Optional composable trailing accessory view.
*
*/
@Composable
fun Header(
title: String,
modifier: Modifier = Modifier,
titleMaxLines: Int = 1,
accessoryTextTitle: String? = null,
accessoryTextOnClick: (() -> Unit)? = null,
enabled: Boolean = true,
style: SectionHeaderStyle = SectionHeaderStyle.Standard,
border: BorderType = NoBorder,
borderInset: BorderInset = None,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
trailingAccessoryView: (@Composable () -> Unit)? = null,
listItemTokens: ListItemTokens? = null
) {
val token = listItemTokens
?: FluentTheme.controlTokens.tokens[ControlType.ListItem] as ListItemTokens
CompositionLocalProvider(LocalListItemTokens provides token) {
val backgroundColor = getColorByState(
stateData = getListItemTokens().backgroundColor(),
enabled = true,
interactionSource = interactionSource
)
val cellHeight = getListItemTokens().cellHeight(listItemType = OneLine)
val textSize =
getListItemTokens().textSize(textType = ListTextType.Text, style = style)
val actionTextSize =
getListItemTokens().textSize(textType = ListTextType.ActionText, style = style)
val textColor = getColorByState(
stateData = getListItemTokens().textColor(
textType = ListTextType.Text
),
enabled = enabled,
interactionSource = interactionSource
)
val actionTextColor =
getColorByState(
stateData = getListItemTokens().textColor(
textType = ListTextType.ActionText
),
enabled = enabled,
interactionSource = interactionSource
)
val horizontalPadding = getListItemTokens().padding(Medium)
val verticalPadding = getListItemTokens().padding(size = XSmall)
val borderSize = getListItemTokens().borderSize().value
val borderInsetToPx =
with(LocalDensity.current) {
getListItemTokens().borderInset(inset = borderInset).toPx()
}
val borderColor = getColorByState(
stateData = getListItemTokens().borderColor(),
enabled = enabled,
interactionSource = interactionSource
)
Surface(
modifier = modifier
.fillMaxWidth()
.heightIn(min = cellHeight)
.background(backgroundColor)
.borderModifier(border, borderColor, borderSize, borderInsetToPx)
.focusable(false)
) {
Row(
modifier
.fillMaxWidth()
.heightIn(min = cellHeight)
.background(backgroundColor)
.focusable(true),
verticalAlignment = Alignment.Bottom
) {
Text(
text = title,
modifier = Modifier
.padding(
start = horizontalPadding,
end = horizontalPadding,
bottom = verticalPadding
)
.weight(1f),
fontSize = textSize.fontSize.size,
fontWeight = textSize.weight,
color = textColor,
maxLines = titleMaxLines,
overflow = TextOverflow.Ellipsis
)
if (accessoryTextTitle != null) {
Text(text = accessoryTextTitle,
modifier
.padding(end = horizontalPadding, bottom = verticalPadding)
.clickable(
role = Role.Button,
onClick = accessoryTextOnClick ?: {}
),
color = actionTextColor,
fontSize = actionTextSize.fontSize.size,
fontWeight = actionTextSize.weight)
}
if (trailingAccessoryView != null) {
Box(
Modifier.padding(end = horizontalPadding, bottom = verticalPadding),
contentAlignment = Alignment.BottomStart
) {
trailingAccessoryView()
}
}
}
}
}
}
}

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

@ -0,0 +1,150 @@
package com.microsoft.fluentui.tokenized.tabItem
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
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
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.style.TextAlign
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.FluentStyle
import com.microsoft.fluentui.theme.token.controlTokens.TabItemInfo
import com.microsoft.fluentui.theme.token.controlTokens.TabItemTokens
import com.microsoft.fluentui.theme.token.controlTokens.TabTextAlignment
import com.microsoft.fluentui.tokenized.listitem.getColorByState
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
internal fun TabItem(
title: String,
modifier: Modifier = Modifier,
icon: ImageVector? = null,
style: FluentStyle = FluentStyle.Neutral,
textAlignment: TabTextAlignment = TabTextAlignment.VERTICAL,
enabled: Boolean = true,
fixedWidth: Boolean = false,
onClick: () -> Unit,
accessory: (@Composable () -> Unit)?,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
tabItemTokens: TabItemTokens? = null
) {
val token =
tabItemTokens
?: FluentTheme.controlTokens.tokens[ControlTokens.ControlType.TabItem] as TabItemTokens
CompositionLocalProvider(
LocalTabItemTokens provides token,
LocalTabItemInfo provides TabItemInfo(textAlignment, style)
) {
val textColor = getColorByState(
stateData = getTabItemTokens().textColor(tabItemInfo = getTabItemInfo()),
enabled = enabled,
interactionSource = interactionSource
)
val iconColor = getColorByState(
stateData = getTabItemTokens().iconColor(tabItemInfo = getTabItemInfo()),
enabled = enabled,
interactionSource = interactionSource
)
val backgroundColor = getTabItemTokens().background(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 = {
Box(
contentAlignment = Alignment.Center
) {
if (icon != null) {
Icon(
imageVector = icon,
modifier = Modifier.height(if (textAlignment == TabTextAlignment.NO_TEXT) 28.dp else 24.dp),
contentDescription = if (textAlignment == TabTextAlignment.NO_TEXT) title else null,
tint = iconColor
)
}
if (accessory != null) {
accessory()
}
}
}
if (textAlignment == TabTextAlignment.HORIZONTAL) {
Row(
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
modifier = modifier
.then(clickableModifier)
.background(backgroundColor)
.padding(top = 8.dp, start = 4.dp, bottom = 4.dp, end = 8.dp)
.then(widthModifier)
) {
iconContent()
Spacer(modifier = Modifier.width(2.dp))
Text(
text = title,
color = textColor,
textAlign = TextAlign.Justify
)
}
} else {
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)
) {
iconContent()
if (textAlignment == TabTextAlignment.VERTICAL) {
Spacer(modifier = Modifier.height(2.dp))
Text(
text = title,
color = textColor,
textAlign = TextAlign.Center
)
}
}
}
}
}