From 71ef509021902e014d8f211fe27b308f3046451b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Horv=C3=A1th=20Gergely?= Date: Fri, 30 Jan 2026 20:44:48 +0100 Subject: [PATCH] Show active break in timetable widget Add break detection and UI for active breaks in timetable widgets. Introduces a new localization key "break_between" and a BreakIndicatorRow view that displays a break marker and minutes left. TimetableMediumView and TimetableLargeView now detect when the current time falls between consecutive lessons, reduce the number of visible lessons accordingly, and inject a BreakIndicatorRow between lessons when in a break (minutes are calculated using ceil of time interval). Also clean up commented/debug lines in the iOS widget cache helper (widget.dart) for locale/theme and removed a TODO comment. --- .../Helpers/Localization.swift | 5 ++ .../Views/TimetableViews.swift | 90 ++++++++++++++++++- firka/lib/helpers/db/widget.dart | 7 +- 3 files changed, 93 insertions(+), 9 deletions(-) diff --git a/firka/ios/HomeWidgetsExtension/Helpers/Localization.swift b/firka/ios/HomeWidgetsExtension/Helpers/Localization.swift index 6530dfb..33e4838 100644 --- a/firka/ios/HomeWidgetsExtension/Helpers/Localization.swift +++ b/firka/ios/HomeWidgetsExtension/Helpers/Localization.swift @@ -149,6 +149,11 @@ struct WidgetLocalization { "en": "lesson", "de": "Std" ], + "break_between": [ + "hu": "Szünet", + "en": "Break", + "de": "Pause" + ], "in_minutes": [ "hu": "%d perc múlva", "en": "in %d min", diff --git a/firka/ios/HomeWidgetsExtension/Views/TimetableViews.swift b/firka/ios/HomeWidgetsExtension/Views/TimetableViews.swift index 370910e..dc6334f 100644 --- a/firka/ios/HomeWidgetsExtension/Views/TimetableViews.swift +++ b/firka/ios/HomeWidgetsExtension/Views/TimetableViews.swift @@ -116,9 +116,19 @@ struct TimetableMediumView: View { return max(0, entry.lessons.count - 1) } + var hasActiveBreak: Bool { + let checkDate = entry.date + for i in 0.. entry.lessons[i].end && checkDate < entry.lessons[i + 1].start { + return true + } + } + return false + } + var visibleLessons: [WidgetLesson] { let totalLessons = entry.lessons.count - let maxVisible = 4 + let maxVisible = hasActiveBreak ? 3 : 4 if totalLessons <= maxVisible { return Array(entry.lessons) @@ -154,8 +164,17 @@ struct TimetableMediumView: View { .widgetTextStyle(style, colors: nil, isPrimary: false) Spacer(minLength: 0) - ForEach(visibleLessons) { lesson in + ForEach(Array(visibleLessons.enumerated()), id: \.element.id) { index, lesson in LessonRow(lesson: lesson, isActive: isLessonActive(lesson), style: style, compact: true) + + if index < visibleLessons.count - 1 { + let nextLesson = visibleLessons[index + 1] + let isInBreak = entry.date > lesson.end && entry.date < nextLesson.start + if isInBreak { + let breakMinutes = Int(ceil(nextLesson.start.timeIntervalSince(entry.date) / 60)) + BreakIndicatorRow(minutesLeft: breakMinutes, localization: localization, style: style, compact: true) + } + } } Spacer(minLength: 0) } @@ -178,6 +197,16 @@ struct TimetableLargeView: View { return checkDate >= lesson.start && checkDate <= lesson.end } + var hasActiveBreak: Bool { + let checkDate = entry.date + for i in 0.. entry.lessons[i].end && checkDate < entry.lessons[i + 1].start { + return true + } + } + return false + } + var headerText: String { if entry.isNextSchoolDay { let dateStr = WidgetLocalization.formatShortDate(entry.nextSchoolDayDateString, locale: localization.locale) @@ -199,8 +228,19 @@ struct TimetableLargeView: View { .fontWeight(.semibold) .widgetTextStyle(style, colors: nil) - ForEach(entry.lessons.prefix(7)) { lesson in + let maxLessons = hasActiveBreak ? 6 : 7 + let lessonsToShow = Array(entry.lessons.prefix(maxLessons)) + ForEach(Array(lessonsToShow.enumerated()), id: \.element.id) { index, lesson in LessonRow(lesson: lesson, isActive: isLessonActive(lesson), style: style, showRoom: true) + + if index < lessonsToShow.count - 1 { + let nextLesson = lessonsToShow[index + 1] + let isInBreak = entry.date > lesson.end && entry.date < nextLesson.start + if isInBreak { + let breakMinutes = Int(ceil(nextLesson.start.timeIntervalSince(entry.date) / 60)) + BreakIndicatorRow(minutesLeft: breakMinutes, localization: localization, style: style) + } + } } } .padding() @@ -209,6 +249,50 @@ struct TimetableLargeView: View { } } +struct BreakIndicatorRow: View { + let minutesLeft: Int + let localization: WidgetLocalization + let style: WidgetStyleType + var compact: Bool = false + @Environment(\.colorScheme) var colorScheme + + var liquidGlassPrimary: Color { + colorScheme == .dark ? .white : .black + } + + var liquidGlassSecondary: Color { + colorScheme == .dark ? .white.opacity(0.7) : .black.opacity(0.6) + } + + var body: some View { + HStack(spacing: 12) { + Text("–") + .font(.caption) + .fontWeight(.bold) + .frame(width: 24, height: 24) + .background( + Circle() + .fill(Color.green.opacity(0.3)) + ) + .foregroundColor(style == .liquidGlass ? liquidGlassPrimary : .primary) + + Text(localization.string("break_between")) + .font(.subheadline) + .fontWeight(.semibold) + .foregroundColor(style == .liquidGlass ? liquidGlassPrimary : .primary) + + Spacer() + + Text("\(minutesLeft) \(localization.string("minutes_abbrev"))") + .font(.caption) + .foregroundColor(style == .liquidGlass ? liquidGlassSecondary : .secondary) + } + .padding(.vertical, compact ? 2 : 4) + .padding(.horizontal, 8) + .currentLessonGlow(isActive: true) + } +} + struct LessonRow: View { let lesson: WidgetLesson let isActive: Bool diff --git a/firka/lib/helpers/db/widget.dart b/firka/lib/helpers/db/widget.dart index de9dd68..d1507e4 100644 --- a/firka/lib/helpers/db/widget.dart +++ b/firka/lib/helpers/db/widget.dart @@ -109,7 +109,6 @@ class WidgetCacheHelper { if (!Platform.isIOS) return; try { - // Get locale final langIndex = (settings.group("settings").subGroup("application")["language"] as SettingsItemsRadio) .activeIndex; @@ -125,10 +124,9 @@ class WidgetCacheHelper { locale = 'de'; break; default: - locale = 'hu'; // Default to Hungarian + locale = 'hu'; } - // Get theme final themeIndex = (settings.group("settings").subGroup("customization")["theme"] as SettingsItemsRadio) .activeIndex; @@ -189,7 +187,6 @@ class WidgetCacheHelper { debugPrint('iOS widget refresh: ${grades.length} grades fetched (cached: ${gradesResponse.cached})'); - // Calculate subject averages final Map subjectAverages = {}; final Set subjectUids = {}; @@ -214,9 +211,7 @@ class WidgetCacheHelper { ? overallSum / validSubjectCount : null; - // Check for break (simplified - you might want to enhance this) WidgetBreakInfo? currentBreak; - // TODO: Add break detection if needed await updateIOSWidgets( locale: locale,