From dbc1afcf28d68d3b26cde28c708ffcb0c1cde3e4 Mon Sep 17 00:00:00 2001 From: N08I40K Date: Thu, 20 Mar 2025 07:52:37 +0400 Subject: [PATCH] 3.2.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Изменённый дизайн. --- app/build.gradle.kts | 5 +- .../ui/screen/schedule/GroupScheduleScreen.kt | 7 +- .../screen/schedule/TeacherScheduleScreen.kt | 7 +- .../ui/screen/schedule/TeacherSearchScreen.kt | 5 +- .../next/ui/widgets/ExpandableCard.kt | 16 +- .../next/ui/widgets/schedule/DayCard.kt | 128 ++----- .../next/ui/widgets/schedule/LessonRow.kt | 329 ++++++++++++------ .../next/ui/widgets/schedule/SchedulePager.kt | 136 +++++++- .../schedule/UpdateInfo.kt | 10 +- app/src/main/res/drawable/ic_cabinet.xml | 9 + app/src/main/res/drawable/ic_group.xml | 9 + app/src/main/res/drawable/ic_teacher.xml | 9 + app/src/main/res/values-ru/strings.xml | 15 +- app/src/main/res/values/strings.xml | 9 + gradle/libs.versions.toml | 2 + 15 files changed, 442 insertions(+), 254 deletions(-) rename app/src/main/java/ru/n08i40k/polytechnic/next/ui/{screen => widgets}/schedule/UpdateInfo.kt (89%) create mode 100644 app/src/main/res/drawable/ic_cabinet.xml create mode 100644 app/src/main/res/drawable/ic_group.xml create mode 100644 app/src/main/res/drawable/ic_teacher.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a591bae..874927b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -46,8 +46,8 @@ android { applicationId = "ru.n08i40k.polytechnic.next" minSdk = 26 targetSdk = 35 - versionCode = 28 - versionName = "3.1.1" + versionCode = 29 + versionName = "3.2.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } @@ -136,6 +136,7 @@ dependencies { implementation(libs.kotlinx.datetime) // default + implementation(libs.androidx.runtime.tracing) implementation(libs.androidx.core.ktx) implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.androidx.activity.compose) diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/screen/schedule/GroupScheduleScreen.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/screen/schedule/GroupScheduleScreen.kt index a96c482..ca56012 100644 --- a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/screen/schedule/GroupScheduleScreen.kt +++ b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/screen/schedule/GroupScheduleScreen.kt @@ -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) } } diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/screen/schedule/TeacherScheduleScreen.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/screen/schedule/TeacherScheduleScreen.kt index 9a753b2..97330ff 100644 --- a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/screen/schedule/TeacherScheduleScreen.kt +++ b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/screen/schedule/TeacherScheduleScreen.kt @@ -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) } } diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/screen/schedule/TeacherSearchScreen.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/screen/schedule/TeacherSearchScreen.kt index 8428dbc..47364de 100644 --- a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/screen/schedule/TeacherSearchScreen.kt +++ b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/screen/schedule/TeacherSearchScreen.kt @@ -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) } } diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/widgets/ExpandableCard.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/widgets/ExpandableCard.kt index 03d7ad6..c4d2f1d 100644 --- a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/widgets/ExpandableCard.kt +++ b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/widgets/ExpandableCard.kt @@ -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) } diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/widgets/schedule/DayCard.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/widgets/schedule/DayCard.kt index 3963018..33dac44 100644 --- a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/widgets/schedule/DayCard.kt +++ b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/widgets/schedule/DayCard.kt @@ -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 { 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) } } } diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/widgets/schedule/LessonRow.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/widgets/schedule/LessonRow.kt index eb7e0de..8130910 100644 --- a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/widgets/schedule/LessonRow.kt +++ b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/widgets/schedule/LessonRow.kt @@ -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 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 + ) + } + } + } + } + } } } \ No newline at end of file diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/widgets/schedule/SchedulePager.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/widgets/schedule/SchedulePager.kt index 0b0d065..76c7e10 100644 --- a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/widgets/schedule/SchedulePager.kt +++ b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/widgets/schedule/SchedulePager.kt @@ -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?>(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 diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/screen/schedule/UpdateInfo.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/widgets/schedule/UpdateInfo.kt similarity index 89% rename from app/src/main/java/ru/n08i40k/polytechnic/next/ui/screen/schedule/UpdateInfo.kt rename to app/src/main/java/ru/n08i40k/polytechnic/next/ui/widgets/schedule/UpdateInfo.kt index 981288f..054f3d4 100644 --- a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/screen/schedule/UpdateInfo.kt +++ b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/widgets/schedule/UpdateInfo.kt @@ -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)) } ) { diff --git a/app/src/main/res/drawable/ic_cabinet.xml b/app/src/main/res/drawable/ic_cabinet.xml new file mode 100644 index 0000000..183de62 --- /dev/null +++ b/app/src/main/res/drawable/ic_cabinet.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_group.xml b/app/src/main/res/drawable/ic_group.xml new file mode 100644 index 0000000..f32f87e --- /dev/null +++ b/app/src/main/res/drawable/ic_group.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_teacher.xml b/app/src/main/res/drawable/ic_teacher.xml new file mode 100644 index 0000000..5e0dbb5 --- /dev/null +++ b/app/src/main/res/drawable/ic_teacher.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index fc0edf5..3b5d9b9 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -67,7 +67,7 @@ Пожалуйста, используйте другую роль. По имени пользователя Поддержка версий ниже %1$s прекращена! - Последнее обновление расписания + Расписание ФИО преподавателя Желаете ли вы обновиться до последней версии? Вышла новая версия приложения! @@ -76,8 +76,8 @@ ОБНОВИТЬ ЗАГЛУШИТЬ Дополнительная информация - Последнее локальное обновление - Последнее обновление кеша + Локально + Кеш Скачать обновление Телеграм канал Расписание обновлено! @@ -107,4 +107,13 @@ "Длительность: " С %1$s до %2$s (%3$d ч. %4$d мин.) "Тип: " + минут + Только у второй + Только у первой + Пн + Вт + Ср + Чт + Пт + Сб \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 97b3e65..8941d77 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -107,4 +107,13 @@ "Duration: " From %1$s to %2$s (%3$d h. %4$d min.) "Type: " + minutes + Only for second + Only for first + Tue + Wed + Thu + Fri + Sat + Mon \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ea60927..836c12a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -16,6 +16,7 @@ hiltAndroidCompiler = "2.55" hiltNavigationCompose = "1.2.0" kotlinxSerializationJson = "1.8.0" protobufLite = "3.0.1" +runtimeTracing = "1.7.8" volley = "1.2.1" datastore = "1.1.3" navigationCompose = "2.8.9" @@ -26,6 +27,7 @@ vkid = "2.2.2" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } +androidx-runtime-tracing = { module = "androidx.compose.runtime:runtime-tracing", version.ref = "runtimeTracing" } desugar_jdk_libs = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugar_jdk_libs" } junit = { group = "junit", name = "junit", version.ref = "junit" } androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }