Object Cell¶
Object Cell is a list item view that can represent a business object.
Anatomy¶
An Object Cell consists of an unread object indicator, avatar,icon stack, labels (headline, subheadline,footnote), tags, description, statuses, attribute icons, avatar stack and action icon.
Object Cell Anatomy |
Example¶
Object Cell on a Tablet |
Development¶
Object Cell are implemented entirely using Jetpack Compose, including a Composable
FioriHorizonTheme
. To understand the fundamentals of Jetpack Compose, refer to the Android Developer's documentation.
The Object Cell provides two UI components for application developers to use
FioriObjectCell
– a UI component to display a single Fiori Object Cell.FioriObjectCellList
– a UI component to display a series of Object Cell in a horizontally scrollable list.
Usage in an Application¶
This section describes how to use the Object Cell components in an application.
Object Cell on a Phone |
How to Create an Object Cell
¶
FioriObjectCell
is a Composable
function that takes below parameters:
modifier
– aModifier
that is used to define the height and width of the cell.cellData
– aFioriObjectCellData
that specifies the data to be displayed in the cell.cellType
– aCellType
that specifies the cell type. Users can chooseOBJECT
orCONTACT
.onClick
– alambda
that specifies thelamda
function that can be called when click on cellonLongPress
– alambda
that specifies thelamda
function that can be called when long press on cellonSwipeToStart
– alambda
that specifies thelamda
function that can be called when swipe to Start, this function will not work whencellSwipeable
inFioriObjectCellData
not set to trueonSwipeToStartIcon
– aPainter
icon define for the icon displayed when swipe to StartonSwipeToEnd
– alambda
that specifies thelamda
function that can be called when swipe to End, this function will not work whencellSwipeable
inFioriObjectCellData
not set trueonSwipeToEndIcon
– aPainter
icon define for the icon displayed when swipe to Endprogress
– alambda
for the progress bar. This will displayed whendisplayProgress
set to true inFioriObjectCellUiState
colors
– aFioriObjectCellColors
define the color use in celltextStyles
– aFioriObjectCellTextStyles
define thetextStyles
in cellstyles
– aFioriObjectCellStyles
define the styles such like padding in cell
@Composable
fun FioriObjectCell(
modifier: Modifier = Modifier,
cellData: FioriObjectCellData,
onClick: (() -> Unit)? = null,
onLongPress: (() -> Unit)? = null,
onSwipeToStart: (() -> Unit)? = null,
onSwipeToStartIcon: Painter = PainterBuilder.build(
icon = FioriIcon(resId = R.drawable.ic_sap_icon_delete_filled)
),
onSwipeToEnd: (() -> Unit)? = null,
onSwipeToEndIcon: Painter = PainterBuilder.build(
icon = FioriIcon(resId = R.drawable.ic_sap_icon_add)
),
progress: (@Composable () -> Unit)? = {
CircularProgressIndicator(
modifier = Modifier.size(FioriObjectCellDefaults.styles().actionButtonSize()),
color = FioriObjectCellDefaults.colors().progressIndicatorColor()
)
},
colors: FioriObjectCellColors = FioriObjectCellDefaults.colors(),
textStyles: FioriObjectCellTextStyles = FioriObjectCellDefaults.textStyles(),
styles: FioriObjectCellStyles = FioriObjectCellDefaults.styles(),
)
FioriObjectCellData
is like below
data class FioriObjectCellData(
override val headline: String = "",
val annotatedHeadline: @RawValue MutableState<AnnotatedString>? = null,
override val avatar: FioriAvatarConstruct? = null,
var iconStack: List<IconStackElement>? = null,
override val subheadline: String? = null,
val annotatedSubheadline: @RawValue MutableState<AnnotatedString>? = null,
override val footnote: String? = null,
val annotatedFootnote: @RawValue MutableState<AnnotatedString>? = null,
val description: String? = null,
val tags: List<FioriTagData> = listOf(),
val avatarStack: FioriAvatarConstruct? = null,
var status: FioriObjectCellStatusData? = null,
var subStatus: FioriObjectCellStatusData? = null,
val attributeIcons: AttributeIcons? = null,
val action: Action? = null,
override val cellClickable: Boolean = true,
override val cellSwipeable: Boolean = false,
var displayReadIndicator: MutableState<Boolean> = mutableStateOf(true),
var displayProcess: MutableState<Boolean> = mutableStateOf(false),
override var displaySelectBox: MutableState<Boolean> = mutableStateOf(false),
override var contentDescription: String = "",
override val enabled: Boolean = true,
override var isSelected: MutableState<Boolean> = mutableStateOf(false),
)
The FioriObjectCellData
object is now created using a Builder class which provides a fluent interface for object construction. This Builder class encapsulates the construction logic of FioriObjectCellData
and allows for cleaner and more readable code.
A typical usage of the builder would look like this:
val fioriObjectCellData = FioriObjectCellData.Builder()
.setHeadline("Headline")
.setAvatar(someAvatarConstruct)
.setIconStack(someIconStackList)
.setCellClickable(true)
.build()
Each setter method in the Builder class corresponds to a property of FioriObjectCellData
.
The build()
method is called at the end to finalize the creation of FioriObjectCellData
. The build()
method ensures all required properties are set and then returns an instance of FioriObjectCellData
.
Note: Some properties, such as headline
and annotatedHeadline
, cannot be set simultaneously. An IllegalArgumentException
will be thrown if an attempt is made to do so.
Fields¶
Headline¶
The headline is the main area for text content. The headline is the only mandatory content for Object Cell. The title can be specified by:
headline = ""
in FioriObjectCellData
Avatar¶
The avatar provides a visual representation of the object , can be specified by :
avatar = FioriAvatarConstruct(
hasBadge = hasBadge,
hasBorder = hasBorder,
avatarList = listOf(
FioriAvatarData(
image = FioriImage(url = ""),
), FioriAvatarData(
image = FioriImage(url = ""),
)
),
size = 18.dp,
avatarBadge = FioriIcon(
resId = R.drawable.ic_circle_unread
),
type = FioriAvatarType.DOUBLE
)
FioriAvatarConstruct
is used to construct the Avatar area.
@Parcelize
data class FioriAvatarConstruct(
val type: FioriAvatarType = FioriAvatarType.SINGLE,
var avatarList: List<FioriAvatarData>,
val hasBadge: Boolean = false,
val avatarBadge: FioriIcon? = null,
val groupDisplayNumber: Int = 0,
val size: Dp = 40.dp,
val hasBorder: Boolean = false,
val borderColor: Color? = null,
val backgroundColor: Color? = null,
val shape: FioriAvatarShape = FioriAvatarShape.ROUNDEDCORNER
) : Parcelable
type
– aFioriAvatar
have three type. SINGLE,DOUBLE,GROUP. SINGLE and DOUBLE is used to display in the Avatar area. Group is used to display in Avatar stack area.avatarList
– aList
containsFioriAvatarData
objectshasBadge
– aBoolean
is used to display the badge or notavatarBadge
– aFioriIcon
is used to define the badgegroupDisplayNumber
– aInt
is used to display the number when use the group type.size
– aDp
is used to define the Avatar area size whenAvatarType
is Single and Double.hasBorder
– aBoolean
is used to display the border of Avatar.borderColor
– aColor
is used to define the color of border.backgroundColor
– aColor
is used to display the background of Avatar.Shape
– aFioriAvatarShape
is used to support two type.ROUNDEDCORNER and CIRCLE.
Icon Stack¶
The iconStack
is the area for icon and text display. The text will always displayed at the top of iconStack
. Two iconStack
elements can be displayed in the stack.
The iconStack
can be specified as follows:
iconStack = listOf(
IconStackElement(text = "Display"),
IconStackElement(
icon = FioriIcon(
resId = R.drawable.ic_directions_24px,
contentDescription = "checked"
)
)
),
Subheadline¶
The subheadline is under the headline and provides additional information.The subheadline can be specified by:
subheadline = ""
Footnote¶
The footnote is under the subheadline and provides further information.The footnote can be specified by:
footnote = ""
Description¶
The description is under the headline, subheadline, footnote, tags, and status, substatus, attribute icons, and provides further information. The description can be specified as follows:
description = ""
Tags¶
Tags may be used to indicate categories, types, or statuses. Tags are displayed next to the footnote if space is available. The tags can be specified by :
tags= listOf(
FioriTagData(text = "gray"),
FioriTagData(text = "blue"),
FioriTagData(text = "red"),
)
Avatar Stack¶
The avatar stack is a FioriAvatarConstruct
that displays a row of FioriAvatar
at the bottom of the cell. FioriAvatarType
should set to the Group. The Avatar stack can be specified by :
avatarStack = FioriAvatarConstruct(
hasBadge = false,
type = FioriAvatarType.GROUP,
avatarList = listOf(avatar2, avatar3, avatar4, avatar5),
size = 16.dp,
groupDisplayNumber = 4,
hasBorder = false
)
Status¶
Up to two statuses(status and substatus) can be displayed, stacked vertically, to show attributes of the object. Status could be customized as either text or an image.
statusData = FioriObjectCellStatusData(
label = "Success",
icon = FioriIcon(
resId = R.drawable.ic_baseline_tag_faces_24,
contentDescription = "Positive status icon",
),
type = FioriObjectCellStatusType.Positive,
isIconAtStart = true
)
Attribute Icons¶
A set of icons can be displayed horizontally under the statuses. These icons can represent both persistent and variable information about the object. It is recommended to have at most four attribute icons.
attribute = AttributeIcons(
iconsList = listOf(
FioriIcon(
resId = R.drawable.ic_chip_checked,
contentDescription = "checked"
),
FioriIcon(
resId = R.drawable.ic_chip_checked,
contentDescription = "checked"
)
)
Action¶
Action can be displayed on the right of cell. It is suggest to only have one action in object cell.
iconAction =
Action(
icon = FioriIcon(
resId = R.drawable.ic_email_black_24dp,
contentDescription = "email",
)
)
Progress¶
User can define the progress by using lambda. Below is the default Progress bar. Progress display or not decide by displayProcess
in FioriObjectCellUiState
{
CircularProgressIndicator(
modifier = Modifier.size(FioriObjectCellDefaults.styles().actionButtonSize()),
color = FioriObjectCellDefaults.colors().progressIndicatorColor(),
)
},
User Action¶
Simple Tap/Click to Enable/Disable Read Mode¶
Upon clicking or tapping an Object Cell, the entire cell will display a ripple effect and then the details about the clicked Object Cell will open up. Returning to the list of Object Cells, the clicked Object Cell will be marked as Read, i.e.the unread object indicator will disappear. User can customize the onClick
function. Below is the default implementation.
Long Press to Enable/Disable Selection Mode¶
An Object Cell can enter and exit Selection mode using displaySelectBox
in FioriObjectCellData
. The Object Cell gains a checkbox in Selection mode. Users can also customize the onLongPress
.
The following sample illustrates how to enable Selection mode in FioriObjectCellList
var enableSelectBox by remember { mutableStateOf(false) }
FioriObjectCellList(
cellList = cellList,
onClick = { p1, _ ->
cellList[p1].displayReadIndicator.value = false
},
onLongPress = { _, _ ->
enableSelectBox = !enableSelectBox
cellViewModel.updateObjectCellDisplaySelectBoxInList(enableSelectBox)
},
cellType = CellType.OBJECT,
)
Enabling Actions on Swipe¶
cellSwipeable
can set to true to enable the swipe action of Object Cell.
onSwipeToStart
,onSwipeToStartIcon
,onSwipeToEnd
,onSwipeToEndIcon
are used to define the swipe lambda
and swipe icon.
App developers can define their own actions in response to the swipe gesture in two directions. In the demo app provided by Fiori UI, a delete action is defined for swiping from right to left, and a mock add action is defined for swiping from left to right.
Swipe can defined either in FioriObjectCell
or FioriObjectCellList
. Below is the example in FioriObjectCellList
.
Swipe can have two behavior when Release the swipe. Release.EXTEND and Release.Reset . These two behavior can defined in styles.
FioriObjectCellList(
objectCellList = objectList,
enableSelectBox = enableSelectBox,
onClick = {
if (it.isRead == false) {
it.isRead = true
}
currentNews.newsData = it.stateData
navController.navigate(Screen.ObjectCellDetail.route)
},
onSwipeToEnd = { p1, p2 ->
p2.isRead = !p2.isRead
},
onSwipeToStart = { p1, p2 ->
objectList.remove(p2)
Toast.makeText(
context,
" The index ${p1!!} item is deleted from list ",
Toast.LENGTH_SHORT
)
.show()
},
styles = FioriObjectCellDefaults.styles(
headlineDisplayLineMaxNumber = headlineNumber,
subheadlineDisplayLineMaxNumber = subheadlineNumber,
footnoteDisplayLineMaxNumber = footnoteNumber,
swipeToEnd = SwipeRelease.RESET,
swipeToStart = SwipeRelease.EXTEND
),
)
Contact Cell¶
Object Cells can defined as a special type of Contact Cell. Contact Cells can have multiple actions. Users can define the Contact Cell using FioriContactCell
or FioriContactCellList
:
FioriContactCell(
cellData = FioriContactCellData(
headline = "Headline",
subheadline = "Developer",
footnote = "Earth",
avatar = FioriAvatarConstruct(
avatarList = listOf(
FioriAvatarData(
text = "A",
shape = FioriAvatarShape.ROUNDEDCORNER
)
),
),
contactActions = listOf(
Action(
icon = FioriIcon(
resId = R.drawable.ic_sap_icon_email,
contentDescription = "email",
)
),
Action(
icon = FioriIcon(
resId = R.drawable.ic_sap_icon_call,
contentDescription = "email",
),
)
),
),
styles = FioriObjectCellDefaults.styles(
divider = true,
),
cellType = CellType.CONTACT
)
Contact Cell can have avatar,headline,subheadline and footnote in the left. Contact Cell only can have contact actions in the right accessories.
Fiori Object Cell List¶
FioriObjectCellList
allows you to display the Object Cell in a list. It accepts a list of FioriObjectCellData
as a parameter.
@Composable
fun FioriObjectCellList(
modifier: Modifier = Modifier,
cellList: List<FioriObjectCellData>,
loadMore: (() -> Unit)? = null,
onSwipeToStart: ((Int, FioriObjectCellData) -> Unit)? = null,
onSwipeToStartIcon: Painter = PainterBuilder.build(
icon = FioriIcon(resId = R.drawable.ic_sap_icon_delete_filled)
),
onSwipeToEnd: ((Int, FioriObjectCellData) -> Unit)? = null,
onSwipeToEndIcon: Painter = PainterBuilder.build(
icon = FioriIcon(resId = R.drawable.ic_sap_icon_add)
),
onClick: ((Int, FioriObjectCellData) -> Unit)? = { _, cellCommonData ->
cellCommonData.setDisplayReadIndicator(false)
},
onLongPress: ((Int, FioriObjectCellData) -> Unit)? = null,
progress: (@Composable () -> Unit)? = {
CircularProgressIndicator(
modifier = Modifier.size(FioriObjectCellDefaults.styles().actionButtonSize()),
color = FioriObjectCellDefaults.colors().progressIndicatorColor()
)
},
colors: FioriObjectCellColors = FioriObjectCellDefaults.colors(),
textStyles: FioriObjectCellTextStyles = FioriObjectCellDefaults.textStyles(),
styles: FioriObjectCellStyles = FioriObjectCellDefaults.styles(),
)
Load More (Lazy Loading)¶
The loadMore
function is used to implement lazy loading in the FioriObjectCellList
. This function is invoked when the user has scrolled to a certain point near the end of the list, defined by the startToLoad
style. The loadMore
function should then load more items and add them to the cellList
.
Here's an example of how you might use the loadMore
function:
val currentPage = 0
val pageSize = 10
fun loadMoreItems() {
viewModelScope.launch(Dispatchers.IO) {
val newItems = retriveData(currentPage * pageSize, pageSize)
_objectCellStateList.value =
((_objectCellStateList.value!! + newItems) as MutableList<FioriObjectCellData>)
currentPage++
}
}
Styles¶
The style of the component can be customized. The recommended approach is to override FioriObjectCellDefaults.styles
and only customize the attributes you desire. This means that other attributes can be retained and all the components in the application will have a consistent look and feel. For example:
FioriObjectCell(
cellData = myCellData,
modifier: Modifier = Modifier,
progress = { MyProgressButton() },
styles = FioriObjectCellDefaults.styles(avatarSize = 40.dp)
)