Skip to content

DON-1396 Add Loading Label to BpkCalendarDayCell, update BpkSkeleton #2274

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Apr 29, 2025
Original file line number Diff line number Diff line change
Expand Up @@ -69,16 +69,20 @@ internal class CalendarCellDayHolder(
icon.setImageResource(cellLabel.resId)
cellLabel.tint?.let { tint -> icon.imageTintList = context.getColorStateList(tint) }
}

is CellLabel.Loading -> {}
}
when {
model.selection != null -> {
day.setTextColor(selectionContentColor(model.selection))
day.background = selectionBackground(model.selection)
}

model.inactive -> {
day.setTextColor(disabledTextColor)
day.background = null
}

else -> {
day.setTextColor(defaultTextColor)
day.background = null
Expand All @@ -90,23 +94,27 @@ internal class CalendarCellDayHolder(
label.isVisible = false
icon.visibility = View.GONE
}

model.info.style == CellStatusStyle.Label -> {
when (val cellLabel = model.info.label) {
is CellLabel.Text -> {
label.isVisible = cellLabel.text.isNotEmpty()
label.setTextColor(labelColor(model.info.status))
icon.visibility = View.GONE
}
is CellLabel.Icon -> {

is CellLabel.Icon, is CellLabel.Loading -> {
icon.visibility = View.VISIBLE
label.visibility = View.GONE
}
}
}

else -> {
when (val cellLabel = model.info.label) {
is CellLabel.Text -> label.isVisible = cellLabel.text.isNotEmpty()
is CellLabel.Icon -> icon.visibility = View.VISIBLE
is CellLabel.Loading -> {}
}
}
}
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,14 @@ package net.skyscanner.backpack.demo.compose
import android.content.Context
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import net.skyscanner.backpack.calendar2.CalendarSelection
import net.skyscanner.backpack.compose.calendar.BpkCalendar
Expand Down Expand Up @@ -98,6 +103,18 @@ fun CalendarNoFloatingYearLabel(modifier: Modifier = Modifier) =
fun CalendarMultiSelection(modifier: Modifier = Modifier) =
CalendarDemo(CalendarStoryType.MultiSelection, modifier)

@Composable
@Calendar2Component
@ComposeStory("Loading")
fun CalendarLoading(modifier: Modifier = Modifier) {
var type by remember { mutableStateOf(CalendarStoryType.Loading) }
CalendarDemo(type, modifier)
LaunchedEffect(Unit) {
delay(1000)
type = CalendarStoryType.WithIconAsLabels
}
}

@Composable
private fun CalendarDemo(
type: CalendarStoryType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ enum class CalendarStoryType {
PreselectedRange,
YearLabelInMonthHeader,
MultiSelection,
Loading,
;

companion object {
Expand Down Expand Up @@ -183,6 +184,22 @@ enum class CalendarStoryType {
),
),
)

Loading ->
CalendarParams(
now = now,
range = range,
selectionMode = rangeSelectionModeWithAccessibilityLabels(),
cellsInfo = range
.toIterable()
.associateWith {
CellInfo(
label = CellLabel.Loading("Loading"),
status = CellStatus.Neutral,
style = CellStatusStyle.Label,
)
},
)
}

private fun rangeSelectionModeWithAccessibilityLabels() = CalendarParams.SelectionMode.Range(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ class BpkCalendarTest : BpkSnapshotTest() {
snap(calendar, padding = 0, captureFullScreen = true)
}

@Test
@Variants(BpkTestVariant.Default, BpkTestVariant.DarkMode)
fun loading() {
val calendar = BpkCalendar(testContext)
calendar.setParams(BpkCalendarTestCases.Params.Loading)
snap(calendar, padding = 0, captureFullScreen = true)
}

@Test
@Variants(BpkTestVariant.Default, BpkTestVariant.DarkMode)
fun past() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,11 @@ object BpkCalendarTestCases {
LocalDate.of(initialStartDate.year, initialStartDate.month, initialStartDate.dayOfMonth + 3) to
CellInfo(style = CellStatusStyle.Label, label = CellLabel.Text("£12"), status = CellStatus.Positive),
LocalDate.of(initialStartDate.year, initialStartDate.month, initialStartDate.dayOfMonth + 4) to
CellInfo(style = CellStatusStyle.Label, label = CellLabel.Text("£900000000000000"), status = CellStatus.Positive),
CellInfo(
style = CellStatusStyle.Label,
label = CellLabel.Text("£900000000000000"),
status = CellStatus.Positive,
),
LocalDate.of(initialStartDate.year, initialStartDate.month, initialStartDate.dayOfMonth + 5) to
CellInfo(style = CellStatusStyle.Label, label = CellLabel.Text("£900000"), status = CellStatus.Positive),
LocalDate.of(initialStartDate.year, initialStartDate.month, initialStartDate.dayOfMonth + 6) to
Expand All @@ -68,7 +72,22 @@ object BpkCalendarTestCases {
),
),
)

val Loading = DefaultRange.copy(
cellsInfo = mapOf(
LocalDate.of(initialStartDate.year, initialStartDate.month, initialStartDate.dayOfMonth + 1) to
CellInfo(style = CellStatusStyle.Label, label = CellLabel.Loading("Loading")),
LocalDate.of(initialStartDate.year, initialStartDate.month, initialStartDate.dayOfMonth + 2) to
CellInfo(style = CellStatusStyle.Label, label = CellLabel.Loading("Loading")),
LocalDate.of(initialStartDate.year, initialStartDate.month, initialStartDate.dayOfMonth + 3) to
CellInfo(style = CellStatusStyle.Label, label = CellLabel.Loading("Loading")),
LocalDate.of(initialStartDate.year, initialStartDate.month, initialStartDate.dayOfMonth + 4) to
CellInfo(style = CellStatusStyle.Label, label = CellLabel.Loading("Loading")),
LocalDate.of(initialStartDate.year, initialStartDate.month, initialStartDate.dayOfMonth + 5) to
CellInfo(style = CellStatusStyle.Label, label = CellLabel.Loading("Loading")),
LocalDate.of(initialStartDate.year, initialStartDate.month, initialStartDate.dayOfMonth + 6) to
CellInfo(style = CellStatusStyle.Label, label = CellLabel.Loading("Loading")),
),
)
val Past = DefaultRange.copy(
range = LocalDate.of(2017, 1, 2)..LocalDate.of(2017, 12, 31),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,13 @@ class BpkCalendarTest : BpkSnapshotTest() {
snap(controller)
}

@Test
@Variants(BpkTestVariant.Default, BpkTestVariant.DarkMode)
fun loading() {
val controller = createController(BpkCalendarTestCases.Params.Loading)
snap(controller)
}

private fun snap(controller: BpkCalendarController) =
snap(padding = 0.dp) {
BpkCalendar(controller = controller)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ sealed class CellLabel : Serializable {
val resId: Int,
val tint: Int? = null,
) : CellLabel()

data class Loading(val contentDescription: String) : CellLabel()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should replace serialisables with Parcelables/custom savers when we can. Not as a part of this PR though

}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@

package net.skyscanner.backpack.compose.calendar.internal

import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
Expand All @@ -27,6 +32,7 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
Expand Down Expand Up @@ -57,6 +63,10 @@ import net.skyscanner.backpack.compose.LocalContentColor
import net.skyscanner.backpack.compose.calendar.BpkCalendarDayCellTestTag
import net.skyscanner.backpack.compose.icon.BpkIcon
import net.skyscanner.backpack.compose.icon.findBySmall
import net.skyscanner.backpack.compose.skeleton.BpkHeadlineSkeleton
import net.skyscanner.backpack.compose.skeleton.BpkShimmerOverlay
import net.skyscanner.backpack.compose.skeleton.BpkShimmerSize
import net.skyscanner.backpack.compose.skeleton.BpkSkeletonHeightSizeType
import net.skyscanner.backpack.compose.text.BpkText
import net.skyscanner.backpack.compose.theme.BpkTheme
import net.skyscanner.backpack.compose.tokens.BpkSpacing
Expand Down Expand Up @@ -134,33 +144,69 @@ internal fun BpkCalendarDayCell(
}

if (!inactive) {
when (val cellLabel = model.info.label) {
is CellLabel.Text -> {
if (cellLabel.text.isNotBlank()) {
BpkText(
text = cellLabel.text,
modifier = Modifier
.padding(horizontal = BpkSpacing.Sm),
overflow = TextOverflow.Ellipsis,
textAlign = TextAlign.Center,
maxLines = 2,
style = BpkTheme.typography.caption,
color = labelColor(status, style),
)
}
}

is CellLabel.Icon -> {
cellLabel.resId.let { resId ->
BpkIcon.findBySmall(resId)?.let { bpkIcon ->
val iconTint = cellLabel.tint
?.let { colorRes -> ContextCompat.getColor(LocalContext.current, colorRes) }
?.let { Color(it) } ?: LocalContentColor.current
BpkIcon(
icon = bpkIcon,
tint = iconTint,
contentDescription = null,
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.fillMaxWidth()
.heightIn(min = BpkSpacing.Base),
) {
val animationDelay = BpkShimmerSize.Small.durationMillis * 2
AnimatedContent(
model.info.label,
label = "AnimatedContent ${model.date}",
contentAlignment = Alignment.Center,
transitionSpec = {
fadeIn(animationSpec = tween(200, delayMillis = animationDelay))
.togetherWith(
fadeOut(animationSpec = tween(200, delayMillis = animationDelay)),
)
},
modifier = Modifier.matchParentSize(),
) { label ->
when (label) {
is CellLabel.Text -> {
if (label.text.isNotBlank()) {
BpkText(
text = label.text,
modifier = Modifier,
overflow = TextOverflow.Ellipsis,
maxLines = 2,
textAlign = TextAlign.Center,
style = BpkTheme.typography.caption,
color = labelColor(status, style),
)
}
}

is CellLabel.Icon -> {
label.resId.let { resId ->
BpkIcon.findBySmall(resId)?.let { bpkIcon ->
val iconTint = label.tint
?.let { colorRes -> ContextCompat.getColor(LocalContext.current, colorRes) }
?.let { Color(it) } ?: LocalContentColor.current
BpkIcon(
icon = bpkIcon,
tint = iconTint,
contentDescription = null,
modifier = Modifier,
)
}
}
}

is CellLabel.Loading -> {
BpkShimmerOverlay(
shimmerSize = BpkShimmerSize.Small,
) {
val height = BpkSpacing.Md + BpkSpacing.Sm
BpkHeadlineSkeleton(
skeletonHeightSize = BpkSkeletonHeightSizeType.Custom,
modifier = Modifier
.semantics { contentDescription = label.contentDescription }
.size(width = BpkSpacing.Xl, height = height)
.align(Alignment.Center),
)
}
}
}
}
Expand Down
Loading