Skip to content

Commit

Permalink
Merge branch 'main' into feature/landscape-support
Browse files Browse the repository at this point in the history
  • Loading branch information
maxkeppeler committed Feb 5, 2023
2 parents 6fbfc3f + 1e882bf commit ca7a396
Show file tree
Hide file tree
Showing 12 changed files with 175 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,13 @@ package com.maxkeppeler.sheets.calendar
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.core.util.toRange
import com.maxkeppeker.sheets.core.views.BaseTypeState
import com.maxkeppeler.sheets.calendar.models.*
import com.maxkeppeler.sheets.calendar.utils.*
import java.io.Serializable
import java.time.DayOfWeek
import java.time.LocalDate
import java.time.Month
import java.time.Period

/**
* Handles the calendar state.
Expand Down Expand Up @@ -81,13 +79,6 @@ internal class CalendarState(
}
null -> Unit
}
// val invalidRange = Period.between(
// config.boundary.toRange().lower,
// config.boundary.toRange().upper
// ).years < 1
// if (invalidRange) {
// throw IllegalStateException("Please correct your setup. Your boundary is too small. ${config.boundary}")
// }
}

private fun getInitYearsRange(): ClosedRange<Int> =
Expand Down Expand Up @@ -123,7 +114,8 @@ internal class CalendarState(
val isPastDisabled = config.disabledTimeline == CalendarTimeline.PAST
return when (config.style) {
CalendarStyle.MONTH -> {
val isPrevOutOfBoundary = prevCameraDate.isBefore(config.boundary.start)
val isPrevOutOfBoundary =
prevCameraDate.isBefore(config.boundary.start.startOfMonth)
val isInPast = cameraDate.year <= today.year
&& cameraDate.monthValue <= today.monthValue
(isInPast && isPastDisabled) || isPrevOutOfBoundary
Expand All @@ -144,7 +136,8 @@ internal class CalendarState(
val isFutureDisabled = config.disabledTimeline == CalendarTimeline.FUTURE
return when (config.style) {
CalendarStyle.MONTH -> {
val isNextOutOfBoundary = nextCameraDate.isAfter(config.boundary.endInclusive)
val isNextOutOfBoundary =
nextCameraDate.isAfter(config.boundary.endInclusive.endOfMonth)
val isInFuture = cameraDate.year >= today.year
&& cameraDate.monthValue >= today.monthValue
(isInFuture && isFutureDisabled) || isNextOutOfBoundary
Expand All @@ -159,6 +152,15 @@ internal class CalendarState(
}
}

val isMonthSelectionEnabled: Boolean
get() = monthsData.disabled.size < 11 // At least 2 months

val isYearSelectionEnabled: Boolean
get() {
val years = yearsRange.endInclusive.minus(yearsRange.start).plus(1)
return years > 1 // at least 2 years
}

val cells: Int
get() = when (mode) {
CalendarDisplayMode.CALENDAR -> DayOfWeek.values().size
Expand Down Expand Up @@ -196,7 +198,7 @@ internal class CalendarState(
}

fun onMonthClick(month: Month) {
cameraDate = cameraDate.withMonth(month.value).startOfWeek
cameraDate = cameraDate.withMonth(month.value).startOfWeekOrMonth
mode = CalendarDisplayMode.CALENDAR
refreshData()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,10 @@ fun CalendarView(
cameraDate = calendarState.cameraDate,
onPrev = calendarState::onPrevious,
onNext = calendarState::onNext,
onMonthClick = { calendarState.onMonthSelectionClick() },
onYearClick = { calendarState.onYearSelectionClick() },
monthSelectionEnabled = calendarState.isMonthSelectionEnabled,
onMonthClick = calendarState::onMonthSelectionClick,
yearSelectionEnabled = calendarState.isYearSelectionEnabled,
onYearClick = calendarState::onYearSelectionClick,
)
CalendarBaseSelectionComponent(
modifier = Modifier.wrapContentHeight(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import com.maxkeppeler.sheets.calendar.models.*
import java.time.DayOfWeek
import java.time.LocalDate
import java.time.Month
import java.time.temporal.TemporalAdjusters
import java.time.temporal.WeekFields
import java.util.*

Expand Down Expand Up @@ -63,6 +64,24 @@ internal val LocalDate.startOfWeekOrMonth: LocalDate
return result
}

/**
* Extension function that jumps to the first day of the month.
*
* @return [LocalDate] representing the first day of the month
*/
internal val LocalDate.startOfMonth: LocalDate
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
get() = withDayOfMonth(1)

/**
Extension function that jumps to the last day of the month.
@return [LocalDate] representing the last day of the month
*/
internal val LocalDate.endOfMonth: LocalDate
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
get() = with(TemporalAdjusters.lastDayOfMonth())

/**
* Get the first day of the previous week from the current date.
*
Expand Down Expand Up @@ -222,10 +241,12 @@ internal fun calcMonthData(
}

// Check that months are within the boundary
val boundaryFilteredMonths = timelineFilteredMonths.filter {
val cameraDateWithMonth = cameraDate.withMonth(it.value)
cameraDateWithMonth.withDayOfMonth(config.boundary.start.dayOfMonth) in config.boundary
|| cameraDateWithMonth.withDayOfMonth(config.boundary.endInclusive.dayOfMonth) in config.boundary
val boundaryFilteredMonths = timelineFilteredMonths.filter { month ->
val maxDayOfMonth = month.length(cameraDate.isLeapYear)
val startDay = minOf(config.boundary.start.dayOfMonth, maxDayOfMonth)
val endDay = minOf(config.boundary.endInclusive.dayOfMonth, maxDayOfMonth)
val cameraDateWithMonth = cameraDate.withMonth(month.value).withDayOfMonth(startDay)
cameraDateWithMonth in config.boundary || cameraDateWithMonth.withDayOfMonth(endDay) in config.boundary
}

return CalendarMonthData(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ internal fun CalendarTopComponent(
cameraDate: LocalDate,
onPrev: () -> Unit,
onNext: () -> Unit,
monthSelectionEnabled: Boolean,
yearSelectionEnabled: Boolean,
onMonthClick: () -> Unit,
onYearClick: () -> Unit,
) {
Expand Down Expand Up @@ -134,7 +136,7 @@ internal fun CalendarTopComponent(
) {
Row(
modifier = selectableContainerModifier
.clickable(config.monthSelection) {
.clickable(config.monthSelection && monthSelectionEnabled) {
if (config.monthSelection) {
chevronMonthAtEnd = !chevronMonthAtEnd
}
Expand All @@ -148,7 +150,7 @@ internal fun CalendarTopComponent(
style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.Bold),
textAlign = TextAlign.Center
)
if (config.monthSelection) {
if (config.monthSelection && monthSelectionEnabled) {
Icon(
modifier = Modifier.size(dimensionResource(RC.dimen.scd_size_150)),
painter = rememberAnimatedVectorPainter(chevronAVD, chevronMonthAtEnd),
Expand All @@ -160,7 +162,7 @@ internal fun CalendarTopComponent(

Row(
modifier = selectableContainerModifier
.clickable(config.yearSelection) {
.clickable(config.yearSelection && yearSelectionEnabled) {
if (config.yearSelection) {
chevronYearAtEnd = !chevronYearAtEnd
}
Expand All @@ -174,7 +176,7 @@ internal fun CalendarTopComponent(
style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.Bold),
textAlign = TextAlign.Center
)
if (config.yearSelection) {
if (config.yearSelection && yearSelectionEnabled) {
Icon(
modifier = Modifier.size(dimensionResource(RC.dimen.scd_size_150)),
painter = rememberAnimatedVectorPainter(chevronAVD, chevronYearAtEnd),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ internal fun MonthItemComponent(
thisMonth: Boolean = false,
disabled: Boolean = false,
selected: Boolean = false,
onMonthClick: (Month) -> Unit
onMonthClick: () -> Unit
) {
val textStyle =
when {
Expand All @@ -61,10 +61,10 @@ internal fun MonthItemComponent(
val baseModifier = Modifier
.wrapContentWidth()
.padding(dimensionResource(RC.dimen.scd_small_50))
.clickable(!disabled) { onMonthClick() }

val normalModifier = baseModifier
.clip(MaterialTheme.shapes.small)
.clickable(!disabled) { onMonthClick(month) }

val selectedModifier = normalModifier
.background(MaterialTheme.colorScheme.primary)
Expand All @@ -76,8 +76,9 @@ internal fun MonthItemComponent(

Column(
modifier = when {
thisMonth -> baseModifier
disabled -> normalModifier
selected -> selectedModifier
thisMonth -> baseModifier
else -> normalModifier
},
verticalArrangement = Arrangement.Center,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,30 @@
package com.maxkeppeler.sheets.calendar.views

import androidx.compose.foundation.lazy.grid.LazyGridScope
import androidx.compose.foundation.lazy.grid.items
import com.maxkeppeler.sheets.calendar.models.CalendarMonthData
import java.time.Month

/**
* The view that displays all relevant year information.
* @param monthRange The range of months.
* @param selectedMonth The month that is currently selected.
* @param monthsData The information for the months selection.
* @param onMonthClick The listener that is invoked when a month is selected.
*/
internal fun LazyGridScope.setupMonthSelectionView(
monthsData: CalendarMonthData,
onMonthClick: (Month) -> Unit,
) {
Month.values().forEach { month ->
items(Month.values()) { month ->
val selected = monthsData.selected == month
val disabled = monthsData.disabled.contains(month)
val thisMonth = monthsData.thisMonth == month
item {
MonthItemComponent(
month = month,
selected = selected,
disabled = disabled,
thisMonth = thisMonth,
onMonthClick = onMonthClick
)
}
MonthItemComponent(
month = month,
selected = selected,
disabled = disabled,
thisMonth = thisMonth,
onMonthClick = { onMonthClick(month) }
)
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ internal class ClockState(
var timeTextValues by mutableStateOf(getTimeInTextValues())
val keys by mutableStateOf(getInputKeys())
var disabledKeys by mutableStateOf(getCurrentDisabledKeys())
var valid by mutableStateOf(isValid())

private fun isValid(): Boolean = config.boundary?.let { time in it } ?: true

private fun isInit24HourFormat(): Boolean {
return config.is24HourFormat ?: DateFormat.is24HourFormat(context)
Expand Down Expand Up @@ -93,6 +96,11 @@ internal class ClockState(

private fun refreshTimeValue() {
time = getTimeOfTextValues()
checkValid()
}

private fun checkValid() {
valid = isValid()
}

fun onChange12HourFormatValue(newIsAm: Boolean) {
Expand Down
46 changes: 33 additions & 13 deletions clock/src/main/java/com/maxkeppeler/sheets/clock/ClockView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import com.maxkeppeler.sheets.clock.models.ClockSelection
import com.maxkeppeler.sheets.clock.views.KeyboardComponent
import com.maxkeppeler.sheets.clock.views.LandscapeTimeValueComponent
import com.maxkeppeler.sheets.clock.views.PortraitTimeValueComponent
import com.maxkeppeler.sheets.clock.views.TimeHintComponent
import com.maxkeppeler.sheets.core.R

/**
Expand Down Expand Up @@ -75,6 +76,13 @@ fun ClockView(
onGroupClick = clockState::onValueGroupClick,
onAm = clockState::onChange12HourFormatValue,
)
TimeHintComponent(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = dimensionResource(R.dimen.scd_normal_150)),
valid = clockState.valid,
boundary = config.boundary,
)
KeyboardComponent(
modifier = Modifier
.sizeIn(maxHeight = BaseConstants.KEYBOARD_HEIGHT_MAX)
Expand All @@ -89,19 +97,30 @@ fun ClockView(
)
},
contentLandscape = {
LandscapeTimeValueComponent(
modifier = Modifier
.wrapContentWidth()
.weight(1f, true),
verticalArrangement = Arrangement.Center,
unitValues = clockState.timeTextValues,
isAm = clockState.isAm,
is24hourFormat = clockState.is24HourFormat,
valueIndex = clockState.valueIndex.value,
groupIndex = clockState.groupIndex.value,
onGroupClick = clockState::onValueGroupClick,
onAm = clockState::onChange12HourFormatValue,
)
Column(
Modifier
.weight(1f)
.weight(1f, true)
) {
LandscapeTimeValueComponent(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.Center,
unitValues = clockState.timeTextValues,
isAm = clockState.isAm,
is24hourFormat = clockState.is24HourFormat,
valueIndex = clockState.valueIndex.value,
groupIndex = clockState.groupIndex.value,
onGroupClick = clockState::onValueGroupClick,
onAm = clockState::onChange12HourFormatValue,
)
TimeHintComponent(
modifier = Modifier
.padding(top = 16.dp)
.fillMaxWidth(),
valid = clockState.valid,
boundary = config.boundary,
)
}
Spacer(modifier = Modifier.width(16.dp))
KeyboardComponent(
modifier = Modifier
Expand All @@ -122,6 +141,7 @@ fun ClockView(
selection = selection,
onNegative = { selection.onNegativeClick?.invoke() },
onPositive = clockState::onFinish,
onPositiveValid = clockState.valid,
onClose = sheetState::finish
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@ import java.time.LocalTime
/**
* The general configuration for the clock dialog.
* @param defaultTime The default time.
* @param boundary Optional [ClosedRange] of [LocalTime] representing the time boundary
* @param is24HourFormat If the 24HourFormat is enabled.
* @param icons The style of icons that are used for dialog/ view-specific icons.
* @param orientation The orientation of the view or null for auto orientation.
*/
data class ClockConfig(
val defaultTime: LocalTime? = null,
val boundary: ClosedRange<LocalTime>? = null,
val is24HourFormat: Boolean? = null,
override val icons: LibIcons = BaseConstants.DEFAULT_ICON_STYLE,
override val orientation: LibOrientation? = BaseConstants.DEFAULT_LIB_LAYOUT,
Expand Down
Loading

0 comments on commit ca7a396

Please sign in to comment.