Skip to content

Commit

Permalink
OGM-2609 Add icon as label support to calendar cell info (#2038)
Browse files Browse the repository at this point in the history
* [Android] Add Icon as label support to Calendar CellInfo

* Fixed broken test

* Updated Component README.md

* Generating doc screenshots

* Fixed broken unit-test

* Address feedback

* Address icon as label screenshot generation test case

* Enhance cell info label API

* Fixed broken unit test

* Updated snapshots

* Made Icon resId non-nullable

* Address feedback

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
ernestkamara and github-actions[bot] authored Jul 10, 2024
1 parent 37b2d35 commit f9dd53d
Show file tree
Hide file tree
Showing 19 changed files with 207 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@

package net.skyscanner.backpack.calendar2.list

import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.core.view.isVisible
import net.skyscanner.backpack.R
import net.skyscanner.backpack.calendar2.CellLabel
import net.skyscanner.backpack.calendar2.CellStatusStyle
import net.skyscanner.backpack.calendar2.data.CalendarCell
import net.skyscanner.backpack.calendar2.data.CalendarInteraction
Expand All @@ -37,7 +40,8 @@ internal class CalendarCellDayHolder(
) : ItemHolder<CalendarCell.Day>(parent, R.layout.view_bpk_calendar_day) {

private val day = findViewById<TextView>(R.id.bpk_calendar_cell_date)
private val label = findViewById<TextView>(R.id.bpk_calendar_cell_label)
private val label = findViewById<TextView>(R.id.bpk_calendar_cell_label_text)
private val icon = findViewById<ImageView>(R.id.bpk_calendar_cell_label_icon)

private val selectionBackground = CalendarDaySelectionBackground(context)

Expand All @@ -58,8 +62,14 @@ internal class CalendarCellDayHolder(
view.isSelected = model.selection != null

day.text = model.text
label.text = model.info.label

when (val cellLabel = model.info.label) {
is CellLabel.Text -> label.text = cellLabel.text
is CellLabel.Icon -> {
icon.setImageResource(cellLabel.resId)
cellLabel.tint?.let { tint -> icon.imageTintList = context.getColorStateList(tint) }
}
}
when {
model.selection != null -> {
day.setTextColor(selectionContentColor(model.selection))
Expand All @@ -78,14 +88,22 @@ internal class CalendarCellDayHolder(
when {
model.inactive -> {
label.isVisible = false
icon.visibility = View.GONE
}
model.info.style == CellStatusStyle.Label -> {
label.isVisible = !model.info.label.isNullOrEmpty()
label.setTextColor(labelColor(model.info.status))
when (val cellLabel = model.info.label) {
is CellLabel.Text -> {
label.isVisible = cellLabel.text.isNotEmpty()
label.setTextColor(labelColor(model.info.status))
}
is CellLabel.Icon -> icon.visibility = View.VISIBLE
}
}
else -> {
label.isVisible = !model.info.label.isNullOrEmpty()
label.setTextColor(labelColor(null))
when (val cellLabel = model.info.label) {
is CellLabel.Text -> label.isVisible = cellLabel.text.isNotEmpty()
is CellLabel.Icon -> icon.visibility = View.VISIBLE
}
}
}
}
Expand Down
25 changes: 20 additions & 5 deletions Backpack/src/main/res/layout/view_bpk_calendar_day.xml
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal|top"
android:orientation="vertical"
android:paddingBottom="@dimen/bpkSpacingLg">

<net.skyscanner.backpack.text.BpkText
Expand All @@ -16,11 +15,14 @@
android:gravity="center"
android:maxLines="1"
android:singleLine="true"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:textStyle="heading5"
tools:text="11" />

<net.skyscanner.backpack.text.BpkText
android:id="@+id/bpk_calendar_cell_label"
android:id="@+id/bpk_calendar_cell_label_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
Expand All @@ -29,7 +31,20 @@
android:paddingStart="@dimen/bpkSpacingSm"
android:paddingEnd="@dimen/bpkSpacingSm"
android:singleLine="false"
app:layout_constraintTop_toBottomOf="@id/bpk_calendar_cell_date"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:textStyle="caption"
tools:text="£29" />

</LinearLayout>
<ImageView
android:id="@+id/bpk_calendar_cell_label_icon"
android:layout_width="wrap_content"
android:layout_height="14dp"
android:visibility="gone"
tools:src="@drawable/bpk_search"
tools:ignore="ContentDescription"
app:layout_constraintTop_toBottomOf="@id/bpk_calendar_cell_date"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
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 @@ -77,6 +77,12 @@ fun CalendarDayLabels(modifier: Modifier = Modifier) =
fun CalendarPreSelectedRange(modifier: Modifier = Modifier) =
CalendarDemo(CalendarStoryType.PreselectedRange, modifier)

@Composable
@Calendar2Component
@ComposeStory("Day icon as labels")
fun CalendarSelectionIconLabelStory(modifier: Modifier = Modifier) =
CalendarDemo(CalendarStoryType.WithIconAsLabels, modifier)

@Composable
private fun CalendarDemo(
type: CalendarStoryType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@

package net.skyscanner.backpack.demo.data

import net.skyscanner.backpack.R
import kotlin.math.roundToInt
import net.skyscanner.backpack.calendar2.CalendarParams
import net.skyscanner.backpack.calendar2.CalendarSelection
import net.skyscanner.backpack.calendar2.CellInfo
import net.skyscanner.backpack.calendar2.CellLabel
import net.skyscanner.backpack.calendar2.CellStatus
import net.skyscanner.backpack.calendar2.CellStatusStyle
import net.skyscanner.backpack.calendar2.extension.toIterable
Expand All @@ -41,6 +43,7 @@ enum class CalendarStoryType {
SelectionWholeMonth,
WithDisabledDates,
WithLabels,
WithIconAsLabels,
PreselectedRange,
;

Expand Down Expand Up @@ -103,8 +106,8 @@ enum class CalendarStoryType {
val price = it.dayOfMonth % maxPrice
CellInfo(
label = when (price) {
in minPrice..noPriceThreshold -> "-"
else -> "£${(it.dayOfMonth * 2.35f).roundToInt()}"
in minPrice..noPriceThreshold -> CellLabel.Text(text = "-")
else -> CellLabel.Text("£${(it.dayOfMonth * 2.35f).roundToInt()}")
},
status = when (price) {
noPriceThreshold -> null
Expand All @@ -124,6 +127,36 @@ enum class CalendarStoryType {
range = range,
selectionMode = CalendarParams.SelectionMode.Range(),
)

WithIconAsLabels ->
CalendarParams(
now = now,
range = range,
selectionMode = rangeSelectionModeWithAccessibilityLabels(),
cellsInfo = range
.toIterable()
.associateWith {
val price = it.dayOfMonth % maxPrice
CellInfo(
label = when (price) {
in minPrice..noPriceThreshold -> CellLabel.Icon(
resId = R.drawable.bpk_search_sm,
tint = R.color.bpkCoreAccent,
)
else -> CellLabel.Text("£${(it.dayOfMonth * 2.35f).roundToInt()}")
},
status = when (price) {
noPriceThreshold -> null
emptyPriceThreshold -> CellStatus.Empty
positivePriceThreshold -> CellStatus.Positive
neutralPriceThreshold -> CellStatus.Neutral
negativePriceThreshold -> CellStatus.Negative
else -> CellStatus.Empty
},
style = CellStatusStyle.Label,
)
},
)
}

private fun rangeSelectionModeWithAccessibilityLabels() = CalendarParams.SelectionMode.Range(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@ fun CalendarDayLabels(modifier: Modifier = Modifier) =
fun CalendarPreSelectedRange(modifier: Modifier = Modifier) =
Calendar2Demo(CalendarStoryType.PreselectedRange, modifier)

@Composable
@Calendar2Component
@ViewStory("Day icon as labels")
fun CalendarSelectionIconLabelStory(modifier: Modifier = Modifier) =
Calendar2Demo(CalendarStoryType.WithIconAsLabels, modifier)

@Composable
private fun Calendar2Demo(
type: CalendarStoryType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

package net.skyscanner.backpack.calendar2

import net.skyscanner.backpack.R
import net.skyscanner.backpack.calendar2.extension.toIterable
import java.time.DayOfWeek
import java.time.LocalDate
Expand Down Expand Up @@ -49,15 +50,20 @@ object BpkCalendarTestCases {
val Labeled = DefaultRange.copy(
cellsInfo = mapOf(
LocalDate.of(initialStartDate.year, initialStartDate.month, initialStartDate.dayOfMonth + 1) to
CellInfo(style = CellStatusStyle.Label, label = "£10", status = CellStatus.Negative),
CellInfo(style = CellStatusStyle.Label, label = CellLabel.Text("£10"), status = CellStatus.Negative),
LocalDate.of(initialStartDate.year, initialStartDate.month, initialStartDate.dayOfMonth + 2) to
CellInfo(style = CellStatusStyle.Label, label = "£11", status = CellStatus.Neutral),
CellInfo(style = CellStatusStyle.Label, label = CellLabel.Text("£11"), status = CellStatus.Neutral),
LocalDate.of(initialStartDate.year, initialStartDate.month, initialStartDate.dayOfMonth + 3) to
CellInfo(style = CellStatusStyle.Label, label = "£12", status = CellStatus.Positive),
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 = "£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 = "£900000", status = CellStatus.Positive),
CellInfo(style = CellStatusStyle.Label, label = CellLabel.Text("£900000"), status = CellStatus.Positive),
LocalDate.of(initialStartDate.year, initialStartDate.month, initialStartDate.dayOfMonth + 6) to
CellInfo(
style = CellStatusStyle.Label,
label = CellLabel.Icon(resId = R.drawable.bpk_search_sm, tint = R.color.bpkCoreAccent),
),
),
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package net.skyscanner.backpack.calendar2.data

import net.skyscanner.backpack.calendar2.CalendarSettings
import net.skyscanner.backpack.calendar2.CellInfo
import net.skyscanner.backpack.calendar2.CellLabel
import net.skyscanner.backpack.calendar2.CellStatus
import net.skyscanner.backpack.calendar2.CellStatusStyle
import net.skyscanner.backpack.calendar2.extension.toIterable
Expand Down Expand Up @@ -53,7 +54,7 @@ class CalendarInfoTests {
fun if_date_with_label_cell_has_correct_state() {
val params = CalendarSettings.Default.copy(
cellsInfo = CalendarSettings.Default.range.toIterable().associateWith {
CellInfo(label = it.dayOfMonth.toString())
CellInfo(label = CellLabel.Text(it.dayOfMonth.toString()))
},
)

Expand All @@ -62,7 +63,27 @@ class CalendarInfoTests {
for (i in 0..<state.cells.size) {
val cell = state.cells[i]
if (cell is CalendarCell.Day) {
assertEquals(cell.date.dayOfMonth.toString(), cell.info.label)
assertEquals(cell.date.dayOfMonth.toString(), (cell.info.label as CellLabel.Text).text)
}
}
}
}
}

@Test
fun if_date_with_icon_label_cell_has_correct_state() {
val params = CalendarSettings.Default.copy(
cellsInfo = CalendarSettings.Default.range.toIterable().associateWith {
CellInfo(label = CellLabel.Icon(resId = 1, tint = 2))
},
)

testCalendarWith(params) {
verify {
for (i in 0..<state.cells.size) {
val cell = state.cells[i]
if (cell is CalendarCell.Day) {
assertEquals(1, (cell.info.label as CellLabel.Icon).resId)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ data class CalendarParams(
data class CellInfo(
val disabled: Boolean = false,
val status: CellStatus? = null,
val label: String? = null,
val label: CellLabel = CellLabel.Text(""), // Default is empty text
val style: CellStatusStyle = CellStatusStyle.Label,
) {

Expand All @@ -145,6 +145,17 @@ data class CellInfo(
}
}

/**
* Describes the label of the cell
*/
sealed class CellLabel {
data class Text(val text: String) : CellLabel()
data class Icon(
val resId: Int,
val tint: Int? = null,
) : CellLabel()
}

/**
* Describes the colouring of the cell
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,23 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.selected
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.stateDescription
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import net.skyscanner.backpack.calendar2.CellLabel
import net.skyscanner.backpack.calendar2.CellStatus
import net.skyscanner.backpack.calendar2.CellStatusStyle
import net.skyscanner.backpack.calendar2.data.CalendarCell
import net.skyscanner.backpack.calendar2.data.CalendarCell.Selection
import net.skyscanner.backpack.compose.LocalContentColor
import net.skyscanner.backpack.compose.icon.BpkIcon
import net.skyscanner.backpack.compose.icon.findBySmall
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 @@ -118,17 +124,37 @@ internal fun BpkCalendarDayCell(
)
}

if (!inactive && !label.isNullOrEmpty()) {
BpkText(
text = label,
modifier = Modifier
.padding(horizontal = BpkSpacing.Sm),
overflow = TextOverflow.Ellipsis,
textAlign = TextAlign.Center,
maxLines = 2,
style = BpkTheme.typography.caption,
color = labelColor(status, style),
)
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,
)
}
}
}
}
}
}
}
Expand Down
Loading

0 comments on commit f9dd53d

Please sign in to comment.