* init personachip

* searchbox persona and ui fixes

* indentation fix

* code review modifications

* code review changes 2

* code review 3

* pading for text alignmnet

* overriding personatokens for searchbar tokens

* modifying chip info data class

* review changes infinite :)

* revert dogfood

* persona chip activity modifications

Co-authored-by: PraveenKumar Yeruva <pyeruva@microsoft.com>
This commit is contained in:
PraveenKumar yeruva 2022-12-22 15:28:21 +05:30 коммит произвёл GitHub
Родитель 8b35393664
Коммит af9a9c2d6e
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
14 изменённых файлов: 1018 добавлений и 8 удалений

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

@ -55,6 +55,7 @@
<activity android:name="com.microsoft.fluentuidemo.demos.PersistentBottomSheetActivity" />
<activity android:name="com.microsoft.fluentuidemo.demos.V2BottomSheetActivity" />
<activity android:name="com.microsoft.fluentuidemo.demos.PersonaChipViewActivity" />
<activity android:name="com.microsoft.fluentuidemo.demos.V2PersonaChipActivity" />
<activity android:name="com.microsoft.fluentuidemo.demos.PersonaListViewActivity" />
<activity android:name="com.microsoft.fluentuidemo.demos.V2PersonaListActivity" />
<activity android:name="com.microsoft.fluentuidemo.demos.PersonaViewActivity" />

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

@ -33,6 +33,7 @@ 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"
@ -72,6 +73,7 @@ val DEMOS = arrayListOf(
Demo(PERSISTENT_BOTTOM_SHEET, PersistentBottomSheetActivity::class),
Demo(V2BOTTOM_SHEET, V2BottomSheetActivity::class),
Demo(PERSONA_CHIP_VIEW, PersonaChipViewActivity::class),
Demo(V2PERSONA_CHIP, V2PersonaChipActivity::class),
Demo(PERSONA_LIST_VIEW, PersonaListViewActivity::class),
Demo(V2PERSONA_LIST, V2PersonaListActivity::class),
Demo(PERSONA_VIEW, PersonaViewActivity::class),

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

@ -0,0 +1,338 @@
package com.microsoft.fluentuidemo.demos
import android.os.Bundle
import android.widget.Toast
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.listSaver
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.microsoft.fluentui.theme.FluentTheme
import com.microsoft.fluentui.theme.token.AliasTokens
import com.microsoft.fluentui.theme.token.FluentStyle
import com.microsoft.fluentui.theme.token.controlTokens.AvatarStatus.Available
import com.microsoft.fluentui.theme.token.controlTokens.PersonaChipSize
import com.microsoft.fluentui.theme.token.controlTokens.PersonaChipStyle.*
import com.microsoft.fluentui.tokenized.controls.ToggleSwitch
import com.microsoft.fluentui.tokenized.persona.Person
import com.microsoft.fluentui.tokenized.persona.PersonaChip
import com.microsoft.fluentui.tokenized.persona.SearchBarPersonaChip
import com.microsoft.fluentuidemo.DemoActivity
import com.microsoft.fluentuidemo.R
import com.microsoft.fluentuidemo.R.drawable
class V2PersonaChipActivity : 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 compose_here = findViewById<ComposeView>(R.id.compose_here)
compose_here.setContent {
FluentTheme {
createPersonaChipActivityUI()
}
}
}
private fun createPersonWithName(): Person {
return Person(
"Allan",
"Munger",
image = drawable.avatar_allan_munger,
email = "allan.munger@microsoft.com",
isActive = true,
status = Available,
isOOO = false
)
}
private fun createPersonWithEmail(): Person {
return Person(
"",
"",
image = drawable.avatar_allan_munger,
email = "allan.munger@microsoft.com",
isActive = true,
status = Available,
isOOO = false
)
}
private fun createPersonWithNothing(): Person {
return Person(
"",
"",
image = drawable.avatar_allan_munger,
email = "",
isActive = true,
status = Available,
isOOO = false
)
}
@Composable
fun createPersonaChipActivityUI() {
val textColor =
FluentTheme.aliasTokens.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.Foreground1].value(
themeMode = FluentTheme.themeMode
)
val brandTextColor =
FluentTheme.aliasTokens.brandForegroundColor[AliasTokens.BrandForegroundColorTokens.BrandForeground1].value(
themeMode = FluentTheme.themeMode
)
var showCloseButton by remember { mutableStateOf(false) }
var selectedList = rememberSaveable(
saver = listSaver(
save = { stateList ->
if (stateList.isNotEmpty()) {
val first = stateList.first()
if (!canBeSaved(first)) {
throw IllegalStateException("${first::class} cannot be saved. By default only types which can be stored in the Bundle class can be saved.")
}
}
stateList.toList()
},
restore = { it.toMutableStateList() }
)) {
mutableStateListOf(
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
)
}
//TODO: Clean Activity using for loops
Box(Modifier.padding(16.dp)) {
LazyColumn(verticalArrangement = Arrangement.spacedBy(16.dp)) {
item {
Row(
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Enable/Disable close button on selected state",
color = brandTextColor,
fontSize = 10.sp
)
ToggleSwitch(
onValueChange = { showCloseButton = !showCloseButton },
checkedState = showCloseButton
)
}
}
item {
Text(text = "Basic Persona chip", color = brandTextColor, fontSize = 20.sp)
}
item {
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
Text(text = "Person Chip Neutral", color = textColor)
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
PersonaChip(
person = createPersonWithName(),
size = PersonaChipSize.Small,
style = Neutral,
selected = selectedList[0],
onClick = { selectedList[0] = !selectedList[0] })
PersonaChip(
person = createPersonWithName(),
style = Neutral,
selected = selectedList[1],
onClick = { selectedList[1] = !selectedList[1] },
onCloseClick = if (showCloseButton) {
{ onClickToast() }
} else null
)
}
Text(text = "Person Chip Brand", color = textColor)
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
PersonaChip(
person = createPersonWithName(),
size = PersonaChipSize.Small,
style = Brand,
selected = selectedList[2],
onClick = { selectedList[2] = !selectedList[2] })
PersonaChip(
person = createPersonWithName(),
style = Brand,
selected = selectedList[3],
onClick = { selectedList[3] = !selectedList[3] },
onCloseClick = if (showCloseButton) {
{ onClickToast() }
} else null
)
}
Text(text = "Person Chip Danger", color = textColor)
LazyRow(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
item {
PersonaChip(
person = createPersonWithEmail(),
size = PersonaChipSize.Small,
style = Danger,
selected = selectedList[4],
onClick = { selectedList[4] = !selectedList[4] })
}
item {
PersonaChip(
person = createPersonWithEmail(),
style = Danger,
selected = selectedList[5],
onClick = { selectedList[5] = !selectedList[5] },
onCloseClick = if (showCloseButton) {
{ onClickToast() }
} else null
)
}
}
Text(text = "Person Chip Severe Warning", color = textColor)
LazyRow(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
item {
PersonaChip(
person = createPersonWithEmail(),
size = PersonaChipSize.Small,
style = SevereWarning,
selected = selectedList[6],
onClick = { selectedList[6] = !selectedList[6] })
}
item {
PersonaChip(
person = createPersonWithEmail(),
style = SevereWarning,
selected = selectedList[7],
onClick = { selectedList[7] = !selectedList[7] },
onCloseClick = if (showCloseButton) {
{ onClickToast() }
} else null
)
}
}
Text(text = "Person Chip Warning", color = textColor)
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
PersonaChip(
person = createPersonWithNothing(),
size = PersonaChipSize.Small,
style = Warning,
selected = selectedList[8],
onClick = { selectedList[8] = !selectedList[8] })
PersonaChip(
person = createPersonWithNothing(),
style = Warning,
selected = selectedList[9],
onClick = { selectedList[9] = !selectedList[9] },
onCloseClick = if (showCloseButton) {
{ onClickToast() }
} else null
)
}
Text(text = "Person Chip Success", color = textColor)
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
PersonaChip(
person = createPersonWithName(),
size = PersonaChipSize.Small,
style = Success,
selected = selectedList[10],
onClick = { selectedList[10] = !selectedList[10] })
PersonaChip(
person = createPersonWithName(),
style = Success,
selected = selectedList[11],
onClick = { selectedList[11] = !selectedList[11] },
onCloseClick = if (showCloseButton) {
{ onClickToast() }
} else null
)
}
Text(text = "Person Chip Disabled", color = textColor)
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
PersonaChip(
person = createPersonWithName(),
size = PersonaChipSize.Small,
style = Neutral,
enabled = false,
selected = selectedList[12],
onClick = { selectedList[12] = !selectedList[12] },
onCloseClick = if (showCloseButton) {
{ onClickToast() }
} else null
)
PersonaChip(
person = createPersonWithName(),
style = Neutral,
enabled = false,
selected = selectedList[13],
onClick = { selectedList[13] = !selectedList[13] },
onCloseClick = if (showCloseButton) {
{ onClickToast() }
} else null
)
}
}
}
item {
Text(
text = "SearchBox Basic Persona chip",
color = brandTextColor,
fontSize = 20.sp
)
}
item {
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
Text(text = "Persona chip Neutral", color = textColor)
SearchBarPersonaChip(
person = createPersonWithName(),
size = PersonaChipSize.Small,
selected = selectedList[14],
onClick = { selectedList[14] = !selectedList[14] },
onCloseClick = if (showCloseButton) {
{ onClickToast() }
} else null
)
Text(text = "Persona chip Brand", color = textColor)
SearchBarPersonaChip(
person = createPersonWithName(),
style = FluentStyle.Brand,
selected = selectedList[15],
onClick = { selectedList[15] = !selectedList[15] },
onCloseClick = if (showCloseButton) {
{ onClickToast() }
} else null
)
}
}
}
}
}
private fun onClickToast() {
Toast.makeText(
this,
"Clicked on close icon",
Toast.LENGTH_SHORT
).show()
}
}

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

@ -16,6 +16,7 @@ fun getString(string: Strings): String {
Strings.NotSelected -> resources.getString(R.string.fluentui_not_selected)
Strings.Disabled -> resources.getString(R.string.fluentui_disabled)
Strings.Enabled -> resources.getString(R.string.fluentui_enabled)
Strings.Close -> resources.getString(R.string.fluentui_close)
else -> ""
}
}

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

@ -11,5 +11,6 @@ value class Strings private constructor(@Suppress("unused") private val value: I
val NotSelected = Strings(2)
val Disabled = Strings(3)
val Enabled = Strings(4)
val Close = Strings(5)
}
}

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

@ -28,12 +28,14 @@ class ControlTokens {
FloatingActionButton,
LinearProgressIndicator,
ListItem,
PersonaChip,
PillButton,
PillBar,
RadioButton,
PillSwitch,
PillTabs,
TabItem,
SearchBarPersonaChip,
Shimmer,
ToggleSwitch
}
@ -54,6 +56,8 @@ class ControlTokens {
ControlType.FloatingActionButton -> FABTokens()
ControlType.LinearProgressIndicator -> LinearProgressIndicatorTokens()
ControlType.ListItem -> ListItemTokens()
ControlType.PersonaChip -> PersonaChipTokens()
ControlType.SearchBarPersonaChip -> SearchBarPersonaChipTokens()
ControlType.PillButton -> PillButtonTokens()
ControlType.PillBar -> PillBarTokens()
ControlType.RadioButton -> RadioButtonTokens()

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

@ -286,6 +286,52 @@ object GlobalTokens : Parcelable {
}
}
enum class SizeTokens {
SizeNone,
Size20,
Size40,
Size60,
Size80,
Size100,
Size120,
Size140,
Size160,
Size180,
Size200,
Size240,
Size280,
Size320,
Size360,
Size400,
Size480,
Size520,
Size560
}
fun size(token: SizeTokens): Dp {
return when(token) {
SizeTokens.SizeNone -> 0.dp
SizeTokens.Size20 -> 2.dp
SizeTokens.Size40 -> 4.dp
SizeTokens.Size60 -> 6.dp
SizeTokens.Size80 -> 8.dp
SizeTokens.Size100 -> 10.dp
SizeTokens.Size120 -> 12.dp
SizeTokens.Size140 -> 14.dp
SizeTokens.Size160 -> 16.dp
SizeTokens.Size180 -> 18.dp
SizeTokens.Size200 -> 20.dp
SizeTokens.Size240 -> 24.dp
SizeTokens.Size280 -> 28.dp
SizeTokens.Size320 -> 32.dp
SizeTokens.Size360 -> 36.dp
SizeTokens.Size400 -> 40.dp
SizeTokens.Size480 -> 48.dp
SizeTokens.Size520 -> 52.dp
SizeTokens.Size560 -> 56.dp
}
}
enum class ShadowTokens {
Shadow02,
Shadow04,

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

@ -0,0 +1,211 @@
package com.microsoft.fluentui.theme.token.controlTokens
import android.os.Parcelable
import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.Dp
import com.microsoft.fluentui.theme.FluentTheme
import com.microsoft.fluentui.theme.token.*
import com.microsoft.fluentui.theme.token.AliasTokens.BrandBackgroundColorTokens.BrandBackground1
import com.microsoft.fluentui.theme.token.AliasTokens.BrandBackgroundColorTokens.BrandBackgroundTint
import com.microsoft.fluentui.theme.token.AliasTokens.BrandForegroundColorTokens.BrandForegroundTint
import com.microsoft.fluentui.theme.token.AliasTokens.ErrorAndStatusColorTokens.*
import com.microsoft.fluentui.theme.token.AliasTokens.NeutralBackgroundColorTokens.Background5
import com.microsoft.fluentui.theme.token.AliasTokens.NeutralBackgroundColorTokens.Background5Selected
import com.microsoft.fluentui.theme.token.AliasTokens.NeutralForegroundColorTokens.*
import com.microsoft.fluentui.theme.token.controlTokens.PersonaChipSize.Medium
import com.microsoft.fluentui.theme.token.controlTokens.PersonaChipSize.Small
import kotlinx.parcelize.Parcelize
enum class PersonaChipStyle {
Neutral,
Brand,
Danger,
SevereWarning,
Warning,
Success
}
enum class PersonaChipSize {
Small,
Medium
}
abstract class PersonaChipControlInfo : ControlInfo {
abstract val size: PersonaChipSize
abstract val enabled: Boolean
}
data class PersonaChipInfo(
val style: PersonaChipStyle = PersonaChipStyle.Neutral,
override val enabled: Boolean = true,
override val size: PersonaChipSize = Small
) : PersonaChipControlInfo()
@Parcelize
open class PersonaChipTokens : ControlToken, Parcelable {
@Composable
open fun backgroundColor(personaChipInfo: PersonaChipControlInfo): StateColor {
personaChipInfo as PersonaChipInfo
when (personaChipInfo.style) {
PersonaChipStyle.Neutral -> return StateColor(
rest = FluentTheme.aliasTokens.neutralBackgroundColor[Background5].value(
themeMode = FluentTheme.themeMode
),
selected = FluentTheme.aliasTokens.neutralBackgroundColor[Background5Selected].value(
themeMode = FluentTheme.themeMode
),
disabled = FluentTheme.aliasTokens.neutralBackgroundColor[Background5].value(
themeMode = FluentTheme.themeMode
)
)
PersonaChipStyle.Brand -> return StateColor(
rest = FluentTheme.aliasTokens.brandBackgroundColor[BrandBackgroundTint].value(
themeMode = FluentTheme.themeMode
),
selected = FluentTheme.aliasTokens.brandBackgroundColor[BrandBackground1].value(
themeMode = FluentTheme.themeMode
),
disabled = FluentTheme.aliasTokens.neutralBackgroundColor[Background5].value(
themeMode = FluentTheme.themeMode
)
)
PersonaChipStyle.Danger -> return StateColor(
rest = FluentTheme.aliasTokens.ErrorAndStatusColor[DangerBackground1].value(
themeMode = FluentTheme.themeMode
),
selected = FluentTheme.aliasTokens.ErrorAndStatusColor[DangerBackground2].value(
themeMode = FluentTheme.themeMode
)
)
PersonaChipStyle.SevereWarning -> return StateColor(
rest = FluentTheme.aliasTokens.ErrorAndStatusColor[SevereBackground1].value(
themeMode = FluentTheme.themeMode
),
selected = FluentTheme.aliasTokens.ErrorAndStatusColor[SevereBackground2].value(
themeMode = FluentTheme.themeMode
)
)
PersonaChipStyle.Warning -> return StateColor(
rest = FluentTheme.aliasTokens.ErrorAndStatusColor[WarningBackground1].value(
themeMode = FluentTheme.themeMode
),
selected = FluentTheme.aliasTokens.ErrorAndStatusColor[WarningBackground2].value(
themeMode = FluentTheme.themeMode
)
)
PersonaChipStyle.Success -> return StateColor(
rest = FluentTheme.aliasTokens.ErrorAndStatusColor[SuccessBackground1].value(
themeMode = FluentTheme.themeMode
),
selected = FluentTheme.aliasTokens.ErrorAndStatusColor[SuccessBackground2].value(
themeMode = FluentTheme.themeMode
)
)
}
}
@Composable
open fun textColor(personaChipInfo: PersonaChipControlInfo): StateColor {
personaChipInfo as PersonaChipInfo
when (personaChipInfo.style) {
PersonaChipStyle.Neutral -> return StateColor(
rest = FluentTheme.aliasTokens.neutralForegroundColor[Foreground2].value(
themeMode = FluentTheme.themeMode
),
selected = FluentTheme.aliasTokens.neutralForegroundColor[Foreground1].value(
themeMode = FluentTheme.themeMode
),
disabled = FluentTheme.aliasTokens.neutralForegroundColor[ForegroundDisable1].value(
themeMode = FluentTheme.themeMode
)
)
PersonaChipStyle.Brand -> return StateColor(
rest = FluentTheme.aliasTokens.brandForegroundColor[BrandForegroundTint].value(
themeMode = FluentTheme.themeMode
),
selected = FluentTheme.aliasTokens.neutralForegroundColor[ForegroundOnColor].value(
themeMode = FluentTheme.themeMode
),
disabled = FluentTheme.aliasTokens.neutralForegroundColor[ForegroundDisable1].value(
themeMode = FluentTheme.themeMode
)
)
PersonaChipStyle.Danger -> return StateColor(
rest = FluentTheme.aliasTokens.ErrorAndStatusColor[DangerForeground1].value(
themeMode = FluentTheme.themeMode
),
selected = FluentTheme.aliasTokens.neutralForegroundColor[ForegroundLightStatic].value(
themeMode = FluentTheme.themeMode
)
)
PersonaChipStyle.SevereWarning -> return StateColor(
rest = FluentTheme.aliasTokens.ErrorAndStatusColor[SevereForeground1].value(
themeMode = FluentTheme.themeMode
),
selected = FluentTheme.aliasTokens.neutralForegroundColor[ForegroundLightStatic].value(
themeMode = FluentTheme.themeMode
)
)
PersonaChipStyle.Warning -> return StateColor(
rest = FluentTheme.aliasTokens.ErrorAndStatusColor[WarningForeground1].value(
themeMode = FluentTheme.themeMode
),
selected = FluentTheme.aliasTokens.neutralForegroundColor[ForegroundDarkStatic].value(
themeMode = FluentTheme.themeMode
)
)
PersonaChipStyle.Success -> return StateColor(
rest = FluentTheme.aliasTokens.ErrorAndStatusColor[SuccessForeground1].value(
themeMode = FluentTheme.themeMode
),
selected = FluentTheme.aliasTokens.neutralForegroundColor[ForegroundLightStatic].value(
themeMode = FluentTheme.themeMode
)
)
}
}
@Composable
open fun borderRadius(personaChipInfo: PersonaChipControlInfo): Dp {
return when (personaChipInfo.size) {
Small -> GlobalTokens.borderRadius(GlobalTokens.BorderRadiusTokens.Small)
Medium -> GlobalTokens.borderRadius(GlobalTokens.BorderRadiusTokens.Medium)
}
}
@Composable
open fun fontSize(personaChipInfo: PersonaChipControlInfo): FontInfo {
return when (personaChipInfo.size) {
Small -> FluentTheme.aliasTokens.typography[AliasTokens.TypographyTokens.Caption1]
Medium -> FluentTheme.aliasTokens.typography[AliasTokens.TypographyTokens.Body2]
}
}
@Composable
open fun verticalPadding(personaChipInfo: PersonaChipControlInfo): Dp {
return when (personaChipInfo.size) {
Small -> GlobalTokens.size(GlobalTokens.SizeTokens.Size20)
Medium -> GlobalTokens.size(GlobalTokens.SizeTokens.Size20)
}
}
@Composable
open fun horizontalPadding(personaChipInfo: PersonaChipControlInfo): Dp {
return when (personaChipInfo.size) {
Small -> GlobalTokens.size(GlobalTokens.SizeTokens.Size40)
Medium -> GlobalTokens.size(GlobalTokens.SizeTokens.Size80)
}
}
@Composable
open fun avatarToTextSpacing(personaChipInfo: PersonaChipControlInfo): Dp {
return GlobalTokens.size(GlobalTokens.SizeTokens.Size80)
}
@Composable
open fun avatarSize(personaChipInfo: PersonaChipControlInfo): AvatarSize {
return AvatarSize.Size16
}
}

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

@ -0,0 +1,75 @@
package com.microsoft.fluentui.theme.token.controlTokens
import androidx.compose.runtime.Composable
import com.microsoft.fluentui.theme.FluentTheme
import com.microsoft.fluentui.theme.token.AliasTokens
import com.microsoft.fluentui.theme.token.FluentStyle
import com.microsoft.fluentui.theme.token.StateColor
import kotlinx.parcelize.Parcelize
data class SearchBarPersonaChipInfo(
val style: FluentStyle = FluentStyle.Neutral,
override val enabled: Boolean = true,
override val size: PersonaChipSize = PersonaChipSize.Small
) : PersonaChipControlInfo()
@Parcelize
open class SearchBarPersonaChipTokens : PersonaChipTokens() {
@Composable
override fun backgroundColor(searchBarPersonaChipInfo: PersonaChipControlInfo): StateColor {
searchBarPersonaChipInfo as SearchBarPersonaChipInfo
when (searchBarPersonaChipInfo.style) {
FluentStyle.Neutral -> return StateColor(
rest = FluentTheme.aliasTokens.neutralBackgroundColor[AliasTokens.NeutralBackgroundColorTokens.Background6].value(
themeMode = FluentTheme.themeMode
),
selected = FluentTheme.aliasTokens.neutralBackgroundColor[AliasTokens.NeutralBackgroundColorTokens.BackgroundInverted].value(
themeMode = FluentTheme.themeMode
),
disabled = FluentTheme.aliasTokens.neutralBackgroundColor[AliasTokens.NeutralBackgroundColorTokens.Background6].value(
themeMode = FluentTheme.themeMode
)
)
FluentStyle.Brand -> return StateColor(
rest = FluentTheme.aliasTokens.brandBackgroundColor[AliasTokens.BrandBackgroundColorTokens.BrandBackground3].value(
themeMode = FluentTheme.themeMode
),
selected = FluentTheme.aliasTokens.neutralBackgroundColor[AliasTokens.NeutralBackgroundColorTokens.Background1].value(
themeMode = FluentTheme.themeMode
),
disabled = FluentTheme.aliasTokens.brandBackgroundColor[AliasTokens.BrandBackgroundColorTokens.BrandBackground3].value(
themeMode = FluentTheme.themeMode
)
)
}
}
@Composable
override fun textColor(searchBarPersonaChipInfo: PersonaChipControlInfo): StateColor {
searchBarPersonaChipInfo as SearchBarPersonaChipInfo
when (searchBarPersonaChipInfo.style) {
FluentStyle.Neutral -> return StateColor(
rest = FluentTheme.aliasTokens.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.Foreground1].value(
themeMode = FluentTheme.themeMode
),
selected = FluentTheme.aliasTokens.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.ForegroundOnColor].value(
themeMode = FluentTheme.themeMode
),
disabled = FluentTheme.aliasTokens.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.ForegroundDisable2].value(
themeMode = FluentTheme.themeMode
)
)
FluentStyle.Brand -> return StateColor(
rest = FluentTheme.aliasTokens.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.ForegroundOnColor].value(
themeMode = FluentTheme.themeMode
),
selected = FluentTheme.aliasTokens.brandForegroundColor[AliasTokens.BrandForegroundColorTokens.BrandForeground1].value(
themeMode = FluentTheme.themeMode
),
disabled = FluentTheme.aliasTokens.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.ForegroundDisable1].value(
themeMode = FluentTheme.themeMode
)
)
}
}
}

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

@ -5,4 +5,5 @@
<string name="fluentui_disabled">Disabled</string>
<string name="fluentui_enabled">Enabled</string>
<string name="fluentui_close_sheet">Close Sheet</string>
<string name="fluentui_close">Close</string>
</resources>

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

@ -98,6 +98,7 @@ fun AvatarCarousel(
val backgroundColor = getColorByState(
stateData = getAvatarCarouselTokens().backgroundColor(getAvatarCarouselInfo()),
enabled = item.enabled,
selected = false,
interactionSource = interactionSource
)
val textColor = getAvatarCarouselTokens().getTextColor(getAvatarCarouselInfo())

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

@ -0,0 +1,151 @@
package com.microsoft.fluentui.tokenized.persona
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Icon
import androidx.compose.material.Text
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
import androidx.compose.ui.draw.clip
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.unit.dp
import com.microsoft.fluentui.compose.Strings
import com.microsoft.fluentui.compose.getString
import com.microsoft.fluentui.theme.FluentTheme
import com.microsoft.fluentui.theme.token.ControlTokens
import com.microsoft.fluentui.theme.token.controlTokens.PersonaChipInfo
import com.microsoft.fluentui.theme.token.controlTokens.PersonaChipSize
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
*
* @param person Person data for the persona chip
* @param modifier Modifier for the persona chip
* @param style Optional persona chip style. See [PersonaChipStyle]
* @param size Option persona chip size. See [PersonaChipSize]
* @param enabled Whether persona chip is enabled or disabled. Enabled by default.
* @param selected Whether persona chip is selected or unselected. Unselected by default.
* @param onClick onClick action for persona chip
* @param onCloseClick onClick action for close button. This action is performed after the chip is selected and on the close icon
* @param interactionSource Optional interactionSource
* @param personaChipTokens Optional tokens for persona chip
*/
@Composable
fun PersonaChip(
person: Person,
modifier: Modifier = Modifier,
style: PersonaChipStyle = PersonaChipStyle.Neutral,
size: PersonaChipSize = Medium,
enabled: Boolean = true,
selected: Boolean = false,
onClick: (() -> Unit)? = null,
onCloseClick: (() -> Unit)? = null,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
personaChipTokens: PersonaChipTokens? = null
) {
val token = personaChipTokens
?: FluentTheme.controlTokens.tokens[ControlTokens.ControlType.PersonaChip] as PersonaChipTokens
CompositionLocalProvider(
LocalPersonaChipTokens provides token,
LocalPersonaChipInfo provides PersonaChipInfo(
style,
enabled,
size
)
) {
val backgroundColor = getColorByState(
stateData = getPersonaChipTokens().backgroundColor(personaChipInfo = getPersonaChipInfo()),
enabled = enabled, selected = selected, interactionSource = interactionSource
)
val textColor = getColorByState(
stateData = getPersonaChipTokens().textColor(personaChipInfo = getPersonaChipInfo()),
enabled = enabled, selected = selected, interactionSource = interactionSource
)
val font = getPersonaChipTokens().fontSize(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().borderRadius(personaChipInfo = getPersonaChipInfo())
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 = getString(string = Strings.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,
lineHeight = font.fontSize.lineHeight,
fontSize = font.fontSize.size,
fontWeight = font.weight
)
}
}
}
}
@Composable
private fun getPersonaChipTokens(): PersonaChipTokens {
return LocalPersonaChipTokens.current
}
@Composable
private fun getPersonaChipInfo(): PersonaChipInfo {
return LocalPersonaChipInfo.current
}

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

@ -0,0 +1,151 @@
package com.microsoft.fluentui.tokenized.persona
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Icon
import androidx.compose.material.Text
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
import androidx.compose.ui.draw.clip
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.unit.dp
import com.microsoft.fluentui.compose.Strings
import com.microsoft.fluentui.compose.getString
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.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
*
* @param person Person data for the persona chip
* @param modifier Modifier for the persona chip
* @param style Optional persona chip style. See [FluentStyle]
* @param size Option persona chip size. See [PersonaChipSize]
* @param enabled Whether persona chip is enabled or disabled. Enabled by default.
* @param selected Whether persona chip is selected or unselected. Unselected by default.
* @param onClick onClick action for persona chip
* @param onCloseClick onClick action for close button. This action is performed after the chip is selected and on the close icon
* @param interactionSource Optional interactionSource
* @param searchbarPersonaChipTokens Optional tokens for persona chip
*/
@Composable
fun SearchBarPersonaChip(
person: Person,
modifier: Modifier = Modifier,
style: FluentStyle = FluentStyle.Neutral,
size: PersonaChipSize = PersonaChipSize.Medium,
enabled: Boolean = true,
selected: Boolean = false,
onClick: (() -> Unit)? = null,
onCloseClick: (() -> Unit)? = null,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
searchbarPersonaChipTokens: SearchBarPersonaChipTokens? = null
) {
val token = searchbarPersonaChipTokens
?: FluentTheme.controlTokens.tokens[ControlTokens.ControlType.SearchBarPersonaChip] as SearchBarPersonaChipTokens
CompositionLocalProvider(
LocalSearchBarPersonaChipTokens provides token,
LocalSearchBarPersonaChipInfo provides SearchBarPersonaChipInfo(
style,
enabled,
size = size
)
) {
val backgroundColor = getColorByState(
stateData = getSearchBarPersonaChipTokens().backgroundColor(searchBarPersonaChipInfo = getSearchBarPersonaChipInfo()),
enabled = enabled, selected = selected, interactionSource = interactionSource
)
val textColor = getColorByState(
stateData = getSearchBarPersonaChipTokens().textColor(searchBarPersonaChipInfo = getSearchBarPersonaChipInfo()),
enabled = enabled, selected = selected, interactionSource = interactionSource
)
val font =
getSearchBarPersonaChipTokens().fontSize(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().borderRadius(personaChipInfo = getSearchBarPersonaChipInfo())
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 = getString(string = Strings.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,
lineHeight = font.fontSize.lineHeight,
fontSize = font.fontSize.size
)
}
}
}
}
@Composable
private fun getSearchBarPersonaChipTokens(): SearchBarPersonaChipTokens {
return LocalSearchBarPersonaChipTokens.current
}
@Composable
private fun getSearchBarPersonaChipInfo(): SearchBarPersonaChipInfo {
return LocalSearchBarPersonaChipInfo.current
}

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

@ -1,7 +1,10 @@
package com.microsoft.fluentui.tokenized.persona
import androidx.annotation.DrawableRes
import androidx.compose.foundation.interaction.*
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
@ -39,6 +42,15 @@ class Person(
return name
}
fun getLabel(): String {
val label = "$firstName $lastName"
if (label.trim().isNotBlank())
return label
if(!email.isNullOrBlank())
return email
return "Anonymous"
}
fun isImageAvailable(): Boolean {
return image != null || imageBitmap != null
}
@ -111,6 +123,7 @@ class Group(
return initial.uppercase()
}
}
class Persona(
val person: Person,
val title: String,
@ -127,34 +140,48 @@ class AvatarCarouselItem(
val enableActivityRing: Boolean = false,
val onItemClick: (() -> Unit)? = null
)
@Composable
fun getColorByState(
stateData: StateColor,
enabled: Boolean,
selected: Boolean,
interactionSource: InteractionSource
): Color {
if (enabled) {
val isPressed by interactionSource.collectIsPressedAsState()
if (isPressed)
if (selected && isPressed)
return stateData.selectedPressed
else if (isPressed)
return stateData.pressed
val isFocused by interactionSource.collectIsFocusedAsState()
if (isFocused)
return stateData.pressed
if (selected && isFocused)
return stateData.selectedFocused
else if (isFocused)
return stateData.focused
val isHovered by interactionSource.collectIsHoveredAsState()
if (selected && isHovered)
return stateData.selectedFocused
if (isHovered)
return stateData.pressed
return stateData.focused
if (selected)
return stateData.selected
return stateData.rest
} else
} else if (selected)
return stateData.selectedDisabled
else
return stateData.disabled
}
fun getAvatarSize(secondaryText: String?, tertiaryText: String?): AvatarSize {
if(secondaryText == null && tertiaryText == null){
if (secondaryText == null && tertiaryText == null) {
return AvatarSize.Size24
}
if(secondaryText != null && tertiaryText == null){
if (secondaryText != null && tertiaryText == null) {
return AvatarSize.Size40
}
return AvatarSize.Size56