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:
@@ -20,6 +20,8 @@ struct LessonCard: View {
|
||||
var body: some View {
|
||||
WithSubjectColor(subject: lesson.subject) { c in
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
// Exams, credits and consultations announce themselves before the subject.
|
||||
if let session = lesson.session { SessionBadge(kind: session).padding(.bottom, 2) }
|
||||
HStack(alignment: .top, spacing: 8) {
|
||||
Text(lesson.subject).font(.headline).frame(maxWidth: .infinity, alignment: .leading)
|
||||
if let status {
|
||||
@@ -66,6 +68,7 @@ struct LessonCard: View {
|
||||
.background(c.soft, in: .rect(cornerRadius: 22, style: .continuous))
|
||||
.overlay {
|
||||
if running { RoundedRectangle(cornerRadius: 22, style: .continuous).strokeBorder(c.onSoft, lineWidth: 2) }
|
||||
else if lesson.session != nil { RoundedRectangle(cornerRadius: 22, style: .continuous).strokeBorder(Color.sessionAccent, lineWidth: 2) }
|
||||
}
|
||||
.contentShape(.rect(cornerRadius: 22))
|
||||
}
|
||||
@@ -74,7 +77,7 @@ struct LessonCard: View {
|
||||
|
||||
func tagsOf(_ lesson: Lesson) -> [String] {
|
||||
var tags: [String] = []
|
||||
if !lesson.type.isEmpty { tags.append(lesson.type) }
|
||||
if !lesson.type.isEmpty && lesson.session == nil { tags.append(lesson.type) }
|
||||
if lesson.variant != "all" { tags.append(lesson.variant == "upper" ? "белые недели" : "зелёные недели") }
|
||||
if lesson.groups.count > 1 { tags.append("общее занятие") }
|
||||
return tags
|
||||
@@ -104,11 +107,11 @@ struct CompactLesson: View {
|
||||
var slot: Slot
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 14) {
|
||||
SubjectDot(subject: lesson.subject, size: 10)
|
||||
HStack(spacing: lesson.session == nil ? 14 : 10) {
|
||||
if let session = lesson.session { SessionDot(kind: session, size: 22) } else { SubjectDot(subject: lesson.subject, size: 10) }
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(lesson.subject).font(.subheadline.weight(.semibold)).lineLimit(2).foregroundStyle(.primary)
|
||||
Text(details).font(.subheadline.monospacedDigit()).foregroundStyle(.secondary)
|
||||
Text(details).font(.subheadline.monospacedDigit()).foregroundStyle(lesson.session == nil ? Color.secondary : Color.sessionAccent)
|
||||
}
|
||||
Spacer(minLength: 0)
|
||||
}
|
||||
@@ -117,9 +120,11 @@ struct CompactLesson: View {
|
||||
|
||||
private var details: String {
|
||||
var parts = [slot.timeLabel]
|
||||
if let session = lesson.session { parts.insert(session.label, at: 0) }
|
||||
if let sub = lesson.subgroup, lesson.session != nil { parts.append(sub.lowercased().replacingOccurrences(of: "подгруппа", with: "подгр.")) }
|
||||
if lesson.distance { parts.append("дистанционно") }
|
||||
else if show.rooms && !lesson.rooms.isEmpty { parts.append("ауд. " + lesson.rooms.joined(separator: ", ")) }
|
||||
if show.tags && !lesson.type.isEmpty { parts.append(lesson.type.lowercased()) }
|
||||
if show.tags && !lesson.type.isEmpty && lesson.session == nil { parts.append(lesson.type.lowercased()) }
|
||||
return parts.joined(separator: " · ")
|
||||
}
|
||||
}
|
||||
@@ -213,7 +218,7 @@ private struct TypeTag: View {
|
||||
let color: Color = switch type {
|
||||
case "Лекция": .brand
|
||||
case "Практика": .purple
|
||||
case "Экзамен", "Зачёт": .red
|
||||
case "Экзамен", "Зачёт", "Защита курсовой работы", "Консультация": .sessionAccent
|
||||
default: .gray
|
||||
}
|
||||
Text(type.isEmpty ? "Занятие" : type)
|
||||
|
||||
Reference in New Issue
Block a user