Изменённый дизайн.
This commit is contained in:
2025-03-20 07:52:37 +04:00
parent 96f84b9f54
commit dbc1afcf28
15 changed files with 442 additions and 254 deletions

View File

@@ -2,9 +2,8 @@ package ru.n08i40k.polytechnic.next.ui.screen.schedule
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
@@ -24,6 +23,7 @@ import ru.n08i40k.polytechnic.next.ui.model.GroupUiState
import ru.n08i40k.polytechnic.next.ui.model.GroupViewModel
import ru.n08i40k.polytechnic.next.ui.widgets.LoadingContent
import ru.n08i40k.polytechnic.next.ui.widgets.schedule.SchedulePager
import ru.n08i40k.polytechnic.next.ui.widgets.schedule.UpdateInfo
import ru.n08i40k.polytechnic.next.utils.rememberUpdatedLifecycleOwner
@Composable
@@ -62,11 +62,10 @@ fun GroupScheduleScreen(viewModel: GroupViewModel) {
) {
when (uiState) {
is GroupUiState.HasData -> {
Column {
Column(Modifier.padding(20.dp), Arrangement.spacedBy(10.dp)) {
val data = uiState as GroupUiState.HasData
UpdateInfo(data.lastUpdateAt, data.cacheDate)
Spacer(Modifier.height(10.dp))
SchedulePager(data.group)
}
}

View File

@@ -2,9 +2,8 @@ package ru.n08i40k.polytechnic.next.ui.screen.schedule
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
@@ -24,6 +23,7 @@ import ru.n08i40k.polytechnic.next.ui.model.TeacherUiState
import ru.n08i40k.polytechnic.next.ui.model.TeacherViewModel
import ru.n08i40k.polytechnic.next.ui.widgets.LoadingContent
import ru.n08i40k.polytechnic.next.ui.widgets.schedule.SchedulePager
import ru.n08i40k.polytechnic.next.ui.widgets.schedule.UpdateInfo
import ru.n08i40k.polytechnic.next.utils.rememberUpdatedLifecycleOwner
@Composable
@@ -62,11 +62,10 @@ fun TeacherScheduleScreen(viewModel: TeacherViewModel) {
) {
when (uiState) {
is TeacherUiState.HasData -> {
Column {
Column(Modifier.padding(20.dp), Arrangement.spacedBy(10.dp)) {
val data = uiState as TeacherUiState.HasData
UpdateInfo(data.lastUpdateAt, data.cacheDate)
Spacer(Modifier.height(10.dp))
SchedulePager(data.teacher)
}
}

View File

@@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
@@ -28,6 +29,7 @@ import ru.n08i40k.polytechnic.next.ui.model.SearchUiState
import ru.n08i40k.polytechnic.next.ui.model.SearchViewModel
import ru.n08i40k.polytechnic.next.ui.widgets.LoadingContent
import ru.n08i40k.polytechnic.next.ui.widgets.schedule.SchedulePager
import ru.n08i40k.polytechnic.next.ui.widgets.schedule.UpdateInfo
import ru.n08i40k.polytechnic.next.ui.widgets.selector.TeacherNameSelector
import ru.n08i40k.polytechnic.next.utils.rememberUpdatedLifecycleOwner
@@ -82,11 +84,10 @@ fun TeacherSearchScreen(viewModel: SearchViewModel) {
when (uiState) {
is SearchUiState.HasData -> {
Column {
Column(Modifier.padding(20.dp), Arrangement.spacedBy(10.dp)) {
val data = uiState as SearchUiState.HasData
UpdateInfo(data.lastUpdateAt, data.cacheDate)
Spacer(Modifier.height(10.dp))
SchedulePager(data.teacher)
}
}

View File

@@ -10,7 +10,6 @@ import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -32,17 +31,12 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@Composable
fun ExpandableCard(
modifier: Modifier = Modifier,
colors: CardColors = CardDefaults.cardColors(),
border: BorderStroke = BorderStroke(
Dp.Hairline,
MaterialTheme.colorScheme.inverseSurface
),
expanded: Boolean = false,
onExpandedChange: () -> Unit,
title: @Composable () -> Unit,
@@ -61,8 +55,7 @@ fun ExpandableCard(
onExpandedChange()
transitionState.targetState = expanded
},
colors = colors,
border = border
colors = colors
) {
Column {
ExpandableCardHeader(title, transition)
@@ -76,15 +69,10 @@ fun ExpandableCard(
modifier: Modifier = Modifier,
title: @Composable () -> Unit,
colors: CardColors = CardDefaults.cardColors(),
border: BorderStroke = BorderStroke(
Dp.Hairline,
MaterialTheme.colorScheme.inverseSurface
),
) {
Card(
modifier = modifier,
colors = colors,
border = border
colors = colors
) {
ExpandableCardHeader(title, null)
}

View File

@@ -1,19 +1,23 @@
package ru.n08i40k.polytechnic.next.ui.widgets.schedule
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeContent
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
@@ -21,9 +25,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import kotlinx.coroutines.delay
@@ -33,8 +35,8 @@ import kotlinx.datetime.LocalDateTime
import ru.n08i40k.polytechnic.next.R
import ru.n08i40k.polytechnic.next.model.Day
import ru.n08i40k.polytechnic.next.model.Lesson
import ru.n08i40k.polytechnic.next.model.LessonType
import ru.n08i40k.polytechnic.next.repository.schedule.impl.MockScheduleRepository
import ru.n08i40k.polytechnic.next.ui.theme.AppTheme
import ru.n08i40k.polytechnic.next.utils.dateTime
import ru.n08i40k.polytechnic.next.utils.now
@@ -75,82 +77,42 @@ private fun getCurrentLessonIdx(day: Day?): Flow<Int> {
return value
}
@Preview(showBackground = true)
@PreviewLightDark
@Composable
private fun DayCardPreview() {
AppTheme {
Surface(
Modifier
.fillMaxSize()
.windowInsetsPadding(WindowInsets.safeContent.only(WindowInsetsSides.Top))
) {
DayCard(Modifier, MockScheduleRepository.exampleTeacher.days[0]) {}
}
}
}
@Composable
fun DayCard(
modifier: Modifier = Modifier,
day: Day = MockScheduleRepository.exampleTeacher.days[0],
onLessonClick: (Lesson) -> Unit = {},
day: Day,
onLessonClick: (Lesson) -> Unit,
) {
val offset = remember(day) { getDayOffset(day) }
val defaultCardColors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.secondaryContainer,
contentColor = MaterialTheme.colorScheme.onSecondaryContainer,
)
val customCardColors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.tertiaryContainer,
contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
)
val noneCardColors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
)
val examCardColors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.errorContainer,
contentColor = MaterialTheme.colorScheme.onErrorContainer,
)
Card(
modifier,
colors = CardDefaults.cardColors(
containerColor = when (offset) {
DayOffset.TODAY -> MaterialTheme.colorScheme.primaryContainer
else -> MaterialTheme.colorScheme.secondaryContainer
}
),
border = BorderStroke(1.dp, MaterialTheme.colorScheme.inverseSurface)
) {
Text(
day.name,
Modifier.fillMaxWidth(),
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center,
style = MaterialTheme.typography.titleLarge
containerColor = MaterialTheme.colorScheme.surfaceContainerLowest
)
day.street?.let {
Text(
it,
Modifier.fillMaxWidth(),
textAlign = TextAlign.Center,
)
}
if (offset != DayOffset.OTHER) {
Text(
stringResource(
when (offset) {
DayOffset.YESTERDAY -> R.string.yesterday
DayOffset.TODAY -> R.string.today
DayOffset.TOMORROW -> R.string.tomorrow
DayOffset.OTHER -> throw RuntimeException()
}
),
Modifier.fillMaxWidth(),
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center,
style = MaterialTheme.typography.bodyMedium,
)
}
) {
val currentLessonIndex by getCurrentLessonIdx(if (offset == DayOffset.TODAY) day else null)
.collectAsStateWithLifecycle(0)
Column(
Modifier
.fillMaxWidth()
.verticalScroll(rememberScrollState()),
.verticalScroll(rememberScrollState())
.padding(10.dp),
Arrangement.spacedBy(0.5.dp)
) {
if (day.lessons.isEmpty()) {
@@ -159,36 +121,10 @@ fun DayCard(
}
for (lessonIndex in day.lessons.indices) {
HorizontalDivider(
thickness = 1.dp,
color = MaterialTheme.colorScheme.inversePrimary
)
val lesson = day.lessons[lessonIndex]
val cardColors = when (lesson.type) {
LessonType.DEFAULT -> defaultCardColors
LessonType.ADDITIONAL -> noneCardColors
LessonType.BREAK -> noneCardColors
LessonType.CONSULTATION -> customCardColors
LessonType.INDEPENDENT_WORK -> customCardColors
LessonType.EXAM -> examCardColors
LessonType.EXAM_WITH_GRADE -> examCardColors
LessonType.EXAM_DEFAULT -> examCardColors
}
Box(
Modifier
.clickable { onLessonClick(lesson) }
.background(cardColors.containerColor)
) {
val modifier =
if (lessonIndex == currentLessonIndex)
Modifier.border(BorderStroke(1.dp, MaterialTheme.colorScheme.error))
else
Modifier
LessonRow(modifier, lesson, cardColors)
Box(Modifier.clickable { onLessonClick(lesson) }) {
LessonRow(modifier, lesson, lessonIndex == currentLessonIndex)
}
}
}

View File

@@ -1,34 +1,47 @@
package ru.n08i40k.polytechnic.next.ui.widgets.schedule
import androidx.annotation.DrawableRes
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeContent
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.text.BasicText
import androidx.compose.material3.CardColors
import androidx.compose.material3.CardDefaults
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.VerticalDivider
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.unit.dp
import ru.n08i40k.polytechnic.next.R
import ru.n08i40k.polytechnic.next.model.Lesson
import ru.n08i40k.polytechnic.next.model.LessonType
import ru.n08i40k.polytechnic.next.repository.schedule.impl.MockScheduleRepository
import ru.n08i40k.polytechnic.next.ui.theme.AppTheme
import ru.n08i40k.polytechnic.next.utils.dayMinutes
import ru.n08i40k.polytechnic.next.utils.fmtAsClock
@@ -42,31 +55,51 @@ private fun fmtTime(start: Int, end: Int, format: TimeFormat): ArrayList<String>
return when (format) {
TimeFormat.CLOCK -> arrayListOf(start.fmtAsClock(), end.fmtAsClock())
TimeFormat.DURATION -> arrayListOf(
"${end - start} ${stringResource(R.string.minutes)}"
"${end - start} ${stringResource(R.string.minutes_full)}"
)
}
}
@Preview(showBackground = true, showSystemUi = true)
@PreviewLightDark
@Composable
private fun LessonRowPreview() {
AppTheme {
Surface(
Modifier
.fillMaxSize()
.windowInsetsPadding(WindowInsets.safeContent.only(WindowInsetsSides.Top))
) {
LessonRow(Modifier, MockScheduleRepository.exampleGroup.days[0].lessons[6], true)
}
}
}
@Composable
fun LessonRow(
modifier: Modifier = Modifier,
lesson: Lesson = MockScheduleRepository.exampleGroup.days[0].lessons[0],
colors: CardColors = CardDefaults.cardColors()
lesson: Lesson,
current: Boolean
) {
val verticalPadding = when (lesson.type) {
LessonType.BREAK -> 2.5.dp
else -> 5.dp
}
var time = fmtTime(
lesson.time.start.dayMinutes,
lesson.time.end.dayMinutes,
if (lesson.type == LessonType.BREAK) TimeFormat.DURATION else TimeFormat.CLOCK
)
val timeFormat = when (lesson.type) {
LessonType.BREAK -> TimeFormat.DURATION
else -> TimeFormat.CLOCK
}
if (lesson.type == LessonType.BREAK) {
Box(Modifier.fillMaxWidth(), Alignment.Center) {
HorizontalDivider(color = MaterialTheme.colorScheme.surfaceContainerHighest)
Text(
time[0],
Modifier
.background(MaterialTheme.colorScheme.surfaceContainerLowest)
.padding(5.dp, 0.dp),
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.surfaceContainerHighest,
)
}
val contentColor = when (lesson.type) {
LessonType.BREAK -> colors.disabledContentColor
else -> colors.contentColor
return
}
// магические вычисления))
@@ -78,120 +111,184 @@ fun LessonRow(
Box(modifier) {
Row(
Modifier.padding(10.dp, verticalPadding * rangeSize),
verticalAlignment = Alignment.CenterVertically,
Modifier.padding(10.dp, 5.dp * rangeSize),
Arrangement.spacedBy(15.dp),
Alignment.CenterVertically,
) {
Text(
when (range) {
null -> " "
else -> {
if (range[0] == range[1])
" ${range[0]} "
else
"${range[0]}-${range[1]}"
}
},
fontFamily = FontFamily.Monospace,
fontWeight = FontWeight.Bold,
color = contentColor
)
Spacer(Modifier.width(5.dp))
val textMeasurer = rememberTextMeasurer()
val timeWidth = textMeasurer.measure(
text = "00:00 ",
text = "00:00",
style = LocalTextStyle.current.copy(fontFamily = FontFamily.Monospace)
)
Column(
Modifier.width(with(LocalDensity.current) { timeWidth.size.width.toDp() }),
horizontalAlignment = Alignment.CenterHorizontally
Modifier
.width(with(LocalDensity.current) { timeWidth.size.width.toDp() + 1.dp }),
Arrangement.spacedBy(5.dp),
Alignment.CenterHorizontally
) {
var time = fmtTime(
lesson.time.start.dayMinutes,
lesson.time.end.dayMinutes,
timeFormat
)
Text(time[0], color = contentColor, fontFamily = FontFamily.Monospace, maxLines = 1)
if (lesson.type != LessonType.BREAK)
Column {
Text(
time[0],
fontFamily = FontFamily.Monospace,
fontWeight = FontWeight.W600,
style = MaterialTheme.typography.titleSmall
)
Text(
time[1],
color = contentColor,
fontFamily = FontFamily.Monospace,
maxLines = 1
fontWeight = FontWeight.W600,
style = MaterialTheme.typography.titleSmall
)
}
}
Spacer(Modifier.width(5.dp))
Row(Modifier.fillMaxWidth(), Arrangement.SpaceBetween, Alignment.CenterVertically) {
Column(Modifier.weight(1f)) {
// FIXME: Очень странный метод отсеивания, может что-нибудь на замену сделать?
if (lesson.type.value > LessonType.BREAK.value) {
Text(
when (lesson.type) {
LessonType.CONSULTATION -> stringResource(R.string.lesson_type_consultation)
LessonType.INDEPENDENT_WORK -> stringResource(R.string.lesson_type_independent_work)
LessonType.EXAM -> stringResource(R.string.lesson_type_exam)
LessonType.EXAM_WITH_GRADE -> stringResource(R.string.lesson_type_exam_with_grade)
LessonType.EXAM_DEFAULT -> stringResource(R.string.lesson_type_exam_default)
else -> throw RuntimeException("Unknown lesson type!")
},
fontWeight = FontWeight.Bold,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
color = contentColor
)
}
if (range != null) {
HorizontalDivider(
Modifier.width(32.dp),
1.dp,
MaterialTheme.colorScheme.inverseSurface
)
Text(
lesson.name ?: stringResource(R.string.lesson_type_break),
fontWeight = FontWeight.Medium,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
color = contentColor
if (range[0] == range[1])
" ${range[0]} "
else
"${range[0]}-${range[1]}",
fontFamily = FontFamily.Monospace,
fontWeight = FontWeight.Bold,
style = MaterialTheme.typography.bodySmall
)
if (lesson.group != null) {
Text(
lesson.group,
color = contentColor,
fontWeight = FontWeight.Medium,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
for (subGroup in lesson.subGroups) {
Text(
subGroup.teacher,
color = contentColor,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
Column(Modifier.wrapContentWidth()) {
if (lesson.subGroups.size != 1) {
BasicText("")
if (lesson.group != null)
BasicText("")
}
for (subGroup in lesson.subGroups) {
Text(
subGroup.cabinet,
color = contentColor,
maxLines = 1,
fontFamily = FontFamily.Monospace
)
}
}
}
VerticalDivider(
Modifier.height(42.dp),
1.dp,
if (current)
MaterialTheme.colorScheme.primary
else
MaterialTheme.colorScheme.inverseSurface
)
Column(Modifier.weight(1f)) {
Text(
lesson.name!!,
fontWeight = FontWeight.W600,
style = MaterialTheme.typography.titleMedium
)
// FIXME: Очень странный метод отсеивания, может что-нибудь на замену сделать?
if (lesson.type.value > LessonType.BREAK.value) {
Text(
when (lesson.type) {
LessonType.CONSULTATION -> stringResource(R.string.lesson_type_consultation)
LessonType.INDEPENDENT_WORK -> stringResource(R.string.lesson_type_independent_work)
LessonType.EXAM -> stringResource(R.string.lesson_type_exam)
LessonType.EXAM_WITH_GRADE -> stringResource(R.string.lesson_type_exam_with_grade)
LessonType.EXAM_DEFAULT -> stringResource(R.string.lesson_type_exam_default)
else -> throw RuntimeException("Unknown lesson type!")
},
maxLines = 1,
overflow = TextOverflow.Ellipsis,
fontWeight = FontWeight.W600,
style = MaterialTheme.typography.titleMedium
)
}
@Composable
fun SmallTextWithIcon(
@DrawableRes iconId: Int,
contentDescription: String,
text: String
) {
Row(
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
painterResource(iconId),
contentDescription,
Modifier.size(12.dp)
)
Text(
text,
fontWeight = FontWeight.W400,
style = MaterialTheme.typography.titleSmall
)
}
}
if (lesson.group != null) {
Row {
Spacer(Modifier.size(5.dp))
SmallTextWithIcon(
R.drawable.ic_group,
"Group",
lesson.group
)
}
}
Row(
Modifier
.fillMaxWidth()
.padding(5.dp),
Arrangement.SpaceBetween,
Alignment.CenterVertically
) {
lesson
.subGroups
.sortedBy { it.number }
.forEachIndexed { subGroupIdx, subGroup ->
if (subGroupIdx > 0) {
VerticalDivider(
Modifier.height(25.dp), 1.dp,
MaterialTheme.colorScheme.inverseSurface
)
}
// FIXME: тупая проверка
if (subGroup.teacher == "Только у другой") {
Text(
stringResource(
if (subGroup.number == 1)
R.string.only_for_second
else
R.string.only_for_first
),
fontWeight = FontWeight.W400,
style = MaterialTheme.typography.titleSmall
)
return@forEachIndexed
}
Column {
val cabinet =
if (subGroup.cabinet.toIntOrNull() == null)
subGroup.cabinet
else
"${subGroup.cabinet}"
SmallTextWithIcon(
R.drawable.ic_cabinet,
"Cabinet",
cabinet
)
SmallTextWithIcon(
R.drawable.ic_teacher,
"Teacher",
subGroup.teacher
)
}
}
}
}
}
}
}

View File

@@ -1,29 +1,53 @@
package ru.n08i40k.polytechnic.next.ui.widgets.schedule
import androidx.compose.animation.Animatable
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeContent
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
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.graphics.graphicsLayer
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.lerp
import kotlinx.coroutines.launch
import kotlinx.datetime.LocalDateTime
import ru.n08i40k.polytechnic.next.R
import ru.n08i40k.polytechnic.next.model.GroupOrTeacher
import ru.n08i40k.polytechnic.next.model.Lesson
import ru.n08i40k.polytechnic.next.model.LessonType
import ru.n08i40k.polytechnic.next.repository.schedule.impl.MockScheduleRepository
import ru.n08i40k.polytechnic.next.ui.theme.AppTheme
import ru.n08i40k.polytechnic.next.ui.widgets.NotificationCard
import ru.n08i40k.polytechnic.next.utils.dateTime
import ru.n08i40k.polytechnic.next.utils.now
@@ -39,9 +63,31 @@ private fun isScheduleOutdated(schedule: GroupOrTeacher): Boolean {
return nowDateTime > lastLesson.time.end.dateTime
}
@Preview(showSystemUi = true)
@PreviewLightDark()
@Composable
fun SchedulePager(schedule: GroupOrTeacher = MockScheduleRepository.exampleTeacher) {
private fun SchedulePagerPreview() {
AppTheme {
Surface(
Modifier
.fillMaxSize()
.windowInsetsPadding(WindowInsets.safeContent.only(WindowInsetsSides.Top))
) {
SchedulePager(MockScheduleRepository.exampleTeacher)
}
}
}
val weekList = listOf(
R.string.week_bar_monday,
R.string.week_bar_tuesday,
R.string.week_bar_wednesday,
R.string.week_bar_thursday,
R.string.week_bar_friday,
R.string.week_bar_saturday,
)
@Composable
fun SchedulePager(schedule: GroupOrTeacher) {
val pagerState = rememberPagerState(
initialPage = (schedule.currentIdx ?: (schedule.days.size - 1)).coerceAtLeast(0),
pageCount = { schedule.days.size }
@@ -49,16 +95,88 @@ fun SchedulePager(schedule: GroupOrTeacher = MockScheduleRepository.exampleTeach
var dialogLesson by remember { mutableStateOf<WeakReference<Lesson>?>(null) }
Column {
Column(
Modifier
.fillMaxWidth(),
Arrangement.spacedBy(20.dp)
) {
if (isScheduleOutdated(schedule))
NotificationCard(Level.WARNING, stringResource(R.string.outdated_schedule))
Card(
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceContainerLowest
)
) {
Row(Modifier.fillMaxWidth(), Arrangement.spacedBy(5.dp)) {
val coroutineScope = rememberCoroutineScope()
for (i in 0..5) {
val primaryContainerColor = MaterialTheme.colorScheme.primaryContainer
val onPrimaryContainerColor = MaterialTheme.colorScheme.onPrimaryContainer
val onSurfaceColor = MaterialTheme.colorScheme.onSurface
val containerColor = remember {
Animatable(
if (pagerState.currentPage == i) primaryContainerColor
else Color.Transparent
)
}
val contentColor = remember {
Animatable(
if (pagerState.currentPage == i) onPrimaryContainerColor
else onSurfaceColor
)
}
LaunchedEffect(pagerState, pagerState.currentPage) {
containerColor.animateTo(
if (pagerState.currentPage == i) primaryContainerColor
else Color.Transparent
)
contentColor.animateTo(
if (pagerState.currentPage == i) onPrimaryContainerColor
else onSurfaceColor
)
}
Column(
modifier = Modifier
.weight(1f)
.clip(RoundedCornerShape(20))
.background(containerColor.value)
.clickable { coroutineScope.launch { pagerState.animateScrollToPage(i) } },
horizontalAlignment = Alignment.CenterHorizontally
) {
Column(
Modifier
.padding(12.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
stringResource(weekList[i]),
style = MaterialTheme.typography.bodyMedium,
color = contentColor.value
)
Text(
schedule.days[i].date.dateTime.date.dayOfMonth.toString(),
style = MaterialTheme.typography.bodyLarge,
fontWeight = FontWeight.W600,
color = contentColor.value
)
}
}
}
}
}
HorizontalPager(
pagerState,
Modifier
.height(600.dp)
.padding(top = 5.dp),
PaddingValues(horizontal = 7.dp),
.height(600.dp),
verticalAlignment = Alignment.Top
) { page ->
DayCard(
@@ -68,7 +186,7 @@ fun SchedulePager(schedule: GroupOrTeacher = MockScheduleRepository.exampleTeach
).absoluteValue
lerp(
start = 1f, stop = 0.95f, fraction = 1f - offset.coerceIn(0f, 1f)
start = 1f, stop = 0.95f, fraction = offset.coerceIn(0f, 1f)
).also { scale ->
scaleX = scale
scaleY = scale

View File

@@ -1,4 +1,4 @@
package ru.n08i40k.polytechnic.next.ui.screen.schedule
package ru.n08i40k.polytechnic.next.ui.widgets.schedule
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
@@ -6,6 +6,8 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@@ -21,20 +23,19 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import ru.n08i40k.polytechnic.next.CacheDate
import ru.n08i40k.polytechnic.next.R
import ru.n08i40k.polytechnic.next.ui.screen.schedule.PaskhalkoDialog
import ru.n08i40k.polytechnic.next.ui.widgets.ExpandableCard
import ru.n08i40k.polytechnic.next.ui.widgets.ExpandableCardTitle
import ru.n08i40k.polytechnic.next.utils.*
import java.util.Date
val expanded = mutableStateOf(false)
@Preview(showBackground = true)
@Composable
fun UpdateInfo(
lastUpdateAt: Long = 0,
cacheDate: CacheDate = CacheDate.newBuilder().build()
) {
var expanded by remember { expanded }
var expanded by remember { mutableStateOf(false) }
val format = "HH:mm:ss dd.MM.yyyy"
@@ -44,6 +45,7 @@ fun UpdateInfo(
ExpandableCard(
expanded = expanded,
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceContainerLowest),
onExpandedChange = { expanded = !expanded },
title = { ExpandableCardTitle(stringResource(R.string.update_info_header)) }
) {

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M120,840v-80h80v-640h400v40h160v600h80v80L680,840v-600h-80v600L120,840ZM280,200v560,-560ZM440,520q17,0 28.5,-11.5T480,480q0,-17 -11.5,-28.5T440,440q-17,0 -28.5,11.5T400,480q0,17 11.5,28.5T440,520ZM280,760h240v-560L280,200v560Z"
android:fillColor="#e8eaed"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M96,768v-92q0,-25.78 12.5,-47.39T143,594q54,-32 114.5,-49T384,528q66,0 126.5,17T625,594q22,13 34.5,34.61T672,676v92L96,768ZM744,768v-92q0,-42 -19.5,-78T672,539q39,8 75.5,21.5T817,594q22,13 34.5,34.67Q864,650.35 864,676v92L744,768ZM384,480q-60,0 -102,-42t-42,-102q0,-60 42,-102t102,-42q60,0 102,42t42,102q0,60 -42,102t-102,42ZM720,336q0,60 -42,102t-102,42q-8,0 -15,-0.5t-15,-2.5q25,-29 39.5,-64.5T600,336q0,-41 -14.5,-76.5T546,195q8,-2 15,-2.5t15,-0.5q60,0 102,42t42,102ZM168,696h432v-20q0,-6.47 -3.03,-11.76 -3.02,-5.3 -7.97,-8.24 -47,-27 -99,-41.5T384,600q-54,0 -106,14t-99,42q-4.95,2.83 -7.98,7.91 -3.02,5.09 -3.02,12L168,696ZM384.21,408Q414,408 435,386.79t21,-51Q456,306 434.79,285t-51,-21Q354,264 333,285.21t-21,51Q312,366 333.21,387t51,21ZM384,696ZM384,336Z"
android:fillColor="#e8eaed"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M480,840 L200,688v-240L40,360l440,-240 440,240v320h-80v-276l-80,44v240L480,840ZM480,508 L754,360 480,212 206,360 480,508ZM480,749 L680,641v-151L480,600 280,490v151l200,108ZM480,508ZM480,598ZM480,598Z"
android:fillColor="#e8eaed"/>
</vector>

View File

@@ -67,7 +67,7 @@
<string name="auth_error_disallowed_role">Пожалуйста, используйте другую роль.</string>
<string name="sign_in_manual">По имени пользователя</string>
<string name="updater_support_end">Поддержка версий ниже %1$s прекращена!</string>
<string name="last_server_schedule_update">Последнее обновление расписания</string>
<string name="last_server_schedule_update">Расписание</string>
<string name="teacher_name">ФИО преподавателя</string>
<string name="updater_body">Желаете ли вы обновиться до последней версии?</string>
<string name="updater_new_version">Вышла новая версия приложения!</string>
@@ -76,8 +76,8 @@
<string name="updater_update">ОБНОВИТЬ</string>
<string name="updater_suppress">ЗАГЛУШИТЬ</string>
<string name="update_info_header">Дополнительная информация</string>
<string name="last_local_update">Последнее локальное обновление</string>
<string name="last_server_cache_update">Последнее обновление кеша</string>
<string name="last_local_update">Локально</string>
<string name="last_server_cache_update">Кеш</string>
<string name="download_update">Скачать обновление</string>
<string name="telegram_channel">Телеграм канал</string>
<string name="schedule_update_title">Расписание обновлено!</string>
@@ -107,4 +107,13 @@
<string name="extra_info_duration">"Длительность: "</string>
<string name="extra_info_duration_second">С %1$s до %2$s (%3$d ч. %4$d мин.)</string>
<string name="extra_info_type">"Тип: "</string>
<string name="minutes_full">минут</string>
<string name="only_for_second">Только у второй</string>
<string name="only_for_first">Только у первой</string>
<string name="week_bar_monday">Пн</string>
<string name="week_bar_tuesday">Вт</string>
<string name="week_bar_wednesday">Ср</string>
<string name="week_bar_thursday">Чт</string>
<string name="week_bar_friday">Пт</string>
<string name="week_bar_saturday">Сб</string>
</resources>

View File

@@ -107,4 +107,13 @@
<string name="extra_info_duration">"Duration: "</string>
<string name="extra_info_duration_second">From %1$s to %2$s (%3$d h. %4$d min.)</string>
<string name="extra_info_type">"Type: "</string>
<string name="minutes_full">minutes</string>
<string name="only_for_second">Only for second</string>
<string name="only_for_first">Only for first</string>
<string name="week_bar_tuesday">Tue</string>
<string name="week_bar_wednesday">Wed</string>
<string name="week_bar_thursday">Thu</string>
<string name="week_bar_friday">Fri</string>
<string name="week_bar_saturday">Sat</string>
<string name="week_bar_monday">Mon</string>
</resources>