Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ sealed class Destination(val route: String) {
object DominantColor : Destination("dominantColor")
object Zoomable : Destination("zoomable")
object PdfViewer : Destination("pdfViewer")
object Calendar : Destination("calendar")
}

sealed class ProfileGraph(val route: String) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import com.skyyo.samples.features.autoscroll.AutoScrollScreen
import com.skyyo.samples.features.bottomSheets.BottomSheetScaffoldScreen
import com.skyyo.samples.features.bottomSheets.BottomSheetScreen
import com.skyyo.samples.features.bottomSheets.ModalBottomSheetScreen
import com.skyyo.samples.features.calendar.preview.CalendarScreen
import com.skyyo.samples.features.cameraX.CameraXScreen
import com.skyyo.samples.features.customView.CustomViewScreen
import com.skyyo.samples.features.dominantColor.DominantColorScreen
Expand Down Expand Up @@ -154,4 +155,5 @@ fun PopulatedNavHost(
composable(Destination.DominantColor.route) { DominantColorScreen() }
composable(Destination.Zoomable.route) { ZoomableScreen() }
composable(Destination.PdfViewer.route) { PdfViewerScreen() }
composable(Destination.Calendar.route) { CalendarScreen() }
}
37 changes: 37 additions & 0 deletions app/src/main/java/com/skyyo/samples/features/calendar/Calendar.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.skyyo.samples.features.calendar

import CalendarPager
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.skyyo.samples.features.calendar.month.MonthHeader
import java.time.DayOfWeek
import java.time.LocalDate
import java.time.YearMonth
import java.time.temporal.WeekFields
import java.util.*

@OptIn(ExperimentalStdlibApi::class)
@Composable
fun Calendar(
modifier: Modifier = Modifier,
firstDayOfWeek: DayOfWeek = WeekFields.of(Locale.getDefault()).firstDayOfWeek,
calendarState: CalendarState = rememberCalendarState(),
selectedDate: LocalDate = LocalDate.now(),
onDateClick: (LocalDate) -> Unit = {},
onVisibleMonthChanged: (YearMonth) -> Unit = {}
) {
Column(
modifier = modifier,
) {
MonthHeader(calendarState = calendarState, onVisibleMonthChanged = onVisibleMonthChanged)
CalendarPager(
firstDayOfWeek = firstDayOfWeek,
calendarState = calendarState,
modifier = modifier,
onDateClick = onDateClick,
selectedDate = selectedDate,
onVisibleMonthChanged = onVisibleMonthChanged
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.skyyo.samples.features.calendar

import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.listSaver
import androidx.compose.runtime.saveable.rememberSaveable
import java.time.YearMonth

@Stable
class CalendarState(
visibleMonth: YearMonth,
visibleWeek: Int,
isCompatView: Boolean
) {

private var _visibleMonth by mutableStateOf(visibleMonth)
private var _visibleWeek by mutableStateOf(visibleWeek)
private var _isCompatView by mutableStateOf(isCompatView)

var visibleMonth: YearMonth
get() = _visibleMonth
set(value) { _visibleMonth = value }
var visibleWeek: Int
get() = _visibleWeek
set(value) { _visibleWeek = value }
var isCompatView: Boolean
get() = _isCompatView
set(value) { _isCompatView = value }

companion object {
val Saver: Saver<CalendarState, *> = listSaver(
save = { listOf(it.visibleMonth.toString(), it.visibleWeek.toString(), it._isCompatView.toString()) },
restore = { CalendarState(YearMonth.parse(it[0]), it[1].toInt(), it[2].toBoolean()) }
)
}
}

@Composable
fun rememberCalendarState(
visibleMonth: YearMonth = YearMonth.now(),
visibleWeek: Int = 0,
isCompatView: Boolean = false
): CalendarState {
return rememberSaveable(saver = CalendarState.Saver) {
CalendarState(visibleMonth, visibleWeek, isCompatView)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.skyyo.samples.features.calendar.day

import com.skyyo.samples.features.calendar.week.Week
import java.time.DayOfWeek
import java.time.YearMonth
import java.time.temporal.WeekFields
import java.util.*

const val DAYS_IN_A_WEEK = 7

internal infix fun DayOfWeek.daysUntil(other: DayOfWeek) = (DAYS_IN_A_WEEK + (value - other.value)) % DAYS_IN_A_WEEK

fun YearMonth.getWeeks(
includeAdjacentMonths: Boolean = true
): List<Week> {
val daysLength = lengthOfMonth()
val firstDayOfTheWeek = WeekFields.of(Locale.getDefault()).firstDayOfWeek
val starOffset = atDay(1).dayOfWeek daysUntil firstDayOfTheWeek
val endOffset =
DAYS_IN_A_WEEK - (atDay(daysLength).dayOfWeek daysUntil firstDayOfTheWeek) - 1

return (1 - starOffset..daysLength + endOffset).chunked(DAYS_IN_A_WEEK).mapIndexed { index, days ->
Week(
isFirstWeekOfTheMonth = index == 0,
days = days.mapNotNull { dayOfMonth ->
val (date, isFromCurrentMonth) = when (dayOfMonth) {
in Int.MIN_VALUE..0 -> if (includeAdjacentMonths) {
val previousMonth = this.dec()
previousMonth.atDay(previousMonth.lengthOfMonth() + dayOfMonth) to false
} else {
return@mapNotNull null
}
in 1..daysLength -> atDay(dayOfMonth) to true
else -> if (includeAdjacentMonths) {
val nextMonth = this.inc()
nextMonth.atDay(dayOfMonth - daysLength) to false
} else {
return@mapNotNull null
}
}
Day(
date = date,
isFromCurrentMonth = isFromCurrentMonth,
false // TODO check date with list of event dates
)
}
)
}
}

operator fun YearMonth.dec(): YearMonth = this.minusMonths(1)

operator fun YearMonth.inc(): YearMonth = this.plusMonths(1)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.skyyo.samples.features.calendar.day

import java.time.LocalDate

data class Day(
val date: LocalDate,
val isFromCurrentMonth: Boolean,
val hasEvents: Boolean
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.skyyo.samples.features.calendar.day

import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
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.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import java.time.LocalDate

@Composable
fun DayItem(
state: Day,
isSelected: Boolean,
modifier: Modifier,
onDateClick: (LocalDate) -> Unit
) {
val date = remember(state.date) {
state.date
}
Box(
modifier = modifier
.aspectRatio(1f)
.padding(8.dp)
.clip(CircleShape)
.clickable {
onDateClick(date)
}
.then(
if (isSelected) {
Modifier
.shadow(5.dp, CircleShape)
.background(MaterialTheme.colors.primary, CircleShape)
} else {
Modifier
}
),
) {
Text(
text = date.dayOfMonth.toString(),
modifier = Modifier.align(Alignment.Center),
color = when {
isSelected -> Color.White
state.isFromCurrentMonth -> Color.Unspecified
else -> Color.Gray
}
)
if (state.hasEvents && !isSelected) {
Spacer(
modifier = Modifier
.size(6.dp)
.background(MaterialTheme.colors.primary, CircleShape)
.align(Alignment.BottomCenter)
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.skyyo.samples.features.calendar.day

import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
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.graphics.Color
import androidx.compose.ui.unit.dp
import java.time.LocalDate
import java.time.format.TextStyle
import java.util.*

@Composable
fun DayItemWeekView(
state: Day,
isSelected: Boolean,
modifier: Modifier,
onDateClick: (LocalDate) -> Unit
) {
val date = remember(state.date) { state.date }
Box(
modifier
.clip(RoundedCornerShape(8.dp))
.clickable {
onDateClick(date)
}
.then(
if (isSelected) {
Modifier
.background(MaterialTheme.colors.primary, RoundedCornerShape(8.dp))
} else {
Modifier
}
),
) {
Column(
modifier = Modifier.fillMaxWidth().padding(vertical = 12.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = date.dayOfWeek.getDisplayName(TextStyle.SHORT, Locale.ROOT),
color = when {
isSelected -> Color.White
state.isFromCurrentMonth -> Color.Unspecified
else -> Color.Gray
}
)
Text(
text = date.dayOfMonth.toString(),
color = when {
isSelected -> Color.White
state.isFromCurrentMonth -> Color.Unspecified
else -> Color.Gray
}
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@

import androidx.compose.foundation.gestures.LocalOverScrollConfiguration
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import com.google.accompanist.pager.ExperimentalPagerApi
import com.google.accompanist.pager.HorizontalPager
import com.google.accompanist.pager.rememberPagerState
import com.skyyo.samples.features.calendar.CalendarState
import com.skyyo.samples.features.calendar.day.DAYS_IN_A_WEEK
import com.skyyo.samples.features.calendar.month.CalendarPagerState
import com.skyyo.samples.features.calendar.month.MonthContent
import com.skyyo.samples.features.calendar.month.PAGE_COUNT
import com.skyyo.samples.features.calendar.month.VISIBLE_PAGER_INDEX
import com.skyyo.samples.features.calendar.week.WeekContent
import com.skyyo.samples.features.calendar.week.WeekHeader
import com.skyyo.samples.features.calendar.week.rotateRight
import java.time.DayOfWeek
import java.time.LocalDate
import java.time.YearMonth

@OptIn(ExperimentalPagerApi::class, androidx.compose.foundation.ExperimentalFoundationApi::class)
@Composable
fun CalendarPager(
calendarState: CalendarState,
firstDayOfWeek: DayOfWeek,
modifier: Modifier = Modifier,
selectedDate: LocalDate,
onDateClick: (LocalDate) -> Unit,
onVisibleMonthChanged: (YearMonth) -> Unit
) {
val pagerState = rememberPagerState(initialPage = VISIBLE_PAGER_INDEX, pageCount = PAGE_COUNT)
val coroutineScope = rememberCoroutineScope()
val daysOfWeek = remember(firstDayOfWeek) {
DayOfWeek.values().rotateRight(DAYS_IN_A_WEEK - firstDayOfWeek.ordinal)
}
val calendarPagerState = remember {
CalendarPagerState(
coroutineScope = coroutineScope,
calendarState = calendarState,
pagerState = pagerState,
onVisibleMonthChanged = onVisibleMonthChanged,
)
}
if (!calendarState.isCompatView) WeekHeader(daysOfWeek, modifier)
CompositionLocalProvider(LocalOverScrollConfiguration provides null) {
HorizontalPager(
state = pagerState,
verticalAlignment = Alignment.Top,
modifier = modifier
) { pageIndex ->
if (calendarState.isCompatView) {
WeekContent(
week = calendarPagerState.getWeekForIndex(pageIndex),
onDateClick = onDateClick,
selectedDate = selectedDate
)
} else {
MonthContent(
currentMonth = calendarPagerState.getMonthForIndex(pageIndex),
selectedDate = selectedDate,
onDateClick = onDateClick
)
}
}
}
}
Loading