Skip to content

Commit

Permalink
(Calendar) Setting the initial week #80
Browse files Browse the repository at this point in the history
  • Loading branch information
maxkeppeler committed Feb 4, 2024
1 parent 547fdc7 commit 9ea7c26
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package com.maxkeppeler.sheets.calendar.functional

import android.util.Range
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsNotEnabled
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.performClick
Expand All @@ -45,7 +46,7 @@ class CalendarViewTests {
val rule = createComposeRule()

@Test
fun calendarViewDateSelectionSuccess() {
fun givenCalendarView_whenDateSelected_thenDateSelectionSuccess() {
val testDate = LocalDate.now()
.withDayOfMonth(12)

Expand All @@ -65,7 +66,7 @@ class CalendarViewTests {
}

@Test
fun calendarViewDateSelectionInvalid() {
fun givenCalendarView_whenNoDateSelected_thenDateSelectionInvalid() {
rule.setContentAndWaitForIdle {
CalendarView(
useCaseState = UseCaseState(visible = true),
Expand All @@ -76,7 +77,7 @@ class CalendarViewTests {
}

@Test
fun calendarViewDatesSelectionSuccess() {
fun givenCalendarView_whenMultipleDatesSelected_thenDatesSelectionSuccess() {
val testDates = listOf(
LocalDate.now().withDayOfMonth(2),
LocalDate.now().withDayOfMonth(8),
Expand Down Expand Up @@ -104,7 +105,7 @@ class CalendarViewTests {
}

@Test
fun calendarViewDatesSelectionInvalid() {
fun givenCalendarView_whenNoDatesSelected_thenDatesSelectionInvalid() {
rule.setContentAndWaitForIdle {
CalendarView(
useCaseState = UseCaseState(visible = true),
Expand All @@ -115,7 +116,7 @@ class CalendarViewTests {
}

@Test
fun calendarViewPeriodSelectionSuccess() {
fun givenCalendarView_whenDateSelectedWithStyleMonthAndDisabledDates_thenDateSelectionStyleMonthConfigDatesDisabled() {
val testStartDate = LocalDate.now().withDayOfMonth(2)
val testEndDate = LocalDate.now().withDayOfMonth(12)

Expand Down Expand Up @@ -147,7 +148,7 @@ class CalendarViewTests {
}

@Test
fun calendarViewDateSelectionStyleMonthConfigDatesDisabled() {
fun givenCalendarView_whenMultipleDatesSelectedWithStyleMonthAndDisabledDates_thenDatesSelectionStyleMonthConfigDatesDisabled() {
val testDate = LocalDate.now().withDayOfMonth(15)
val newDates = listOf(
testDate.plusDays(2),
Expand Down Expand Up @@ -183,7 +184,7 @@ class CalendarViewTests {
}

@Test
fun calendarViewDatesSelectionStyleMonthConfigDatesDisabled() {
fun givenCalendarView_whenPeriodSelectedWithStyleMonthAndDisabledDates_thenPeriodSelectionStyleMonthConfigDatesDisabled() {
val testDate = LocalDate.now().withDayOfMonth(15)
val defaultDates = listOf(
testDate.minusDays(10),
Expand Down Expand Up @@ -224,7 +225,7 @@ class CalendarViewTests {


@Test
fun calendarViewPeriodSelectionStyleMonthConfigDatesDisabled() {
fun givenCalendarView_whenPeriodSelectedWithStyleMonthAndDisabledDatesAlt_thenPeriodSelectionStyleMonthConfigDatesDisabled() {
val testDate = LocalDate.now().withDayOfMonth(15)
val disabledDates = listOf(
testDate.minusDays(1),
Expand Down Expand Up @@ -262,7 +263,7 @@ class CalendarViewTests {
}

@Test
fun calendarViewPeriodSelectionInvalid() {
fun givenCalendarView_whenNoPeriodSelected_thenPeriodSelectionInvalid() {
rule.setContentAndWaitForIdle {
CalendarView(
useCaseState = UseCaseState(visible = true),
Expand All @@ -273,7 +274,7 @@ class CalendarViewTests {
}

@Test
fun calendarViewPeriodSelectionInvalidSelectEndDateBeforeStartDate() {
fun givenCalendarView_whenEndDateSelectedBeforeStartDate_thenPeriodSelectionInvalidSelectEndDateBeforeStartDate() {
val testStartDate = LocalDate.now().withDayOfMonth(12)
val testEndDate = LocalDate.now().withDayOfMonth(2)
rule.setContentAndWaitForIdle {
Expand All @@ -297,7 +298,7 @@ class CalendarViewTests {
}

@Test
fun calendarViewDisplaysCalendarStyleWeek() {
fun givenCalendarView_whenCalendarStyleWeek_thenCalendarViewDisplaysCalendarStyleWeek() {
rule.setContentAndWaitForIdle {
CalendarView(
useCaseState = UseCaseState(visible = true),
Expand All @@ -309,7 +310,7 @@ class CalendarViewTests {
}

@Test
fun calendarViewDisplaysCalendarStyleMonth() {
fun givenCalendarView_whenCalendarStyleMonth_thenCalendarViewDisplaysCalendarStyleMonth() {
rule.setContentAndWaitForIdle {
CalendarView(
useCaseState = UseCaseState(visible = true),
Expand All @@ -320,4 +321,123 @@ class CalendarViewTests {
rule.onPositiveButton().assertIsNotEnabled()
}


@Test
fun givenCalendarView_whenDateSelectedWithCameraDate_thenDisplayCorrectTime() {
val testDate = LocalDate.now().withDayOfMonth(15)
val testCameraDate = LocalDate.now().minusMonths(2)
rule.setContentAndWaitForIdle {
CalendarView(
useCaseState = UseCaseState(visible = true),
selection = CalendarSelection.Date(
selectedDate = testDate,
onSelectDate = { date -> }
),
config = CalendarConfig(
style = CalendarStyle.MONTH,
cameraDate = testCameraDate
)
)
}

rule.onNodeWithTags(
TestTags.CALENDAR_DATE_SELECTION,
testCameraDate.format(DateTimeFormatter.ISO_DATE)
).apply {
assertExists()
assertIsDisplayed()
}
}

@Test
fun givenCalendarView_whenDateSelectedWithCameraDateOutsideBoundary_thenDisplaySelectedTime() {
val testDate = LocalDate.now().withDayOfMonth(15)
val testBoundary = testDate.minusYears(2)..testDate.plusYears(2)
val testCameraDate = LocalDate.now().minusYears(4)
rule.setContentAndWaitForIdle {
CalendarView(
useCaseState = UseCaseState(visible = true),
selection = CalendarSelection.Date(
selectedDate = testDate,
onSelectDate = { date -> }
),
config = CalendarConfig(
boundary = testBoundary,
cameraDate = testCameraDate,
style = CalendarStyle.MONTH
)
)
}

rule.onNodeWithTags(
TestTags.CALENDAR_DATE_SELECTION,
testCameraDate.format(DateTimeFormatter.ISO_DATE)
).assertDoesNotExist()

rule.onNodeWithTags(
TestTags.CALENDAR_DATE_SELECTION,
testDate.format(DateTimeFormatter.ISO_DATE)
).apply {
assertExists()
assertIsDisplayed()
}
}


@Test
fun givenCalendarView_whenCameraDateOutsideBoundaryCurrentTimeInsideBoundary_thenDisplayCurrentTime() {
val testDate = LocalDate.now()
val testBoundary = testDate.minusYears(2)..testDate.plusYears(2)
val testCameraDate = LocalDate.now().minusYears(4)
rule.setContentAndWaitForIdle {
CalendarView(
useCaseState = UseCaseState(visible = true),
selection = CalendarSelection.Date(
onSelectDate = { date -> }
),
config = CalendarConfig(
boundary = testBoundary,
cameraDate = testCameraDate,
style = CalendarStyle.MONTH
)
)
}

rule.onNodeWithTags(
TestTags.CALENDAR_DATE_SELECTION,
testDate.format(DateTimeFormatter.ISO_DATE)
).apply {
assertExists()
assertIsDisplayed()
}
}

@Test
fun givenCalendarView_whenCameraDateOutsideBoundaryCurrentTimeOutsideBoundary_thenDisplayCurrentTime() {
val testDate = LocalDate.now()
val testBoundary = testDate.plusYears(2)..testDate.plusYears(4)
val testCameraDate = LocalDate.now().minusYears(4)
rule.setContentAndWaitForIdle {
CalendarView(
useCaseState = UseCaseState(visible = true),
selection = CalendarSelection.Date(
onSelectDate = { date -> }
),
config = CalendarConfig(
boundary = testBoundary,
cameraDate = testCameraDate,
style = CalendarStyle.MONTH
)
)
}

rule.onNodeWithTags(
TestTags.CALENDAR_DATE_SELECTION,
testBoundary.start.format(DateTimeFormatter.ISO_DATE)
).apply {
assertExists()
assertIsDisplayed()
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import com.maxkeppeler.sheets.calendar.utils.endOfMonth
import com.maxkeppeler.sheets.calendar.utils.endOfWeek
import com.maxkeppeler.sheets.calendar.utils.endValue
import com.maxkeppeler.sheets.calendar.utils.getInitialCameraDate
import com.maxkeppeler.sheets.calendar.utils.getInitialCustomCameraDate
import com.maxkeppeler.sheets.calendar.utils.jumpNext
import com.maxkeppeler.sheets.calendar.utils.jumpPrev
import com.maxkeppeler.sheets.calendar.utils.rangeValue
Expand Down Expand Up @@ -65,7 +66,9 @@ internal class CalendarState(
val today by mutableStateOf(LocalDate.now())
var mode by mutableStateOf(stateData?.mode ?: CalendarDisplayMode.CALENDAR)
var cameraDate by mutableStateOf(
stateData?.cameraDate ?: selection.getInitialCameraDate(config.boundary)
stateData?.cameraDate
?: getInitialCustomCameraDate(config.cameraDate, config.boundary)
?: getInitialCameraDate(selection, config.boundary)
)
var date = mutableStateOf(stateData?.date ?: selection.dateValue)
var dates = mutableStateListOf(*(stateData?.dates ?: selection.datesValue))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,13 @@ import com.maxkeppeker.sheets.core.models.base.BaseConfigs
import com.maxkeppeker.sheets.core.utils.BaseConstants.DEFAULT_ICON_STYLE
import com.maxkeppeler.sheets.calendar.utils.Constants
import java.time.LocalDate
import java.util.*
import java.util.Locale

/**
* The general configuration for the calendar dialog.
* @param locale The locale of the calendar.
* @param style The style of the calendar.
* @param cameraDate The date that is initially displayed when the calendar is opened.
* @param monthSelection Allow the direct selection of a month.
* @param yearSelection Allow the direct selection of a year.
* @param boundary The range of dates that are displayed.
Expand All @@ -35,6 +36,7 @@ import java.util.*
class CalendarConfig(
val locale: Locale = Locale.getDefault(),
val style: CalendarStyle = CalendarStyle.MONTH,
val cameraDate: LocalDate? = null,
val monthSelection: Boolean = Constants.DEFAULT_MONTH_SELECTION,
val yearSelection: Boolean = Constants.DEFAULT_YEAR_SELECTION,
val boundary: ClosedRange<LocalDate> = Constants.DEFAULT_RANGE,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,18 @@
package com.maxkeppeler.sheets.calendar.utils

import androidx.annotation.RestrictTo
import com.maxkeppeler.sheets.calendar.models.*
import com.maxkeppeler.sheets.calendar.models.CalendarConfig
import com.maxkeppeler.sheets.calendar.models.CalendarData
import com.maxkeppeler.sheets.calendar.models.CalendarDateData
import com.maxkeppeler.sheets.calendar.models.CalendarMonthData
import com.maxkeppeler.sheets.calendar.models.CalendarSelection
import com.maxkeppeler.sheets.calendar.models.CalendarStyle
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.*
import java.util.Locale

/**
* Returns the week of the week-based-year for this [LocalDate].
Expand Down Expand Up @@ -95,9 +100,11 @@ internal val LocalDate.previousWeek: LocalDate
get() = when {
dayOfMonth == Constants.FIRST_DAY_IN_MONTH
&& dayOfWeek != DayOfWeek.MONDAY -> with(DayOfWeek.MONDAY)

dayOfMonth >= Constants.DAYS_IN_WEEK ||
dayOfMonth == Constants.FIRST_DAY_IN_MONTH
&& dayOfWeek == DayOfWeek.MONDAY -> minusWeeks(1)

else -> withDayOfMonth(Constants.FIRST_DAY_IN_MONTH)
}

Expand Down Expand Up @@ -150,27 +157,34 @@ fun LocalDate.jumpNext(config: CalendarConfig): LocalDate = when (config.style)

/**
* Returns the initial date to be displayed on the CalendarView based on the selection mode.
*
* The initial camera date is calculated based on the selected mode. If the mode is [CalendarSelection.Date],
* the selected date is returned. If the mode is [CalendarSelection.Dates], the first selected date is returned.
* If the mode is [CalendarSelection.Period], the lower range of the selected period is returned.
*
* If the selected mode doesn't have a date, the current date will be returned as the initial camera date.
*
* @return The initial camera date.
* @param selection The selection mode.
* @param boundary The boundary of the calendar.
* @return The initial date to be displayed on the CalendarView.
*/
internal fun CalendarSelection.getInitialCameraDate(boundary: ClosedRange<LocalDate>): LocalDate {
val cameraDateBasedOnMode = when (this) {
is CalendarSelection.Date -> selectedDate
is CalendarSelection.Dates -> selectedDates?.firstOrNull()
is CalendarSelection.Period -> selectedRange?.lower
} ?: kotlin.run {
internal fun getInitialCameraDate(selection: CalendarSelection, boundary: ClosedRange<LocalDate>): LocalDate {
val cameraDateBasedOnMode = when (selection) {
is CalendarSelection.Date -> selection.selectedDate
is CalendarSelection.Dates -> selection.selectedDates?.firstOrNull()
is CalendarSelection.Period -> selection.selectedRange?.lower
} ?: run {
val now = LocalDate.now()
if (now in boundary) now else boundary.endInclusive
if (now in boundary) now else boundary.start
}
return cameraDateBasedOnMode.startOfWeekOrMonth
}

/**
* Returns the custom initial date in case the camera date is within the boundary. Otherwise, it returns null.
*
* @param cameraDate The initial camera date.
* @param boundary The boundary of the calendar.
* @return The initial camera date if it's within the boundary, otherwise null.
*/
internal fun getInitialCustomCameraDate(
cameraDate: LocalDate?,
boundary: ClosedRange<LocalDate>
): LocalDate? = cameraDate?.takeIf { it in boundary }?.startOfWeekOrMonth

/**
* Get selection value of date.
*/
Expand Down Expand Up @@ -300,6 +314,7 @@ internal fun calcCalendarDateData(
is CalendarSelection.Dates -> {
selectedDates?.contains(date) ?: false
}

is CalendarSelection.Period -> {
val selectedStart = selectedRange.first == date
selectedStartInit = selectedStart && selectedRange.second != null
Expand Down

0 comments on commit 9ea7c26

Please sign in to comment.