Compare commits

...
Author SHA1 Message Date
Agzamus 9a96bd3dac ci: installing from /github.com/adoptium 2026-09-10 00:47:38 +03:00
Agzamus c4345f9dbe ci: bullseye repo archive mirror 2026-09-10 00:44:18 +03:00
Agzamus 4797811097 ci: ignore jdk Check-Valid-Until 2026-09-10 00:31:17 +03:00
Agzamus 2dfef426a7 ui: schedule striped copyable rows 2026-09-10 00:23:28 +03:00
Agzamus 97c19647cc ui: LessonSlot separator 2026-09-09 22:25:55 +03:00
2 changed files with 115 additions and 65 deletions
+10 -11
View File
@@ -29,19 +29,18 @@ jobs:
- name: Set up JDK 17 - name: Set up JDK 17
# Ставим JDK вручную и явно прописываем JAVA_HOME. # Ставим JDK вручную и явно прописываем JAVA_HOME.
# actions/setup-java не всегда передаёт JAVA_HOME дальше в Gitea Actions. # actions/setup-java не всегда передаёт JAVA_HOME дальше в Gitea Actions.
# JDK качаем из архива Adoptium напрямую: образ раннера — Debian bullseye,
# его репозитории уже в архиве, apt-установка openjdk падает с 404.
run: | run: |
set -euxo pipefail set -euxo pipefail
apt-get update cd /tmp
apt-get install -y --no-install-recommends wget unzip wget -q https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.12%2B7/OpenJDK17U-jdk_x64_linux_hotspot_17.0.12_7.tar.gz
mkdir -p /opt/jdk tar -xzf OpenJDK17U-jdk_x64_linux_hotspot_17.0.12_7.tar.gz -C /opt
wget -qO /tmp/jdk17.tar.gz \ mv /opt/jdk-17.0.12+7 /opt/java17
"https://api.adoptium.net/v3/binary/latest/17/ga/linux/x64/jdk/hotspot/normal/eclipse" export JAVA_HOME=/opt/java17
tar -xzf /tmp/jdk17.tar.gz -C /opt/jdk --strip-components=1 export PATH=$JAVA_HOME/bin:$PATH
echo "JAVA_HOME=/opt/jdk" >> "$GITHUB_ENV" echo "JAVA_HOME=/opt/java17" >> $GITHUB_ENV
echo "/opt/jdk/bin" >> "$GITHUB_PATH" echo "$JAVA_HOME/bin" >> $GITHUB_PATH
/opt/jdk/bin/java -version java -version
javac -version
- name: Install Android SDK - name: Install Android SDK
# Устанавливаем command-line tools и нужные компоненты SDK, # Устанавливаем command-line tools и нужные компоненты SDK,
@@ -4,23 +4,31 @@ import agzam.vyatsu.domain.model.Day
import agzam.vyatsu.domain.model.LessonType import agzam.vyatsu.domain.model.LessonType
import agzam.vyatsu.domain.model.SchedulePeriod import agzam.vyatsu.domain.model.SchedulePeriod
import agzam.vyatsu.domain.model.TimeSlot import agzam.vyatsu.domain.model.TimeSlot
import android.widget.Toast
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowDropDown import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.material.icons.filled.Refresh import androidx.compose.material.icons.filled.Refresh
@@ -46,14 +54,19 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import java.time.DayOfWeek import java.time.DayOfWeek
import java.time.LocalDate import java.time.LocalDate
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
val hPadding = 16.dp
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun ScheduleScreen(viewModel: ScheduleViewModel, onSwitchGroup: () -> Unit) { fun ScheduleScreen(viewModel: ScheduleViewModel, onSwitchGroup: () -> Unit) {
val state by viewModel.uiState.collectAsState() val state by viewModel.uiState.collectAsState()
Column(Modifier.fillMaxSize()) { Column(Modifier.fillMaxSize()) {
@@ -69,7 +82,7 @@ fun ScheduleScreen(viewModel: ScheduleViewModel, onSwitchGroup: () -> Unit) {
}, },
windowInsets = WindowInsets(0.dp) windowInsets = WindowInsets(0.dp)
) )
Column(Modifier.fillMaxSize().padding(horizontal = 16.dp, vertical = 0.dp)) { Column(Modifier.fillMaxSize().padding(horizontal = 0.dp, vertical = 0.dp)) {
when { when {
state.isSyncing && state.schedule == null -> { state.isSyncing && state.schedule == null -> {
Row( Row(
@@ -110,7 +123,7 @@ fun ScheduleScreen(viewModel: ScheduleViewModel, onSwitchGroup: () -> Unit) {
val dayLabel = if (isTodayDay) "Сегодня " else "" val dayLabel = if (isTodayDay) "Сегодня " else ""
Row( Row(
Modifier.fillMaxWidth(), Modifier.fillMaxWidth().padding(horizontal = hPadding),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Text( Text(
@@ -125,7 +138,7 @@ fun ScheduleScreen(viewModel: ScheduleViewModel, onSwitchGroup: () -> Unit) {
onSelect = viewModel::selectWeek onSelect = viewModel::selectWeek
) )
} }
HorizontalDivider(Modifier.padding(top = 8.dp, bottom = 0.dp)) HorizontalDivider(Modifier.padding(top = 8.dp, bottom = 0.dp, start = hPadding, end = hPadding))
DayChips( DayChips(
days = weekDays, days = weekDays,
@@ -134,14 +147,15 @@ fun ScheduleScreen(viewModel: ScheduleViewModel, onSwitchGroup: () -> Unit) {
onSelect = viewModel::selectDay onSelect = viewModel::selectDay
) )
HorizontalDivider(Modifier.padding(top=0.dp, bottom = 8.dp)) HorizontalDivider(Modifier.padding(top=0.dp, start = hPadding, end = hPadding))
if (day.intervals.all { it.lessons.isEmpty() }) { if (day.intervals.all { it.lessons.isEmpty() }) {
Text("Занятий нет", style = MaterialTheme.typography.bodyLarge) Text("Занятий нет", style = MaterialTheme.typography.bodyLarge)
} else { } else {
LazyColumn( LazyColumn(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(8.dp) verticalArrangement = Arrangement.spacedBy(8.dp),
contentPadding = PaddingValues(vertical = 8.dp)
) { ) {
itemsIndexed(day.intervals) { slotIndex, slot -> itemsIndexed(day.intervals) { slotIndex, slot ->
LessonSlot(slotIndex = slotIndex, slot = slot) LessonSlot(slotIndex = slotIndex, slot = slot)
@@ -227,7 +241,7 @@ private fun DayChips(
todayDate: LocalDate?, todayDate: LocalDate?,
onSelect: (LocalDate) -> Unit onSelect: (LocalDate) -> Unit
) { ) {
LazyRow(horizontalArrangement = Arrangement.spacedBy(6.dp)) { LazyRow(horizontalArrangement = Arrangement.spacedBy(6.dp), contentPadding = PaddingValues(horizontal = hPadding, vertical = 2.dp),) {
items(days, key = { it.date.toEpochDay() }) { day -> items(days, key = { it.date.toEpochDay() }) { day ->
val selected = selectedDate == day.date val selected = selectedDate == day.date
val isTodayChip = todayDate == day.date val isTodayChip = todayDate == day.date
@@ -275,56 +289,92 @@ private fun weekLabel(period: SchedulePeriod): String {
@Composable @Composable
private fun LessonSlot(slotIndex: Int, slot: TimeSlot) { private fun LessonSlot(slotIndex: Int, slot: TimeSlot) {
val context = LocalContext.current
var indexColor = 0
Column(Modifier.fillMaxWidth()) { Column(Modifier.fillMaxWidth()) {
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
HorizontalDivider(
modifier = Modifier.width(10.dp + hPadding).padding(start = hPadding),
color = MaterialTheme.colorScheme.primary
)
Text( Text(
"${slotIndex + 1} пара · ${slot.start.format(TIME)}–${slot.end.format(TIME)}", "${slotIndex + 1} пара · ${slot.start.format(TIME)}–${slot.end.format(TIME)}",
style = MaterialTheme.typography.labelMedium, style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.primary color = MaterialTheme.colorScheme.primary,
modifier = Modifier.padding(horizontal = 4.dp)
) )
HorizontalDivider(color = MaterialTheme.colorScheme.primary, modifier = Modifier.weight(1f).padding(end = hPadding))
}
if (slot.lessons.isEmpty()) { if (slot.lessons.isEmpty()) {
Text("—", style = MaterialTheme.typography.bodyMedium) Text("—", style = MaterialTheme.typography.bodyMedium)
} else { } else {
slot.lessons.forEach { lesson -> slot.lessons.forEachIndexed { index, lesson ->
Column(Modifier.fillMaxWidth().padding(vertical = 2.dp)) { val backgroundColor = if (indexColor % 2 == 0) {
MaterialTheme.colorScheme.surfaceBright
} else {
MaterialTheme.colorScheme.surfaceVariant
}
indexColor++
// XXX:
// Убрать SelectionContainer?
// Режим редактирования расписания?
// Меню на onClick?
// Меню на onLongClick?
SelectionContainer() {
Column(
Modifier.fillMaxWidth().padding(vertical = 2.dp).background(backgroundColor)
// XXX: combinedClickable?
//.combinedClickable(
// onClick = {
// Toast.makeText(
// context,
// lesson.raw,
// Toast.LENGTH_SHORT
// ).show()
// },
// onLongClick = {},
// // TODO: onLongClickLabel = "(Accessibility)"
//),
) {
var any = false var any = false
if(lesson.type != LessonType.OTHER) {
Text(
lesson.type.label,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
any = true
}
val subject = lesson.subject?.takeIf { it.isNotBlank() } ?: lesson.raw
if(subject.isNotEmpty()) {
Text(
subject,
style = MaterialTheme.typography.bodyLarge
)
any = true
}
val meta = listOfNotNull( val meta = listOfNotNull(
if (lesson.type == LessonType.OTHER) {
null
} else {
lesson.type.label
},
lesson.subgroup, lesson.subgroup,
lesson.teacher, lesson.teacher,
lesson.room lesson.room
).filter { it.isNotBlank() }.joinToString(" · ") ).filter { it.isNotBlank() }.joinToString(" · ")
if(meta.isNotEmpty()) { if (meta.isNotEmpty()) {
Text( Text(
meta, meta,
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(horizontal = hPadding)
) )
any = true any = true
} }
if(!any) { val subject = lesson.subject?.takeIf { it.isNotBlank() } ?: lesson.raw
if (subject.isNotEmpty()) {
Text( Text(
"-", subject,
style = MaterialTheme.typography.bodyLarge,
modifier = Modifier.padding(horizontal = hPadding)
)
any = true
}
if (!any) {
Text(
"пусто",
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(horizontal = hPadding)
) )
} }
@@ -332,6 +382,7 @@ private fun LessonSlot(slotIndex: Int, slot: TimeSlot) {
} }
} }
} }
}
} }
private fun dayName(dayOfWeek: DayOfWeek): String = when (dayOfWeek) { private fun dayName(dayOfWeek: DayOfWeek): String = when (dayOfWeek) {