Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99e05e8232 | ||
|
|
c4ecc43597 | ||
|
|
9a96bd3dac | ||
|
|
c4345f9dbe | ||
|
|
4797811097 | ||
|
|
2dfef426a7 | ||
|
|
97c19647cc |
@@ -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
|
||||||
@@ -33,6 +41,7 @@ import androidx.compose.material3.FilterChipDefaults
|
|||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.LocalContentColor
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
@@ -46,14 +55,20 @@ 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.graphics.compositeOver
|
||||||
|
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 +84,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 +125,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 +140,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 +149,14 @@ 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 +242,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 +290,93 @@ 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)) {
|
var baseSurface = Modifier.fillMaxWidth().padding(vertical = 2.dp)
|
||||||
|
if ((indexColor) % 2 != 0) {
|
||||||
|
baseSurface = baseSurface.background(
|
||||||
|
MaterialTheme.colorScheme.surfaceContainer
|
||||||
|
);
|
||||||
|
}
|
||||||
|
indexColor++
|
||||||
|
// XXX:
|
||||||
|
// Убрать SelectionContainer?
|
||||||
|
// Режим редактирования расписания?
|
||||||
|
// Меню на onClick?
|
||||||
|
// Меню на onLongClick?
|
||||||
|
SelectionContainer() {
|
||||||
|
Column(
|
||||||
|
baseSurface
|
||||||
|
// 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 +384,7 @@ private fun LessonSlot(slotIndex: Int, slot: TimeSlot) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun dayName(dayOfWeek: DayOfWeek): String = when (dayOfWeek) {
|
private fun dayName(dayOfWeek: DayOfWeek): String = when (dayOfWeek) {
|
||||||
|
|||||||
Reference in New Issue
Block a user