diff --git a/app/src/main/java/com/skyyo/samples/application/Destination.kt b/app/src/main/java/com/skyyo/samples/application/Destination.kt index 6fae31b5..a055470b 100644 --- a/app/src/main/java/com/skyyo/samples/application/Destination.kt +++ b/app/src/main/java/com/skyyo/samples/application/Destination.kt @@ -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) { diff --git a/app/src/main/java/com/skyyo/samples/application/activity/PopulatedNavHost.kt b/app/src/main/java/com/skyyo/samples/application/activity/PopulatedNavHost.kt index 1ad05803..335c6f75 100644 --- a/app/src/main/java/com/skyyo/samples/application/activity/PopulatedNavHost.kt +++ b/app/src/main/java/com/skyyo/samples/application/activity/PopulatedNavHost.kt @@ -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 @@ -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() } } \ No newline at end of file diff --git a/app/src/main/java/com/skyyo/samples/features/calendar/Calendar.kt b/app/src/main/java/com/skyyo/samples/features/calendar/Calendar.kt new file mode 100644 index 00000000..1f268353 --- /dev/null +++ b/app/src/main/java/com/skyyo/samples/features/calendar/Calendar.kt @@ -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 + ) + } +} diff --git a/app/src/main/java/com/skyyo/samples/features/calendar/CalendarState.kt b/app/src/main/java/com/skyyo/samples/features/calendar/CalendarState.kt new file mode 100644 index 00000000..6140eade --- /dev/null +++ b/app/src/main/java/com/skyyo/samples/features/calendar/CalendarState.kt @@ -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 = 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) + } +} diff --git a/app/src/main/java/com/skyyo/samples/features/calendar/day/DateUtil.kt b/app/src/main/java/com/skyyo/samples/features/calendar/day/DateUtil.kt new file mode 100644 index 00000000..682f5be8 --- /dev/null +++ b/app/src/main/java/com/skyyo/samples/features/calendar/day/DateUtil.kt @@ -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 { + 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) diff --git a/app/src/main/java/com/skyyo/samples/features/calendar/day/Day.kt b/app/src/main/java/com/skyyo/samples/features/calendar/day/Day.kt new file mode 100644 index 00000000..25ee1bac --- /dev/null +++ b/app/src/main/java/com/skyyo/samples/features/calendar/day/Day.kt @@ -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 +) diff --git a/app/src/main/java/com/skyyo/samples/features/calendar/day/DayItem.kt b/app/src/main/java/com/skyyo/samples/features/calendar/day/DayItem.kt new file mode 100644 index 00000000..f40f5c96 --- /dev/null +++ b/app/src/main/java/com/skyyo/samples/features/calendar/day/DayItem.kt @@ -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) + ) + } + } +} diff --git a/app/src/main/java/com/skyyo/samples/features/calendar/day/DayItemWeekView.kt b/app/src/main/java/com/skyyo/samples/features/calendar/day/DayItemWeekView.kt new file mode 100644 index 00000000..1ba6842d --- /dev/null +++ b/app/src/main/java/com/skyyo/samples/features/calendar/day/DayItemWeekView.kt @@ -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 + } + ) + } + } +} diff --git a/app/src/main/java/com/skyyo/samples/features/calendar/month/CalendarPager.kt b/app/src/main/java/com/skyyo/samples/features/calendar/month/CalendarPager.kt new file mode 100644 index 00000000..e1ca70d8 --- /dev/null +++ b/app/src/main/java/com/skyyo/samples/features/calendar/month/CalendarPager.kt @@ -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 + ) + } + } + } +} diff --git a/app/src/main/java/com/skyyo/samples/features/calendar/month/CalendarPagerState.kt b/app/src/main/java/com/skyyo/samples/features/calendar/month/CalendarPagerState.kt new file mode 100644 index 00000000..9c633466 --- /dev/null +++ b/app/src/main/java/com/skyyo/samples/features/calendar/month/CalendarPagerState.kt @@ -0,0 +1,113 @@ +package com.skyyo.samples.features.calendar.month + +import androidx.compose.runtime.* +import com.google.accompanist.pager.ExperimentalPagerApi +import com.google.accompanist.pager.PagerState +import com.skyyo.samples.features.calendar.CalendarState +import com.skyyo.samples.features.calendar.day.dec +import com.skyyo.samples.features.calendar.day.getWeeks +import com.skyyo.samples.features.calendar.day.inc +import com.skyyo.samples.features.calendar.week.MonthWeeks +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import java.time.YearMonth + +const val PAGE_COUNT = 3 +const val VISIBLE_PAGER_INDEX = 1 + +@OptIn(ExperimentalPagerApi::class, kotlinx.coroutines.ExperimentalCoroutinesApi::class) +@Stable +class CalendarPagerState( + coroutineScope: CoroutineScope, + private val calendarState: CalendarState, + private val pagerState: PagerState, + private val onVisibleMonthChanged: (YearMonth) -> Unit +) { + + private var previousMonth by mutableStateOf( + MonthWeeks(calendarState.visibleMonth.dec(), calendarState.visibleMonth.dec().getWeeks()) + ) + private var currentMonth by mutableStateOf( + MonthWeeks(calendarState.visibleMonth, calendarState.visibleMonth.getWeeks()) + ) + private var nextMonth by mutableStateOf( + MonthWeeks(calendarState.visibleMonth.inc(), calendarState.visibleMonth.inc().getWeeks()) + ) + + fun getMonthForIndex(index: Int) = when (index) { + 0 -> previousMonth + 1 -> currentMonth + 2 -> nextMonth + else -> throw IllegalArgumentException() + } + + fun getWeekForIndex(index: Int) = when (index) { + 0 -> when { + calendarState.visibleWeek > 0 -> currentMonth.weeks[calendarState.visibleWeek - 1] + else -> previousMonth.weeks.last() + } + 1 -> currentMonth.weeks[calendarState.visibleWeek] + 2 -> when { + calendarState.visibleWeek < currentMonth.weeks.size - 1 -> currentMonth.weeks[calendarState.visibleWeek + 1] + else -> nextMonth.weeks.first() + } + else -> throw IllegalArgumentException() + } + + init { + snapshotFlow { calendarState.visibleMonth }.onEach { month -> + moveToMonth(month) + }.launchIn(coroutineScope) + + snapshotFlow { if (!pagerState.isScrollInProgress) pagerState.currentPage else VISIBLE_PAGER_INDEX } + .distinctUntilChanged() + .onEach { newIndex -> + if (newIndex == VISIBLE_PAGER_INDEX || pagerState.isScrollInProgress) return@onEach + if (calendarState.isCompatView) { + onScrolled(newIndex) + } else { + val visibleMonth = getMonthForIndex(newIndex) + calendarState.visibleMonth = visibleMonth.month + onVisibleMonthChanged(visibleMonth.month) + } + pagerState.scrollToPage(if (pagerState.pageCount == 0) 0 else VISIBLE_PAGER_INDEX) + }.launchIn(coroutineScope) + } + + private fun moveToMonth(month: YearMonth) { + if (month == currentMonth.month) return + if (month > currentMonth.month) { + calendarState.visibleWeek = 0 + } else { + calendarState.visibleWeek = previousMonth.weeks.size - 1 + } + previousMonth = MonthWeeks(month.dec(), month.dec().getWeeks()) + currentMonth = MonthWeeks(month, month.getWeeks()) + nextMonth = MonthWeeks(month.inc(), month.inc().getWeeks()) + } + + private fun onScrolled(newIndex: Int) { + when (newIndex) { + 2 -> { + if (calendarState.visibleWeek < currentMonth.weeks.size - 1) { + calendarState.visibleWeek = calendarState.visibleWeek + 1 + } else { + onVisibleMonthChanged(nextMonth.month) + calendarState.visibleMonth = nextMonth.month + calendarState.visibleWeek = 0 + } + } + else -> { + if (calendarState.visibleWeek != 0) { + calendarState.visibleWeek = calendarState.visibleWeek - 1 + } else { + onVisibleMonthChanged(previousMonth.month) + calendarState.visibleMonth = previousMonth.month + calendarState.visibleWeek = previousMonth.weeks.size - 1 + } + } + } + } +} diff --git a/app/src/main/java/com/skyyo/samples/features/calendar/month/MonthContent.kt b/app/src/main/java/com/skyyo/samples/features/calendar/month/MonthContent.kt new file mode 100644 index 00000000..31a786ec --- /dev/null +++ b/app/src/main/java/com/skyyo/samples/features/calendar/month/MonthContent.kt @@ -0,0 +1,42 @@ +package com.skyyo.samples.features.calendar.month + +import androidx.compose.foundation.layout.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.google.accompanist.pager.ExperimentalPagerApi +import com.skyyo.samples.features.calendar.day.DayItem +import com.skyyo.samples.features.calendar.week.MonthWeeks +import java.time.LocalDate + +const val MAX_WEEKS = 6 + +@OptIn(ExperimentalPagerApi::class) +@Composable +fun MonthContent( + currentMonth: MonthWeeks, + selectedDate: LocalDate, + onDateClick: (LocalDate) -> Unit +) { + Column(modifier = Modifier.wrapContentWidth()) { + currentMonth.weeks.forEach { week -> + Row(modifier = Modifier.fillMaxWidth()) { + week.days.forEach { day -> + val isSelected = remember(selectedDate, day) { day.date == selectedDate } + + DayItem( + state = day, + onDateClick = onDateClick, + isSelected = isSelected, + modifier = Modifier.weight(1f) + ) + } + } + } + // imitate empty row for case when nex month have 6 weeks and bottom content will dragging + if (currentMonth.weeks.size != MAX_WEEKS) { + Spacer(modifier = Modifier.height(52.dp)) + } + } +} diff --git a/app/src/main/java/com/skyyo/samples/features/calendar/month/MonthHeader.kt b/app/src/main/java/com/skyyo/samples/features/calendar/month/MonthHeader.kt new file mode 100644 index 00000000..8fed910f --- /dev/null +++ b/app/src/main/java/com/skyyo/samples/features/calendar/month/MonthHeader.kt @@ -0,0 +1,63 @@ +package com.skyyo.samples.features.calendar.month + +import androidx.compose.foundation.layout.* +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.filled.ArrowForward +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import com.skyyo.samples.features.calendar.CalendarState +import com.skyyo.samples.features.calendar.day.dec +import com.skyyo.samples.features.calendar.day.inc +import java.time.YearMonth + +@Composable +fun MonthHeader( + calendarState: CalendarState, + modifier: Modifier = Modifier, + onVisibleMonthChanged: (YearMonth) -> Unit +) { + Row( + modifier = modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + IconButton( + onClick = { + calendarState.visibleMonth = calendarState.visibleMonth.dec() + onVisibleMonthChanged(calendarState.visibleMonth) + } + ) { + Icon( + Icons.Filled.ArrowBack, + contentDescription = null, + tint = MaterialTheme.colors.primary + ) + } + Text( + text = calendarState.visibleMonth.month.name.lowercase().replaceFirstChar { it.titlecase() }, + fontWeight = FontWeight.W600 + ) + Spacer(modifier = Modifier.width(8.dp)) + Text(text = calendarState.visibleMonth.year.toString(), fontWeight = FontWeight.W600) + IconButton( + onClick = { + calendarState.visibleMonth = calendarState.visibleMonth.inc() + onVisibleMonthChanged(calendarState.visibleMonth) + } + ) { + Icon( + Icons.Filled.ArrowForward, + contentDescription = null, + tint = MaterialTheme.colors.primary + ) + } + } +} diff --git a/app/src/main/java/com/skyyo/samples/features/calendar/preview/CalendarScreen.kt b/app/src/main/java/com/skyyo/samples/features/calendar/preview/CalendarScreen.kt new file mode 100644 index 00000000..1f35b12a --- /dev/null +++ b/app/src/main/java/com/skyyo/samples/features/calendar/preview/CalendarScreen.kt @@ -0,0 +1,43 @@ +package com.skyyo.samples.features.calendar.preview + +import androidx.compose.animation.animateContentSize +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.material.Button +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import com.google.accompanist.insets.ProvideWindowInsets +import com.google.accompanist.insets.statusBarsPadding +import com.skyyo.samples.features.calendar.Calendar +import com.skyyo.samples.features.calendar.rememberCalendarState + +@Composable +fun CalendarScreen(viewModel: CalendarViewModel = hiltViewModel()) { + val isWeekViewEnabled by viewModel.isWeekViewEnabled.collectAsState() + val calendarState = rememberCalendarState(isCompatView = isWeekViewEnabled) + + remember(isWeekViewEnabled) { + calendarState.isCompatView = isWeekViewEnabled + null + } + + ProvideWindowInsets { + Column( + Modifier + .statusBarsPadding() + .animateContentSize()) { + Calendar(calendarState = calendarState) + Spacer(modifier = Modifier.height(20.dp)) + Button(onClick = viewModel::toggleView) { + Text(text = "Toggle view") + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/skyyo/samples/features/calendar/preview/CalendarViewModel.kt b/app/src/main/java/com/skyyo/samples/features/calendar/preview/CalendarViewModel.kt new file mode 100644 index 00000000..0f9f5398 --- /dev/null +++ b/app/src/main/java/com/skyyo/samples/features/calendar/preview/CalendarViewModel.kt @@ -0,0 +1,21 @@ +package com.skyyo.samples.features.calendar.preview + +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.skyyo.samples.extensions.getStateFlow +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject + +@HiltViewModel +class CalendarViewModel @Inject constructor( + savedStateHandle: SavedStateHandle +) : ViewModel() { + + val isWeekViewEnabled = savedStateHandle.getStateFlow(viewModelScope, "isWeekViewEnabled", false) + + fun toggleView() { + isWeekViewEnabled.value = !isWeekViewEnabled.value + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/skyyo/samples/features/calendar/week/MonthWeeks.kt b/app/src/main/java/com/skyyo/samples/features/calendar/week/MonthWeeks.kt new file mode 100644 index 00000000..3b75fd5d --- /dev/null +++ b/app/src/main/java/com/skyyo/samples/features/calendar/week/MonthWeeks.kt @@ -0,0 +1,8 @@ +package com.skyyo.samples.features.calendar.week + +import java.time.YearMonth + +data class MonthWeeks( + val month: YearMonth, + val weeks: List +) diff --git a/app/src/main/java/com/skyyo/samples/features/calendar/week/Week.kt b/app/src/main/java/com/skyyo/samples/features/calendar/week/Week.kt new file mode 100644 index 00000000..01df6f13 --- /dev/null +++ b/app/src/main/java/com/skyyo/samples/features/calendar/week/Week.kt @@ -0,0 +1,8 @@ +package com.skyyo.samples.features.calendar.week + +import com.skyyo.samples.features.calendar.day.Day + +data class Week( + val isFirstWeekOfTheMonth: Boolean = false, + val days: List, +) diff --git a/app/src/main/java/com/skyyo/samples/features/calendar/week/WeekContent.kt b/app/src/main/java/com/skyyo/samples/features/calendar/week/WeekContent.kt new file mode 100644 index 00000000..13b9d40b --- /dev/null +++ b/app/src/main/java/com/skyyo/samples/features/calendar/week/WeekContent.kt @@ -0,0 +1,30 @@ +package com.skyyo.samples.features.calendar.week + +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import com.skyyo.samples.features.calendar.day.DayItemWeekView +import java.time.LocalDate + +@Composable +fun WeekContent( + week: Week, + selectedDate: LocalDate, + onDateClick: (LocalDate) -> Unit +) { + Row( + modifier = Modifier.fillMaxWidth(), + ) { + week.days.forEachIndexed { _, day -> + val isSelected = remember(selectedDate, day) { day.date == selectedDate } + DayItemWeekView( + state = day, + onDateClick = onDateClick, + isSelected = isSelected, + modifier = Modifier.weight(1f) + ) + } + } +} diff --git a/app/src/main/java/com/skyyo/samples/features/calendar/week/WeekHeader.kt b/app/src/main/java/com/skyyo/samples/features/calendar/week/WeekHeader.kt new file mode 100644 index 00000000..4c65b004 --- /dev/null +++ b/app/src/main/java/com/skyyo/samples/features/calendar/week/WeekHeader.kt @@ -0,0 +1,33 @@ +package com.skyyo.samples.features.calendar.week + +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.style.TextAlign +import java.time.DayOfWeek +import java.time.format.TextStyle +import java.util.* + +@Composable +fun WeekHeader( + daysOfWeek: List, + modifier: Modifier = Modifier, +) { + Row(modifier = modifier) { + daysOfWeek.forEach { dayOfWeek -> + Text( + textAlign = TextAlign.Center, + text = dayOfWeek.getDisplayName(TextStyle.SHORT, Locale.ROOT), + modifier = modifier + .weight(1f) + .wrapContentHeight(), + color = if (dayOfWeek == DayOfWeek.SUNDAY || dayOfWeek == DayOfWeek.SATURDAY) Color.Gray else Color.Unspecified + ) + } + } +} + +fun Array.rotateRight(n: Int): List = takeLast(n) + dropLast(n) diff --git a/app/src/main/java/com/skyyo/samples/features/sampleContainer/SampleContainerScreen.kt b/app/src/main/java/com/skyyo/samples/features/sampleContainer/SampleContainerScreen.kt index 6cdadf02..8c57c0ec 100644 --- a/app/src/main/java/com/skyyo/samples/features/sampleContainer/SampleContainerScreen.kt +++ b/app/src/main/java/com/skyyo/samples/features/sampleContainer/SampleContainerScreen.kt @@ -58,6 +58,9 @@ fun SampleContainerScreen(viewModel: SampleContainerViewModel = hiltViewModel()) Button(modifier = Modifier.fillMaxWidth(), onClick = viewModel::goPdfViewer) { Text(text = "pdf viewer") } + Button(modifier = Modifier.fillMaxWidth(), onClick = viewModel::goCalendar) { + Text(text = "calendar") + } } } diff --git a/app/src/main/java/com/skyyo/samples/features/sampleContainer/SampleContainerViewModel.kt b/app/src/main/java/com/skyyo/samples/features/sampleContainer/SampleContainerViewModel.kt index 05b393e9..6edd7da9 100644 --- a/app/src/main/java/com/skyyo/samples/features/sampleContainer/SampleContainerViewModel.kt +++ b/app/src/main/java/com/skyyo/samples/features/sampleContainer/SampleContainerViewModel.kt @@ -168,4 +168,8 @@ class SampleContainerViewModel @Inject constructor( fun goPdfViewer() = navigationDispatcher.emit { it.navigate(Destination.PdfViewer.route) } + + fun goCalendar() = navigationDispatcher.emit { + it.navigate(Destination.Calendar.route) + } }