Почти живой календарь

This commit is contained in:
2023-04-04 01:09:26 +03:00
parent 97551f0332
commit 39caedaab0
3 changed files with 154 additions and 41 deletions
+61 -1
View File
@@ -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<WeekDay>()
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<WeekDay>()
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)
}
@@ -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<CalendarDay>() }
val selections = remember { mutableStateListOf<LocalDate>() }
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<LocalDate?>(null) }
val coroutineScope = rememberCoroutineScope()
var selectedWeek by remember { mutableStateOf<Week?>(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<DayOfWeek>) {
)
}
}
@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,
)
}
}
}
@@ -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 {
}