Skip to content

Commit

Permalink
feat(explore/search): add between month gradient for ranges spanning …
Browse files Browse the repository at this point in the history
…months

Signed-off-by: Brandon McAnsh <[email protected]>
  • Loading branch information
bmc08gt committed Nov 21, 2023
1 parent f9c8bfa commit d28b86a
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ package com.airbnb.sample.data.explore

sealed interface SearchLocationOption {

val imageUrl: String?

companion object {
val choices = listOf(
Flexible,
Expand All @@ -18,26 +20,46 @@ sealed interface SearchLocationOption {
)
}

data object Flexible : SearchLocationOption
data object Flexible : SearchLocationOption {
override val imageUrl: String = "https://images.unsplash.com/photo-1613235795113-e2c223afc08c?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxfDB8MXxyYW5kb218MHx8d29ybGQtbWFwfHx8fHx8MTcwMDYwMDYyOA&ixlib=rb-4.0.3&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=1080"
}

data class Custom(val place: String) : SearchLocationOption
data class Custom(val place: String) : SearchLocationOption {
override val imageUrl: String? = null
}

sealed interface PhysicalPlace {
data object Europe : SearchLocationOption, PhysicalPlace
data object Canada : SearchLocationOption, PhysicalPlace
data object Caribbean : SearchLocationOption, PhysicalPlace
data object Italy : SearchLocationOption, PhysicalPlace
data object South_America : SearchLocationOption, PhysicalPlace
data object United_Kingdom : SearchLocationOption, PhysicalPlace
data object Mexico : SearchLocationOption, PhysicalPlace
data object Spain : SearchLocationOption, PhysicalPlace
data object Europe : SearchLocationOption, PhysicalPlace {
override val imageUrl: String = "https://images.unsplash.com/photo-1576185850227-1f72b7f8d483?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxfDB8MXxyYW5kb218MHx8ZXVyb3BlLW1hcHx8fHx8fDE3MDA2MDA3MTc&ixlib=rb-4.0.3&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=1080"
}
data object Canada : SearchLocationOption, PhysicalPlace {
override val imageUrl: String = "https://images.unsplash.com/photo-1578924825042-31d14cf13c35?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxfDB8MXxyYW5kb218MHx8Y2FuYWRhLW1hcHx8fHx8fDE3MDA2MDA3ODg&ixlib=rb-4.0.3&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=1080"
}
data object Caribbean : SearchLocationOption, PhysicalPlace {
override val imageUrl: String = "https://images.unsplash.com/photo-1625239622428-ba0ae330a1f9?q=80&w=2826&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
}
data object Italy : SearchLocationOption, PhysicalPlace {
override val imageUrl: String = "https://images.unsplash.com/photo-1553290322-0440fe3b1ddd?q=80&w=2670&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
}
data object South_America : SearchLocationOption, PhysicalPlace {
override val imageUrl: String = "https://images.unsplash.com/photo-1544906243-a69767cc000b?q=80&w=2670&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
}
data object United_Kingdom : SearchLocationOption, PhysicalPlace {
override val imageUrl: String = "https://images.unsplash.com/photo-1662578546948-a30badf89995?q=80&w=2672&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
}
data object Mexico : SearchLocationOption, PhysicalPlace {
override val imageUrl: String = "https://images.unsplash.com/photo-1553290322-0440fe3b1ddd?q=80&w=2670&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
}
data object Spain : SearchLocationOption, PhysicalPlace {
override val imageUrl: String = "https://images.unsplash.com/photo-1522072176817-41673f7f0ccc?q=80&w=2819&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
}

fun named() = this::class.simpleName.orEmpty().replace("_", " ")
}

fun displayLabel() = when (this) {
is SearchLocationOption.Custom -> place
is SearchLocationOption.PhysicalPlace -> named()
SearchLocationOption.Flexible -> "I'm flexible"
is Custom -> place
is PhysicalPlace -> named()
Flexible -> "I'm flexible"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.airbnb.sample.screens.explore.search.components
import androidx.compose.animation.Crossfade
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
Expand All @@ -22,13 +23,18 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.ColorMatrix
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import com.airbnb.sample.data.explore.SearchLocationOption
import com.airbnb.sample.screens.explore.experience.components.SearchBar
import com.airbnb.sample.theme.dimens
import com.airbnb.sample.theme.secondaryText
import com.airbnb.sample.utils.printed
import com.seiko.imageloader.rememberImagePainter

@Composable
internal fun LocationSelection(
Expand Down Expand Up @@ -100,10 +106,28 @@ internal fun LocationSelection(
modifier = Modifier
.fillParentMaxWidth(0.3f)
.aspectRatio(1f)
.clip(MaterialTheme.shapes.medium)
.clickable { onLocationOptionSelected(choice) }
.background(MaterialTheme.colorScheme.background)
.border(borderWidth, borderColor, MaterialTheme.shapes.medium)
)
) {
val imageUrl = choice.imageUrl
if (imageUrl != null) {
val painter =
rememberImagePainter(url = imageUrl)
Image(
modifier = Modifier.matchParentSize(),
painter = painter,
contentDescription = null,
contentScale = ContentScale.FillBounds,
colorFilter = ColorFilter.colorMatrix(
ColorMatrix().apply {
setToSaturation(0f)
}
)
)
}
}
Text(
text = choice.displayLabel(),
style = MaterialTheme.typography.bodySmall
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.airbnb.sample.ui.components

import androidx.compose.animation.animateColorAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
Expand All @@ -20,11 +22,13 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.RoundRect
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.CompositingStrategy
import androidx.compose.ui.graphics.Path
Expand All @@ -36,11 +40,14 @@ import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.dp
import com.airbnb.sample.theme.dimens
import com.airbnb.sample.theme.secondaryText
import com.airbnb.sample.utils.ui.addIf
import com.kizitonwose.calendar.compose.VerticalCalendar
import com.kizitonwose.calendar.compose.rememberCalendarState
import com.kizitonwose.calendar.core.CalendarDay
import com.kizitonwose.calendar.core.DayPosition
import com.kizitonwose.calendar.core.YearMonth
import com.kizitonwose.calendar.core.atEndOfMonth
import com.kizitonwose.calendar.core.atStartOfMonth
import com.kizitonwose.calendar.core.dayOfWeekRangeFrom
import com.kizitonwose.calendar.core.firstDayOfWeekFromLocale
import com.kizitonwose.calendar.core.lastDayOfWeekFromLocale
Expand Down Expand Up @@ -89,6 +96,13 @@ fun VerticalScrollingCalendar(
)
},
dayContent = { day ->
val startOfMonth = YearMonth.of(day.date)
.plusMonths(1)
.atStartOfMonth()
val endOfMonth = YearMonth.of(day.date)
.minusMonths(1)
.atEndOfMonth()

// only render days in the given month (no in or out dates)
if (day.position == DayPosition.MonthDate) {
val isTodayOrFuture = day.date.toEpochDays() >= today.toEpochDays()
Expand All @@ -114,6 +128,31 @@ fun VerticalScrollingCalendar(
color = if (isTodayOrFuture) textColor else MaterialTheme.colorScheme.secondaryText,
textDecoration = if (!isTodayOrFuture) TextDecoration.LineThrough else null
)
} else {
val range = if (selectedDates.count() > 1) {
selectedDates.first().rangeTo(selectedDates.last())
} else {
null
}

if (range != null && range.contains(day.date)) {
val month = YearMonth.of(day.date)
if (day.date.dayOfMonth == 1 || day.date.dayOfMonth == month.lengthOfMonth()) {
Text(
modifier = Modifier
.fillMaxSize()
.align(Alignment.Center)
.graphicsLayer {
compositingStrategy = CompositingStrategy.Offscreen
}
.drawCrossMonthGradient(day)
.padding(MaterialTheme.dimens.staticGrid.x3),
text = day.date.dayOfMonth.toString(), // not drawn in [drawCrossMonthGradient]
style = MaterialTheme.typography.labelSmall,
textAlign = TextAlign.Center,
)
}
}
}
}
)
Expand Down Expand Up @@ -153,6 +192,7 @@ private fun Modifier.drawSelectionsAndRange(

val shape = MaterialTheme.shapes.medium
val density = LocalDensity.current

this.drawBehind {
val range = if (selectedDates.count() > 1) {
selectedDates.first().rangeTo(selectedDates.last())
Expand Down Expand Up @@ -180,7 +220,7 @@ private fun Modifier.drawSelectionsAndRange(
RoundRect(
rect = Rect(
offset = Offset(xMax - curveSize + 1, yMin),
size = Size(curveSize, yMax + radiusDiff),
size = Size(curveSize, yMax + radiusDiff - 10f),
),
topLeft = cornerRadius,
bottomLeft = cornerRadius
Expand All @@ -197,7 +237,7 @@ private fun Modifier.drawSelectionsAndRange(
RoundRect(
rect = Rect(
offset = Offset(0f, yMin),
size = Size(curveSize, yMax + radiusDiff)
size = Size(curveSize, yMax + radiusDiff - 10f)
),
topRight = cornerRadius,
bottomRight = cornerRadius
Expand All @@ -217,7 +257,10 @@ private fun Modifier.drawSelectionsAndRange(
RoundRect(
rect = Rect(
offset = Offset(0f, 0f),
size = size,
size = Size(
width = size.width,
height = size.height - 10f
),
),
topLeft = cornerRadius,
bottomLeft = cornerRadius
Expand All @@ -227,14 +270,18 @@ private fun Modifier.drawSelectionsAndRange(

drawPath(path, backgroundColor)
}

lastDayOfWeekFromLocale() -> {
// end of week, round end
val path = Path().apply {
addRoundRect(
RoundRect(
rect = Rect(
offset = Offset(0f, 0f),
size = size,
size = Size(
width = size.width,
height = size.height - 10f
),
),
topRight = cornerRadius,
bottomRight = cornerRadius
Expand All @@ -244,8 +291,12 @@ private fun Modifier.drawSelectionsAndRange(

drawPath(path, backgroundColor)
}

else -> {
drawRect(backgroundColor)
drawRect(
color = backgroundColor,
size = Size(width = size.width, height = size.height - 10f),
)
}
}
}
Expand All @@ -256,4 +307,30 @@ private fun Modifier.drawSelectionsAndRange(
drawCircle(Color.Black)
}
}
}

private fun Modifier.drawCrossMonthGradient(day: CalendarDay) = composed {
this.drawWithContent {
if (day.position == DayPosition.OutDate) {
drawRect(
brush = Brush.horizontalGradient(
colors = listOf(
Color(0xFFF1F1F1),
Color.Transparent,
)
),
size = Size(width = size.width, height = size.height - 10f),
)
} else if (day.position == DayPosition.InDate) {
drawRect(
brush = Brush.horizontalGradient(
colors = listOf(
Color.Transparent,
Color(0xFFF1F1F1),
)
),
size = Size(width = size.width, height = size.height - 10f),
)
}
}
}

0 comments on commit d28b86a

Please sign in to comment.