feat: Enhance session handling in timetable and UI

- Introduced session types (exams, credits, consultations, coursework defenses) with distinct icons and colors for better visibility in the UI.
- Updated PlansScreen, ScheduleScreen, and WeekGrid to display session information appropriately.
- Modified CalendarView and PlansView to include session indicators in the calendar and plans.
- Enhanced LessonCard and CompactLesson to show session details and adjust styling based on session presence.
- Added new SessionMark component for consistent session representation across Android and iOS.
- Updated server logic to handle session entries from external documents, ensuring accurate timetable representation.
- Improved date parsing to accommodate session-related dates in various formats.
- Added tests to verify session handling and date parsing functionality.
This commit is contained in:
2026-10-19 20:49:13 +03:00
parent db9a3f8243
commit 15851b26cd
21 changed files with 421 additions and 42 deletions

View File

@@ -164,6 +164,7 @@ private struct MonthGrid: View {
let isToday = d == model.today
let lessons = model.timetable.map { !model.slots($0, on: d).isEmpty } ?? false
let plans = model.notes.contains { $0.date == d.iso }
let session = model.timetable.map { model.slots($0, on: d).contains { $0.lessons.contains { $0.session != nil } } } ?? false
let color: Color = isSelected ? .onBrand : isToday ? .brand : inMonth ? .primary : Color(.tertiaryLabel)
return Button { onSelect(d) } label: {
VStack(spacing: 3) {
@@ -173,8 +174,10 @@ private struct MonthGrid: View {
.frame(width: 34, height: 34)
.background { if isSelected { Circle().fill(Color.brand) } else if isToday { Circle().strokeBorder(Color.brand, lineWidth: 1.5) } }
HStack(spacing: 3) {
Circle().fill(lessons ? Color.secondary : .clear).frame(width: 4, height: 4)
if plans { Circle().fill(Color.orange).frame(width: 4, height: 4) }
// Lessons in grey, an exam or credit in amber, plans in purple.
if session { RoundedRectangle(cornerRadius: 1.5).fill(Color.sessionAccent).frame(width: 6, height: 6) }
else { Circle().fill(lessons ? Color.secondary : .clear).frame(width: 4, height: 4) }
if plans { Circle().fill(Color.purple).frame(width: 4, height: 4) }
}
.frame(height: 4)
}
@@ -183,7 +186,7 @@ private struct MonthGrid: View {
.contentShape(.rect)
}
.buttonStyle(.plain)
.accessibilityLabel(d.weekdayName + ", " + d.longLabel + (lessons ? ", есть пары" : "") + (plans ? ", есть планы" : ""))
.accessibilityLabel(d.weekdayName + ", " + d.longLabel + (lessons ? ", есть пары" : "") + (plans ? ", есть планы" : "") + (session ? ", экзамен или зачёт" : ""))
.accessibilityAddTraits(isSelected ? .isSelected : [])
}
}