diff --git a/app/src/main/java/ru/megboyzz/dnevnik/Utils.kt b/app/src/main/java/ru/megboyzz/dnevnik/Utils.kt index 6395b4e..da6d396 100644 --- a/app/src/main/java/ru/megboyzz/dnevnik/Utils.kt +++ b/app/src/main/java/ru/megboyzz/dnevnik/Utils.kt @@ -24,10 +24,13 @@ import androidx.compose.ui.unit.dp import androidx.navigation.NavController import com.kizitonwose.calendar.compose.CalendarLayoutInfo import com.kizitonwose.calendar.compose.CalendarState -import com.kizitonwose.calendar.core.CalendarMonth +import com.kizitonwose.calendar.compose.weekcalendar.WeekCalendarLayoutInfo +import com.kizitonwose.calendar.compose.weekcalendar.WeekCalendarState +import com.kizitonwose.calendar.core.* import kotlinx.coroutines.flow.filterNotNull import ru.megboyzz.dnevnik.navigation.BaseNavRote import ru.megboyzz.dnevnik.ui.theme.dark +import java.time.DayOfWeek @Composable fun Int.AsPainter() = painterResource(this) @@ -134,6 +137,21 @@ fun rememberFirstMostVisibleMonth( } +@Composable +fun rememberFirstMostVisibleWeek( + state: WeekCalendarState, + viewportPercent: Float = 50f, +): Week { + val visibleMonth = remember(state) { mutableStateOf(state.firstVisibleWeek) } + LaunchedEffect(state) { + snapshotFlow { state.layoutInfo.firstMostVisibleWeek(viewportPercent) } + .filterNotNull() + .collect { month -> visibleMonth.value = month } + } + return visibleMonth.value +} + + private fun CalendarLayoutInfo.firstMostVisibleMonth(viewportPercent: Float = 50f): CalendarMonth? { return if (visibleMonthsInfo.isEmpty()) { null @@ -149,3 +167,45 @@ private fun CalendarLayoutInfo.firstMostVisibleMonth(viewportPercent: Float = 50 } } + +private fun WeekCalendarLayoutInfo.firstMostVisibleWeek(viewportPercent: Float = 50f): Week? { + return if (visibleWeeksInfo.isEmpty()) { + null + } else { + val viewportSize = (viewportEndOffset + viewportStartOffset) * viewportPercent / 100f + visibleWeeksInfo.firstOrNull { itemInfo -> + if (itemInfo.offset < 0) { + itemInfo.offset + itemInfo.size >= viewportSize + } else { + itemInfo.size - itemInfo.offset >= viewportSize + } + }?.week + } +} + +fun CalendarDay.week(): Week{ + val weekList = mutableListOf() + val dayOrder = this.date.dayOfWeek.ordinal + var mondayLocalDate = this.date.minusDays(dayOrder.toLong()) + + DayOfWeek.values().forEach { + weekList.add(WeekDay(mondayLocalDate, WeekDayPosition.RangeDate)) + mondayLocalDate = mondayLocalDate.plusDays(1) + } + return Week(weekList) + +} + +fun WeekDay.week(): Week{ + val weekList = mutableListOf() + val dayOrder = this.date.dayOfWeek.ordinal + var mondayLocalDate = this.date.minusDays(dayOrder.toLong()) + + DayOfWeek.values().forEach { + weekList.add(WeekDay(mondayLocalDate, WeekDayPosition.RangeDate)) + mondayLocalDate = mondayLocalDate.plusDays(1) + } + return Week(weekList) + +} + diff --git a/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/calendar/Calendar.kt b/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/calendar/Calendar.kt index 19b1c6e..f5f7a16 100644 --- a/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/calendar/Calendar.kt +++ b/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/calendar/Calendar.kt @@ -1,6 +1,5 @@ package ru.megboyzz.dnevnik.screens.ui.calendar -import android.util.Log import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape @@ -12,7 +11,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.semantics.Role import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp @@ -28,6 +26,7 @@ import ru.megboyzz.dnevnik.ui.theme.* import java.time.DayOfWeek import java.time.LocalDate import java.time.Month +import java.time.MonthDay import java.time.YearMonth import java.time.format.TextStyle import java.util.* @@ -39,8 +38,9 @@ fun NiceCalendar( onClick: (day: CalendarDay, month: YearMonth, year: Int) -> Unit ) { - val selections = remember { mutableStateListOf() } + val selections = remember { mutableStateListOf() } val daysOfWeek = remember { daysOfWeek() } + var transitionEnabled by remember { mutableStateOf(true) } val state = rememberCalendarState( startMonth = startMonth, @@ -56,8 +56,11 @@ fun NiceCalendar( firstDayOfWeek = daysOfWeek.first() ) - var selectedDate by remember { mutableStateOf(null) } - val coroutineScope = rememberCoroutineScope() + var selectedWeek by remember { mutableStateOf(null) } + + val monthScrollCoroutineScope = rememberCoroutineScope() + val weekScrollCoroutineScope = rememberCoroutineScope() + val visibleMonth = rememberFirstMostVisibleMonth(state, viewportPercent = 90f) Column { @@ -65,28 +68,49 @@ fun NiceCalendar( SimpleCalendarTitle( currentMonth = visibleMonth.yearMonth, goToPrevious = { - coroutineScope.launch { + monthScrollCoroutineScope.launch { state.animateScrollToMonth(state.firstVisibleMonth.yearMonth.previousMonth) } }, goToNext = { - coroutineScope.launch { + monthScrollCoroutineScope.launch { state.animateScrollToMonth(state.firstVisibleMonth.yearMonth.nextMonth) } - } + }, + transitionEnabled = transitionEnabled ) Card( shape = RoundedCornerShape(10.dp), modifier = Modifier.defaultMinSize(300.dp) ) { - if(selectedDate == null) { + if(selectedWeek == null) { + transitionEnabled = true HorizontalCalendar( state = state, dayContent = { day -> - Day(day, isSelected = selectedDate == day.date) { day -> - selectedDate = if (selectedDate == day.date) null else day.date - onClick(day, currentMonth, currentMonth.year) + + MonthDay( + day = day, + isSelected = selections.contains(day.date) + ) { clickedDay -> + + //Чтобы два раза не вызывать week() + val week = clickedDay.week() + + if(!selections.contains(day.date)) { + selections.removeIf { it != day.date } + selections.add(day.date) + }else selections.remove(day.date) + + weekScrollCoroutineScope.launch { + weekState.startDate = clickedDay.date + weekState.animateScrollToWeek(clickedDay.date) + } + + selectedWeek = if (selectedWeek == week) null else week + + onClick(clickedDay, currentMonth, currentMonth.year) } }, monthHeader = { DaysOfWeekTitle(daysOfWeek = DayOfWeek.values().asList()) }, @@ -94,19 +118,20 @@ fun NiceCalendar( ) } else { - //TODO по возможности устранить повторы + transitionEnabled = false WeekCalendar( state = weekState, weekHeader = { _ -> DaysOfWeekTitle(daysOfWeek = DayOfWeek.values().asList()) }, dayContent = { day -> - Day( - CalendarDay(day.date, DayPosition.InDate), - isSelected = selectedDate == day.date + WeekDay( + day = CalendarDay(day.date, DayPosition.InDate), + isSelected = selections.contains(day.date) ) { calendarDay -> - selectedDate = if (selectedDate == calendarDay.date) null else calendarDay.date - onClick(calendarDay, currentMonth, currentMonth.year) + + selectedWeek = null + } }, modifier = Modifier.padding(10.dp) @@ -122,12 +147,13 @@ fun CalendarNavigationIcon( icon: Painter, contentDescription: String, onClick: () -> Unit, + enabled: Boolean = true ) = Box( modifier = Modifier .size(35.dp) .aspectRatio(1f) .clip(shape = CircleShape) - .clickable(role = Role.Button, onClick = onClick), + .clickable(role = Role.Button, onClick = onClick, enabled = enabled), ) { Icon( modifier = Modifier @@ -146,6 +172,7 @@ fun SimpleCalendarTitle( currentMonth: YearMonth, goToPrevious: () -> Unit, goToNext: () -> Unit, + transitionEnabled: Boolean = true ) { val listOfMonth = listOf( @@ -164,7 +191,9 @@ fun SimpleCalendarTitle( ) Box( - modifier = Modifier.fillMaxWidth().padding(10.dp), + modifier = Modifier + .fillMaxWidth() + .padding(10.dp), contentAlignment = Alignment.Center, ) { Row( @@ -178,6 +207,7 @@ fun SimpleCalendarTitle( icon = R.drawable.ic_month_swipe_left.AsPainter(), contentDescription = "Previous", onClick = goToPrevious, + enabled = transitionEnabled ) Text( text = currentMonth.displayText(listOfMonth), @@ -188,6 +218,7 @@ fun SimpleCalendarTitle( icon = R.drawable.ic_month_swipe_right.AsPainter(), contentDescription = "Next", onClick = goToNext, + enabled = transitionEnabled ) } } @@ -230,30 +261,57 @@ fun DaysOfWeekTitle(daysOfWeek: List) { ) } } + +@Composable +fun MonthDay( + day: CalendarDay, + isSelected: Boolean, + onClick: (CalendarDay) -> Unit +) { + Day(day = day, isSelected = isSelected, onClick = onClick) +} + +@Composable +fun WeekDay( + day: CalendarDay, + isSelected: Boolean, + onClick: (CalendarDay) -> Unit +) { + val newDay = day.copy(position = DayPosition.MonthDate) + Day(day = newDay, isSelected = isSelected, onClick = onClick) +} + @Composable fun Day(day: CalendarDay, isSelected: Boolean, onClick: (CalendarDay) -> Unit) { Box( modifier = Modifier .aspectRatio(ratio = 1f) // This is important for square sizing! - .clip(CircleShape) - .background(color = if (isSelected) lightGray else Color.Transparent) - .border( - border = BorderStroke( - width = 1.dp, - color = if (isSelected) mainBlue else Color.Transparent - ), - shape = CircleShape - ) .clickable( enabled = day.position == DayPosition.MonthDate, onClick = { onClick(day) } ), contentAlignment = Alignment.Center ) { - Text( - text = day.date.dayOfMonth.toString(), - style = MainText, - color = if (day.position == DayPosition.MonthDate) dark else Color.Gray - ) + Box( + modifier = Modifier + .size(30.dp) + .clip(CircleShape) + .background(color = if (isSelected) lightGray else Color.Transparent) + .border( + border = BorderStroke( + width = 1.dp, + color = if (isSelected) mainBlue else Color.Transparent + ), + shape = CircleShape + ), + contentAlignment = Alignment.Center + ) { + Text( + text = day.date.dayOfMonth.toString(), + style = MainText, + textAlign = TextAlign.Justify, + color = if (day.position == DayPosition.MonthDate) dark else Color.Gray, + ) + } } } diff --git a/app/src/test/java/ru/megboyzz/dnevnik/UtilsTest.kt b/app/src/test/java/ru/megboyzz/dnevnik/UtilsTest.kt index 0ebc180..fb4f960 100644 --- a/app/src/test/java/ru/megboyzz/dnevnik/UtilsTest.kt +++ b/app/src/test/java/ru/megboyzz/dnevnik/UtilsTest.kt @@ -1,11 +1,6 @@ package ru.megboyzz.dnevnik -import org.junit.Test - -import org.junit.Assert.* -import ru.megboyzz.dnevnik.screens.ui.Month -import java.time.DayOfWeek - class UtilsTest { + } \ No newline at end of file