diff --git a/FluentUI.Demo/src/main/AndroidManifest.xml b/FluentUI.Demo/src/main/AndroidManifest.xml index 3742eec6..1330ec8d 100644 --- a/FluentUI.Demo/src/main/AndroidManifest.xml +++ b/FluentUI.Demo/src/main/AndroidManifest.xml @@ -27,49 +27,51 @@ - + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - diff --git a/FluentUI.Demo/src/main/java/com/microsoft/fluentuidemo/Demos.kt b/FluentUI.Demo/src/main/java/com/microsoft/fluentuidemo/Demos.kt index 40bb4d7e..c1c4ffef 100644 --- a/FluentUI.Demo/src/main/java/com/microsoft/fluentuidemo/Demos.kt +++ b/FluentUI.Demo/src/main/java/com/microsoft/fluentuidemo/Demos.kt @@ -9,42 +9,44 @@ import com.microsoft.fluentuidemo.demos.* import java.util.* import kotlin.reflect.KClass +const val V2AVATAR = "V2 Avatar" +const val V2AVATAR_CAROUSEL = "V2 Avatar Carousel" +const val V2AVATAR_GROUP = "V2 Avatar Group" +const val V2Badge = "V2 Badge" +const val V2BASIC_CONTROLS = "V2 Basic Controls" +const val V2BASIC_INPUTS = "V2 Basic Inputs" +const val V2BOTTOM_SHEET = "V2 BottomSheet" +const val V2CONTEXTUAL_COMMAND_BAR = "V2 ContextualCommandBar" +const val V2DRAWER = "V2 Drawer" +const val V2LIST_ITEM = "V2 ListItem" +const val V2PERSONA = "V2 Persona" +const val V2PERSONA_CHIP = "V2 PersonaChip" +const val V2PERSONA_LIST = "V2 PersonaList" +const val V2PROGRESS = "V2 Progress" +const val V2SEARCHBAR = "V2 SearchBar" +const val V2SEGMENTED_CONTROL = "V2 SegmentedControl" +const val V2TABBAR = "V2 TabBar" const val ACTION_BAR_LAYOUT = "ActionBarLayout" const val APP_BAR_LAYOUT = "AppBarLayout" const val V2APP_BAR_LAYOUT = "V2 AppBarLayout" const val AVATAR_VIEW = "AvatarView" const val AVATAR_GROUP_VIEW = "AvatarGroupView" const val BASIC_INPUTS = "Basic Inputs" -const val V2AVATAR = "V2 Avatar" -const val V2AVATAR_CAROUSEL = "V2 Avatar Carousel" -const val V2AVATAR_GROUP = "V2 Avatar Group" -const val V2BASIC_INPUTS = "V2 Basic Inputs" -const val V2BASIC_CONTROLS = "V2 Basic Controls" const val BOTTOM_NAVIGATION = "BottomNavigation" const val BOTTOM_SHEET = "BottomSheet" const val CALENDAR_VIEW = "CalendarView" const val CONTEXTUAL_COMMAND_BAR = "ContextualCommandBar" -const val V2CONTEXTUAL_COMMAND_BAR = "V2 ContextualCommandBar" const val DATE_TIME_PICKER = "DateTimePicker" const val DRAWER = "Drawer" -const val V2DRAWER = "V2 Drawer" const val LIST_ITEM_VIEW = "ListItemView" -const val V2LIST_ITEM = "V2 ListItem" const val PEOPLE_PICKER_VIEW = "PeoplePickerView" const val PERSISTENT_BOTTOM_SHEET = "PersistentBottomSheet" -const val V2BOTTOM_SHEET = "V2 BottomSheet" const val PERSONA_CHIP_VIEW = "PersonaChipView" -const val V2PERSONA_CHIP = "V2 PersonaChip" const val PERSONA_LIST_VIEW = "PersonaListView" -const val V2PERSONA_LIST = "V2 PersonaList" const val PERSONA_VIEW = "PersonaView" -const val V2PERSONA = "V2 Persona" const val POPUP_MENU = "PopupMenu" const val PROGRESS = "Progress" -const val V2PROGRESS = "V2 Progress" const val SNACKBAR = "Snackbar" -const val V2SEGMENTED_CONTROL = "V2 SegmentedControl" -const val V2SEARCHBAR = "V2 SearchBar" const val TAB_LAYOUT = "TabLayout" const val TEMPLATE_VIEW = "TemplateView" const val TOOLTIP = "Tooltip" @@ -55,6 +57,7 @@ val DEMOS = arrayListOf( Demo(V2AVATAR, V2AvatarActivity::class), Demo(V2AVATAR_CAROUSEL, V2AvatarCarouselActivity::class), Demo(V2AVATAR_GROUP, V2AvatarGroupActivity::class), + Demo(V2Badge, V2BadgeActivity::class), Demo(V2BASIC_INPUTS, V2BasicInputsActivity::class), Demo(V2BASIC_CONTROLS, V2BasicControlsActivity::class), Demo(V2BOTTOM_SHEET, V2BottomSheetActivity::class), @@ -67,6 +70,7 @@ val DEMOS = arrayListOf( Demo(V2PROGRESS, V2ProgressActivity::class), Demo(V2SEARCHBAR, V2SearchBarActivity::class), Demo(V2SEGMENTED_CONTROL, V2SegmentedControlActivity::class), + Demo(V2TABBAR, V2TabBarActivity::class), Demo(ACTION_BAR_LAYOUT, ActionBarLayoutActivity::class), Demo(APP_BAR_LAYOUT, AppBarLayoutActivity::class), Demo(AVATAR_VIEW, AvatarViewActivity::class), diff --git a/FluentUI.Demo/src/main/java/com/microsoft/fluentuidemo/demos/V2BadgeActivity.kt b/FluentUI.Demo/src/main/java/com/microsoft/fluentuidemo/demos/V2BadgeActivity.kt new file mode 100644 index 00000000..86693451 --- /dev/null +++ b/FluentUI.Demo/src/main/java/com/microsoft/fluentuidemo/demos/V2BadgeActivity.kt @@ -0,0 +1,111 @@ +package com.microsoft.fluentuidemo.demos + +import android.os.Bundle +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.material.Text +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.unit.dp +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.BadgeType +import com.microsoft.fluentui.tokenized.notification.Badge +import com.microsoft.fluentuidemo.DemoActivity +import com.microsoft.fluentuidemo.R + + +class V2BadgeActivity : DemoActivity() { + override val contentLayoutId: Int + get() = R.layout.v2_activity_compose + override val contentNeedsScrollableContainer: Boolean + get() = false + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val composeHere = findViewById(R.id.compose_here) + + composeHere.setContent { + FluentTheme { + val title1Font = + FluentTheme.aliasTokens.typography[AliasTokens.TypographyTokens.Title1] + val title2Font = + FluentTheme.aliasTokens.typography[AliasTokens.TypographyTokens.Title2] + + Column(Modifier.background(Color.Gray)) { + Text( + text = resources.getString(R.string.badge_notification_badge), + fontWeight = title1Font.weight, + fontSize = title1Font.fontSize.size, + lineHeight = title1Font.fontSize.lineHeight, + color = FluentTheme.aliasTokens.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.Foreground1].value(), + modifier = Modifier.padding(8.dp) + + ) + Row(Modifier.padding(16.dp)) { + Text( + text = resources.getString(R.string.badge_notification_dot), + fontWeight = title2Font.weight, + fontSize = title2Font.fontSize.size, + lineHeight = title2Font.fontSize.lineHeight, + color = FluentTheme.aliasTokens.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.Foreground1].value() + ) + Badge() + } + + Row(Modifier.padding(16.dp)) { + Text( + text = resources.getString(R.string.badge_notification_character), + fontWeight = title2Font.weight, + fontSize = title2Font.fontSize.size, + lineHeight = title2Font.fontSize.lineHeight, + color = FluentTheme.aliasTokens.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.Foreground1].value() + ) + Spacer(modifier = Modifier.width(8.dp)) + Badge("1", badgeType = BadgeType.Character) + Spacer(modifier = Modifier.width(8.dp)) + Badge("2", badgeType = BadgeType.Character) + Spacer(modifier = Modifier.width(8.dp)) + Badge("8", badgeType = BadgeType.Character) + Spacer(modifier = Modifier.width(8.dp)) + Badge("12", badgeType = BadgeType.Character) + Spacer(modifier = Modifier.width(8.dp)) + Badge("123", badgeType = BadgeType.Character) + Spacer(modifier = Modifier.width(8.dp)) + Badge("12345678910", badgeType = BadgeType.Character) + Spacer(modifier = Modifier.width(8.dp)) + Badge("Badge", badgeType = BadgeType.Character) + } + Row(Modifier.padding(16.dp)) { + Text( + text = "List", + fontWeight = title2Font.weight, + fontSize = title2Font.fontSize.size, + lineHeight = title2Font.fontSize.lineHeight, + color = FluentTheme.aliasTokens.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.Foreground1].value( + themeMode = ThemeMode.Auto + ) + ) + Spacer(modifier = Modifier.width(8.dp)) + Badge("1", badgeType = BadgeType.List) + Spacer(modifier = Modifier.width(8.dp)) + Badge("2", badgeType = BadgeType.List) + Spacer(modifier = Modifier.width(8.dp)) + Badge("8", badgeType = BadgeType.List) + Spacer(modifier = Modifier.width(8.dp)) + Badge("12", badgeType = BadgeType.List) + Spacer(modifier = Modifier.width(8.dp)) + Badge("123", badgeType = BadgeType.List) + Spacer(modifier = Modifier.width(8.dp)) + Badge("12345678910", badgeType = BadgeType.List) + Spacer(modifier = Modifier.width(8.dp)) + Badge("Badge", badgeType = BadgeType.List) + } + } + } + } + } +} \ No newline at end of file diff --git a/FluentUI.Demo/src/main/java/com/microsoft/fluentuidemo/demos/V2TabBarActivity.kt b/FluentUI.Demo/src/main/java/com/microsoft/fluentuidemo/demos/V2TabBarActivity.kt new file mode 100644 index 00000000..7ddc05bf --- /dev/null +++ b/FluentUI.Demo/src/main/java/com/microsoft/fluentuidemo/demos/V2TabBarActivity.kt @@ -0,0 +1,215 @@ +package com.microsoft.fluentuidemo.demos + +import android.content.Context +import android.os.Bundle +import android.widget.Toast +import androidx.compose.foundation.layout.* +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.* +import androidx.compose.material.icons.outlined.* +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.unit.dp +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.BadgeType +import com.microsoft.fluentui.theme.token.controlTokens.ButtonSize +import com.microsoft.fluentui.theme.token.controlTokens.ButtonStyle +import com.microsoft.fluentui.theme.token.controlTokens.TabTextAlignment +import com.microsoft.fluentui.tokenized.controls.Button +import com.microsoft.fluentui.tokenized.controls.RadioButton +import com.microsoft.fluentui.tokenized.listitem.ListItem +import com.microsoft.fluentui.tokenized.navigation.TabBar +import com.microsoft.fluentui.tokenized.navigation.TabData +import com.microsoft.fluentui.tokenized.notification.Badge +import com.microsoft.fluentuidemo.DemoActivity +import com.microsoft.fluentuidemo.R + +class V2TabBarActivity : DemoActivity() { + override val contentLayoutId: Int + get() = R.layout.v2_activity_compose + override val contentNeedsScrollableContainer: Boolean + get() = false + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val composeHere = findViewById(R.id.compose_here) + val context = this + + composeHere.setContent { + var selectedIndex by rememberSaveable { mutableStateOf(0) } + val tabDataList = arrayListOf( + TabData( + title = resources.getString(R.string.tabBar_home), + icon = Icons.Outlined.Home, + selectedIcon = Icons.Filled.Home, + onClick = { + invokeToast(resources.getString(R.string.tabBar_home), context) + Toast.makeText(context, "Home Tab Clicked", Toast.LENGTH_SHORT).show() + selectedIndex = 0 + }, + badge = { Badge() } + ), + TabData( + title = resources.getString(R.string.tabBar_mail), + icon = Icons.Outlined.Email, + selectedIcon = Icons.Filled.Email, + onClick = { + invokeToast(resources.getString(R.string.tabBar_mail), context) + selectedIndex = 1 + }, + badge = { Badge("123+", badgeType = BadgeType.Character) } + ), + TabData( + title = resources.getString(R.string.tabBar_settings), + icon = Icons.Outlined.Settings, + selectedIcon = Icons.Filled.Settings, + onClick = { + invokeToast(resources.getString(R.string.tabBar_settings), context) + selectedIndex = 2 + } + ), + TabData( + title = resources.getString(R.string.tabBar_notification), + icon = Icons.Outlined.Notifications, + selectedIcon = Icons.Filled.Notifications, + onClick = { + invokeToast(resources.getString(R.string.tabBar_notification), context) + selectedIndex = 3 + }, + badge = { Badge("10", badgeType = BadgeType.Character) } + ), + TabData( + title = resources.getString(R.string.tabBar_more), + icon = Icons.Outlined.List, + selectedIcon = Icons.Filled.List, + onClick = { + invokeToast(resources.getString(R.string.tabBar_more), context) + selectedIndex = 4 + }, + badge = { Badge() } + ) + ) + FluentTheme { + val content = listOf(0, 1, 2) + var selectedOption by rememberSaveable { mutableStateOf(content[0]) } + var tabTextAlignment by rememberSaveable { mutableStateOf(TabTextAlignment.VERTICAL) } + var tabItemsCount by rememberSaveable { mutableStateOf(5) } + + Column { + ListItem.Header(title = resources.getString(R.string.tabBar_text_alignment)) + Column( + modifier = Modifier.padding(16.dp), + verticalArrangement = Arrangement.spacedBy(10.dp), + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(16.dp), + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth() + ) { + Text( + text = resources.getString(R.string.tabBar_vertical), + modifier = Modifier.weight(1F), + color = AppThemeController.aliasTokens.value!!.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.Foreground1].value( + themeMode = ThemeMode.Auto + ) + ) + RadioButton( + selected = (selectedOption == content[0]), + onClick = { + selectedOption = content[0] + tabTextAlignment = TabTextAlignment.VERTICAL + } + ) + } + Row( + horizontalArrangement = Arrangement.spacedBy(16.dp), + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth() + ) { + Text( + text = resources.getString(R.string.tabBar_horizontal), + modifier = Modifier.weight(1F), + color = AppThemeController.aliasTokens.value!!.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.Foreground1].value( + themeMode = ThemeMode.Auto + ) + ) + RadioButton( + selected = (selectedOption == content[1]), + onClick = { + selectedOption = content[1] + tabTextAlignment = TabTextAlignment.HORIZONTAL + } + ) + } + Row( + horizontalArrangement = Arrangement.spacedBy(16.dp), + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth() + ) { + Text( + text = resources.getString(R.string.tabBar_no_text), + modifier = Modifier.weight(1F), + color = AppThemeController.aliasTokens.value!!.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.Foreground1].value( + themeMode = ThemeMode.Auto + ) + ) + RadioButton( + selected = (selectedOption == content[2]), + onClick = { + selectedOption = content[2] + tabTextAlignment = TabTextAlignment.NO_TEXT + } + ) + } + + } + ListItem.Header(title = resources.getString(R.string.tabBar_tab_items), + trailingAccessoryView = + { + Row( + horizontalArrangement = Arrangement.spacedBy(16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Button( + style = ButtonStyle.Button, + size = ButtonSize.Medium, + text = "+", + enabled = tabItemsCount < 5, + onClick = { tabItemsCount++ }) + + Button( + style = ButtonStyle.Button, + size = ButtonSize.Medium, + text = "-", + enabled = tabItemsCount > 1, + onClick = { tabItemsCount-- }) + + } + } + ) + } + Column(verticalArrangement = Arrangement.Bottom) { + TabBar( + tabDataList = tabDataList.take(tabItemsCount), + selectedIndex = selectedIndex, + tabTextAlignment = tabTextAlignment + ) + } + } + } + } + + private fun invokeToast(string: String, context: Context) { + Toast.makeText(context, "$string Tab Clicked", Toast.LENGTH_SHORT).show() + } +} \ No newline at end of file diff --git a/FluentUI.Demo/src/main/res/values/strings.xml b/FluentUI.Demo/src/main/res/values/strings.xml index 0c5aa880..b0fb94e0 100644 --- a/FluentUI.Demo/src/main/res/values/strings.xml +++ b/FluentUI.Demo/src/main/res/values/strings.xml @@ -101,6 +101,13 @@ AvatarView at index %d clicked + + + Notification Badge + Dot + List + Character + Photos @@ -643,6 +650,19 @@ Danger style + + + Home + Mail + Settings + Notification + More + Text Alignment + Vertical + Horizontal + No Text + Tab Items + Title diff --git a/FluentUI/build.gradle b/FluentUI/build.gradle index c2e65d77..ca243c5c 100644 --- a/FluentUI/build.gradle +++ b/FluentUI/build.gradle @@ -37,6 +37,7 @@ dependencies { api project(':fluentui_icons') api project(':fluentui_listitem') api project(':fluentui_menus') + api project(':fluentui_notification') api project(':fluentui_others') api project(':fluentui_peoplepicker') api project(':fluentui_persona') diff --git a/config.gradle b/config.gradle index b12903c0..4efd7fe3 100644 --- a/config.gradle +++ b/config.gradle @@ -26,6 +26,7 @@ project.ext.fluentui_peoplepicker_versionid = '0.0.25' project.ext.fluentui_persona_versionid = '0.1.4' project.ext.fluentui_progress_versionid = '0.1.1' project.ext.fluentui_icons_versionid = '0.1.2' +project.ext.fluentui_notification_versionid = '0.1.0' project.ext.FluentUI_versionid = '0.1.15' project.ext.fluentui_calendar_version_code = 25 project.ext.fluentui_controls_version_code = 8 @@ -42,6 +43,7 @@ project.ext.fluentui_peoplepicker_version_code = 25 project.ext.fluentui_persona_version_code = 30 project.ext.fluentui_progress_version_code = 26 project.ext.fluentui_icons_version_code = 3 +project.ext.fluentui_notification_version_code = 1 project.ext.FluentUI_version_code = 50 project.ext.license_type = 'MIT License' project.ext.license_url = 'https://github.com/microsoft/fluentui-android/blob/master/LICENSE' diff --git a/fluentui_core/src/main/java/com/microsoft/fluentui/theme/token/ControlTokens.kt b/fluentui_core/src/main/java/com/microsoft/fluentui/theme/token/ControlTokens.kt index db8edde6..df466517 100644 --- a/fluentui_core/src/main/java/com/microsoft/fluentui/theme/token/ControlTokens.kt +++ b/fluentui_core/src/main/java/com/microsoft/fluentui/theme/token/ControlTokens.kt @@ -19,6 +19,7 @@ class ControlTokens { Avatar, AvatarCarousel, AvatarGroup, + Badge, BottomSheet, Button, CheckBox, @@ -38,6 +39,8 @@ class ControlTokens { SearchBarPersonaChip, SearchBar, Shimmer, + TabBar, + TabBarTabItem, TabItem, ToggleSwitch } @@ -49,6 +52,7 @@ class ControlTokens { ControlType.Avatar -> AvatarTokens() ControlType.AvatarCarousel -> AvatarCarouselTokens() ControlType.AvatarGroup -> AvatarGroupTokens() + ControlType.Badge -> BadgeTokens() ControlType.BottomSheet -> BottomSheetTokens() ControlType.Button -> ButtonTokens() ControlType.CheckBox -> CheckBoxTokens() @@ -68,6 +72,8 @@ class ControlTokens { ControlType.SearchBarPersonaChip -> SearchBarPersonaChipTokens() ControlType.SearchBar -> SearchBarTokens() ControlType.Shimmer -> ShimmerTokens() + ControlType.TabBar -> TabBarTokens() + ControlType.TabBarTabItem -> TabBarTabItemsTokens() ControlType.TabItem -> TabItemTokens() ControlType.ToggleSwitch -> ToggleSwitchTokens() } diff --git a/fluentui_core/src/main/java/com/microsoft/fluentui/theme/token/controlTokens/BadgeTokens.kt b/fluentui_core/src/main/java/com/microsoft/fluentui/theme/token/controlTokens/BadgeTokens.kt new file mode 100644 index 00000000..8251ec4b --- /dev/null +++ b/fluentui_core/src/main/java/com/microsoft/fluentui/theme/token/controlTokens/BadgeTokens.kt @@ -0,0 +1,65 @@ +package com.microsoft.fluentui.theme.token.controlTokens + +import android.os.Parcelable +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import com.microsoft.fluentui.theme.FluentTheme +import com.microsoft.fluentui.theme.token.* +import kotlinx.parcelize.Parcelize + +enum class BadgeType { + Character, + List +} + +class BadgeInfo(val type: BadgeType) : ControlInfo + +@Parcelize +open class BadgeTokens : ControlToken, Parcelable { + + @Composable + open fun backgroundColor(badgeInfo: BadgeInfo): Color { + return FluentTheme.aliasTokens.errorAndStatusColor[AliasTokens.ErrorAndStatusColorTokens.DangerBackground2].value() + } + + @Composable + open fun textColor(badgeInfo: BadgeInfo): Color { + return FluentTheme.aliasTokens.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.ForegroundLightStatic].value() + } + + @Composable + open fun typography(badgeInfo: BadgeInfo): FontInfo { + return when (badgeInfo.type) { + BadgeType.Character -> FluentTheme.aliasTokens.typography[AliasTokens.TypographyTokens.Caption2] + BadgeType.List -> FluentTheme.aliasTokens.typography[AliasTokens.TypographyTokens.Caption1Strong] + } + } + + @Composable + open fun padding(badgeInfo: BadgeInfo): PaddingValues { + return when (badgeInfo.type) { + BadgeType.Character -> PaddingValues( + horizontal = GlobalTokens.size(GlobalTokens.SizeTokens.Size60) + borderStroke( + badgeInfo + ).width + ) + BadgeType.List -> PaddingValues( + start = GlobalTokens.size(GlobalTokens.SizeTokens.Size80) + borderStroke(badgeInfo).width, + end = GlobalTokens.size(GlobalTokens.SizeTokens.Size80) + borderStroke(badgeInfo).width, + top = 3.dp + borderStroke(badgeInfo).width, + bottom = 3.dp + borderStroke(badgeInfo).width + ) + } + } + + @Composable + open fun borderStroke(badgeInfo: BadgeInfo): BorderStroke { + return BorderStroke( + GlobalTokens.strokeWidth(GlobalTokens.StrokeWidthTokens.StrokeWidth20), + FluentTheme.aliasTokens.neutralStrokeColor[AliasTokens.NeutralStrokeColorTokens.StrokeFocus1].value() + ) + } +} \ No newline at end of file diff --git a/fluentui_core/src/main/java/com/microsoft/fluentui/theme/token/controlTokens/TabBarTabItemTokens.kt b/fluentui_core/src/main/java/com/microsoft/fluentui/theme/token/controlTokens/TabBarTabItemTokens.kt new file mode 100644 index 00000000..0eba6d7d --- /dev/null +++ b/fluentui_core/src/main/java/com/microsoft/fluentui/theme/token/controlTokens/TabBarTabItemTokens.kt @@ -0,0 +1,33 @@ +package com.microsoft.fluentui.theme.token.controlTokens + +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.StateColor +import kotlinx.parcelize.Parcelize + +@Parcelize +open class TabBarTabItemsTokens : TabItemTokens() { + + @Composable + override fun backgroundColor(tabItemInfo: TabItemInfo): Color { + return FluentTheme.aliasTokens.neutralBackgroundColor[AliasTokens.NeutralBackgroundColorTokens.Background1].value() + } + + @Composable + override fun iconColor(tabItemInfo: TabItemInfo): StateColor { + return StateColor( + rest = FluentTheme.aliasTokens.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.Foreground2].value(), + selected = FluentTheme.aliasTokens.brandForegroundColor[AliasTokens.BrandForegroundColorTokens.BrandForeground1].value() + ) + } + + @Composable + override fun textColor(tabItemInfo: TabItemInfo): StateColor { + return StateColor( + rest = FluentTheme.aliasTokens.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.Foreground2].value(), + selected = FluentTheme.aliasTokens.brandForegroundColor[AliasTokens.BrandForegroundColorTokens.BrandForeground1].value() + ) + } +} \ No newline at end of file diff --git a/fluentui_core/src/main/java/com/microsoft/fluentui/theme/token/controlTokens/TabBarTokens.kt b/fluentui_core/src/main/java/com/microsoft/fluentui/theme/token/controlTokens/TabBarTokens.kt new file mode 100644 index 00000000..f65c36d4 --- /dev/null +++ b/fluentui_core/src/main/java/com/microsoft/fluentui/theme/token/controlTokens/TabBarTokens.kt @@ -0,0 +1,28 @@ +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 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 TabBarInfo : ControlInfo + +@Parcelize +open class TabBarTokens : ControlToken, Parcelable { + + @Composable + open fun topBorderColor(tabBarInfo: TabBarInfo): Color { + return FluentTheme.aliasTokens.neutralStrokeColor[AliasTokens.NeutralStrokeColorTokens.Stroke2].value() + } + + @Composable + open fun topBorderWidth(tabBarInfo: TabBarInfo): Dp { + return GlobalTokens.strokeWidth(GlobalTokens.StrokeWidthTokens.StrokeWidth05) + } +} \ No newline at end of file diff --git a/fluentui_core/src/main/java/com/microsoft/fluentui/theme/token/controlTokens/TabItemTokens.kt b/fluentui_core/src/main/java/com/microsoft/fluentui/theme/token/controlTokens/TabItemTokens.kt index a3c5a6bc..3000775b 100644 --- a/fluentui_core/src/main/java/com/microsoft/fluentui/theme/token/controlTokens/TabItemTokens.kt +++ b/fluentui_core/src/main/java/com/microsoft/fluentui/theme/token/controlTokens/TabItemTokens.kt @@ -30,7 +30,7 @@ open class TabItemTokens : ControlToken, Parcelable { } @Composable - open fun background(tabItemInfo: TabItemInfo): Color { + open fun backgroundColor(tabItemInfo: TabItemInfo): Color { return FluentTheme.aliasTokens.neutralBackgroundColor[AliasTokens.NeutralBackgroundColorTokens.Background1].value( FluentTheme.themeMode ) diff --git a/fluentui_listitem/build.gradle b/fluentui_listitem/build.gradle index 26a24761..9a7f9a6f 100644 --- a/fluentui_listitem/build.gradle +++ b/fluentui_listitem/build.gradle @@ -59,7 +59,7 @@ dependencies { implementation("androidx.compose.material:material:$composeVersion") implementation("androidx.compose.runtime:runtime:$composeVersion") implementation("androidx.compose.ui:ui:$composeVersion") - + implementation "androidx.constraintlayout:constraintlayout-compose:$constraintLayoutComposeVersion" } task sourceJar(type: Jar) { diff --git a/fluentui_listitem/src/main/java/com/microsoft/fluentui/tokenized/contentBuilder/ListContentBuilder.kt b/fluentui_listitem/src/main/java/com/microsoft/fluentui/tokenized/contentBuilder/ListContentBuilder.kt index b975bdc2..4ce53fd2 100644 --- a/fluentui_listitem/src/main/java/com/microsoft/fluentui/tokenized/contentBuilder/ListContentBuilder.kt +++ b/fluentui_listitem/src/main/java/com/microsoft/fluentui/tokenized/contentBuilder/ListContentBuilder.kt @@ -31,7 +31,7 @@ data class ItemData( var enabled: Boolean = true, var onClick: () -> Unit, var accessory: @Composable (() -> Unit)? = null, - var icon: ImageVector? = null + var icon: ImageVector ) // marker interface @@ -174,7 +174,7 @@ class ListContentBuilder { Layout( modifier = Modifier .background( - token.background( + token.backgroundColor( TabItemInfo( TabTextAlignment.VERTICAL, FluentStyle.Brand @@ -250,7 +250,7 @@ class ListContentBuilder { LazyRow( state = rowLazyListState, modifier = Modifier .background( - token.background( + token.backgroundColor( TabItemInfo( TabTextAlignment.VERTICAL, FluentStyle.Brand diff --git a/fluentui_listitem/src/main/java/com/microsoft/fluentui/tokenized/listitem/ListItem.kt b/fluentui_listitem/src/main/java/com/microsoft/fluentui/tokenized/listitem/ListItem.kt index 9bf63334..a6074e08 100644 --- a/fluentui_listitem/src/main/java/com/microsoft/fluentui/tokenized/listitem/ListItem.kt +++ b/fluentui_listitem/src/main/java/com/microsoft/fluentui/tokenized/listitem/ListItem.kt @@ -786,7 +786,7 @@ object ListItem { .focusable(false) ) { Row( - modifier + Modifier .fillMaxWidth() .heightIn(min = cellHeight) .background(backgroundColor) @@ -811,7 +811,7 @@ object ListItem { if (accessoryTextTitle != null) { Text(text = accessoryTextTitle, - modifier + Modifier .padding(end = horizontalPadding, bottom = verticalPadding) .clickable( role = Role.Button, diff --git a/fluentui_listitem/src/main/java/com/microsoft/fluentui/tokenized/tabItem/TabItem.kt b/fluentui_listitem/src/main/java/com/microsoft/fluentui/tokenized/tabItem/TabItem.kt index 6fe09459..88fee9fa 100644 --- a/fluentui_listitem/src/main/java/com/microsoft/fluentui/tokenized/tabItem/TabItem.kt +++ b/fluentui_listitem/src/main/java/com/microsoft/fluentui/tokenized/tabItem/TabItem.kt @@ -14,9 +14,15 @@ 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.layout.Layout +import androidx.compose.ui.layout.layoutId import androidx.compose.ui.semantics.Role import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import androidx.constraintlayout.compose.ChainStyle +import androidx.constraintlayout.compose.ConstraintLayout +import androidx.constraintlayout.compose.Dimension import com.microsoft.fluentui.theme.FluentTheme import com.microsoft.fluentui.theme.token.ControlTokens import com.microsoft.fluentui.theme.token.FluentStyle @@ -39,13 +45,14 @@ fun getTabItemInfo(): TabItemInfo { } @Composable -internal fun TabItem( +fun TabItem( title: String, modifier: Modifier = Modifier, - icon: ImageVector? = null, + icon: ImageVector, style: FluentStyle = FluentStyle.Neutral, textAlignment: TabTextAlignment = TabTextAlignment.VERTICAL, enabled: Boolean = true, + selected: Boolean = false, fixedWidth: Boolean = false, onClick: () -> Unit, accessory: (@Composable () -> Unit)?, @@ -60,18 +67,20 @@ internal fun TabItem( LocalTabItemTokens provides token, LocalTabItemInfo provides TabItemInfo(textAlignment, style) ) { - val textColor = getTabItemTokens().textColor(tabItemInfo = getTabItemInfo()).getColorByState( - enabled = enabled, - selected = false, - interactionSource = interactionSource - ) - val iconColor = getTabItemTokens().iconColor(tabItemInfo = getTabItemInfo()).getColorByState( - enabled = enabled, - selected = false, - interactionSource = interactionSource - ) + 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 backgroundColor = getTabItemTokens().background(tabItemInfo = getTabItemInfo()) + val backgroundColor = getTabItemTokens().backgroundColor(tabItemInfo = getTabItemInfo()) val rippleColor = getTabItemTokens().rippleColor(tabItemInfo = getTabItemInfo()) val clickableModifier = Modifier @@ -90,41 +99,115 @@ internal fun TabItem( 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() - } - } + 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) { - Row( - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically, + ConstraintLayout( 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)) + ) + { + 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.Justify + textAlign = TextAlign.Center, + overflow = TextOverflow.Ellipsis, + maxLines = 1 + ) + + 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, @@ -134,7 +217,7 @@ internal fun TabItem( .padding(top = 8.dp, start = 8.dp, bottom = 4.dp, end = 8.dp) .then(widthModifier) ) { - iconContent() + badgeWithIcon() if (textAlignment == TabTextAlignment.VERTICAL) { Spacer(modifier = Modifier.height(2.dp)) Text( diff --git a/fluentui_notification/.gitignore b/fluentui_notification/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/fluentui_notification/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/fluentui_notification/build.gradle b/fluentui_notification/build.gradle new file mode 100644 index 00000000..d6c8e285 --- /dev/null +++ b/fluentui_notification/build.gradle @@ -0,0 +1,71 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +apply from: '../config.gradle' +apply from: '../publish.gradle' + +android { + compileSdkVersion constants.compileSdkVersion + defaultConfig { + minSdkVersion constants.minSdkVersion + targetSdkVersion constants.targetSdkVersion + versionCode project.ext.fluentui_notification_version_code + versionName project.ext.fluentui_notification_versionid + testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' + } + lintOptions { + abortOnError false + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + testOptions { + unitTests { + includeAndroidResources = true + } + } + productFlavors { + } + kotlinOptions { + jvmTarget = '1.8' + useIR = true + } + buildFeatures { + compose true + } + composeOptions { + kotlinCompilerExtensionVersion composeVersion + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + implementation fileTree(include: ['*.jar'], dir: 'libs') + implementation project(':fluentui_core') + + implementation 'androidx.core:core-ktx:1.7.0' + + implementation("androidx.compose.foundation:foundation:$composeVersion") + implementation("androidx.compose.material:material:$composeVersion") + implementation("androidx.compose.runtime:runtime:$composeVersion") + implementation("androidx.compose.ui:ui:$composeVersion") + + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' +} + +task sourceJar(type: Jar) { + from android.sourceSets.main.java.srcDirs + classifier "sources" +} + +project.afterEvaluate { + project.ext.publishingFunc('fluentui_notification') +} \ No newline at end of file diff --git a/fluentui_notification/consumer-rules.pro b/fluentui_notification/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/fluentui_notification/proguard-rules.pro b/fluentui_notification/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/fluentui_notification/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/fluentui_notification/src/main/AndroidManifest.xml b/fluentui_notification/src/main/AndroidManifest.xml new file mode 100644 index 00000000..11d49586 --- /dev/null +++ b/fluentui_notification/src/main/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/fluentui_notification/src/main/java/com/microsoft/fluentui/tokenized/notification/Badge.kt b/fluentui_notification/src/main/java/com/microsoft/fluentui/tokenized/notification/Badge.kt new file mode 100644 index 00000000..25b9814e --- /dev/null +++ b/fluentui_notification/src/main/java/com/microsoft/fluentui/tokenized/notification/Badge.kt @@ -0,0 +1,113 @@ +package com.microsoft.fluentui.tokenized.notification + +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.background +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 +import androidx.compose.ui.graphics.drawscope.Fill +import androidx.compose.ui.text.ExperimentalTextApi +import androidx.compose.ui.text.PlatformTextStyle +import androidx.compose.ui.text.TextStyle +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.BadgeInfo +import com.microsoft.fluentui.theme.token.controlTokens.BadgeTokens +import com.microsoft.fluentui.theme.token.controlTokens.BadgeType +import com.microsoft.fluentui.util.dpToPx + +val LocalBadgeInfo = compositionLocalOf { BadgeInfo(BadgeType.Character) } +val LocalBadgeToken = compositionLocalOf { BadgeTokens() } + +@Composable +fun getBadgeInfo(): BadgeInfo { + return LocalBadgeInfo.current +} + +@Composable +fun getBadgeTokens(): BadgeTokens { + return LocalBadgeToken.current +} + +@OptIn(ExperimentalTextApi::class) +@Composable +fun Badge( + text: String? = null, + modifier: Modifier = Modifier, + 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) + ) { + 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), + style = LocalTextStyle.current.merge( + TextStyle( + color = textColor, + lineHeight = typography.fontSize.lineHeight, + fontSize = typography.fontSize.size, + fontWeight = typography.weight, + platformStyle = PlatformTextStyle( + includeFontPadding = false + ) + ) + ) + ) + } + } + } +} \ No newline at end of file diff --git a/fluentui_notification/src/main/res/values/strings.xml b/fluentui_notification/src/main/res/values/strings.xml new file mode 100644 index 00000000..a6b3daec --- /dev/null +++ b/fluentui_notification/src/main/res/values/strings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/fluentui_tablayout/build.gradle b/fluentui_tablayout/build.gradle index f2c803ff..a10ed53e 100644 --- a/fluentui_tablayout/build.gradle +++ b/fluentui_tablayout/build.gradle @@ -45,6 +45,7 @@ dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation project(':fluentui_core') implementation project(':fluentui_icons') + implementation project(':fluentui_listitem') implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "androidx.appcompat:appcompat:$appCompatVersion" implementation "com.google.android.material:material:$materialVersion" diff --git a/fluentui_tablayout/src/main/java/com/microsoft/fluentui/tokenized/navigation/TabBar.kt b/fluentui_tablayout/src/main/java/com/microsoft/fluentui/tokenized/navigation/TabBar.kt new file mode 100644 index 00000000..8a87561a --- /dev/null +++ b/fluentui_tablayout/src/main/java/com/microsoft/fluentui/tokenized/navigation/TabBar.kt @@ -0,0 +1,85 @@ +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 +import com.microsoft.fluentui.theme.FluentTheme +import com.microsoft.fluentui.theme.token.ControlTokens +import com.microsoft.fluentui.theme.token.controlTokens.* +import com.microsoft.fluentui.tokenized.tabItem.TabItem + + +data class TabData( + var title: String, + var icon: ImageVector, + var selectedIcon: ImageVector = icon, + var selected: Boolean = false, + var onClick: () -> Unit, + var badge: @Composable (() -> Unit)? = null +) + +val LocalTabBarInfo = compositionLocalOf { TabBarInfo() } +val LocalTabBarToken = compositionLocalOf { TabBarTokens() } + +@Composable +fun getTabBarInfo(): TabBarInfo { + return LocalTabBarInfo.current +} + +@Composable +fun getTabBarToken(): TabBarTokens { + return LocalTabBarToken.current +} + +@Composable +fun TabBar( + tabDataList: List, + modifier: Modifier = Modifier, + selectedIndex: Int = 0, + tabTextAlignment: TabTextAlignment = TabTextAlignment.VERTICAL, + tabItemTokens: TabBarTabItemsTokens? = null, + tabBarTokens: TabBarTokens? = null +) { + 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.TabBarTabItem] as TabItemTokens + ) + } + } + } + } +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index a7068015..7f71e455 100644 --- a/settings.gradle +++ b/settings.gradle @@ -15,3 +15,4 @@ include ':FluentUI' include ':fluentui_ccb' include ':fluentui_controls' include ':fluentui_icons' +include ':fluentui_notification'