diff --git a/App/StudyAppApp.swift b/App/StudyAppApp.swift index 9039d7b..e85b433 100644 --- a/App/StudyAppApp.swift +++ b/App/StudyAppApp.swift @@ -134,26 +134,30 @@ struct StudyAppApp: App { BackupCommands(viewModel: backupViewModel) } - MenuBarExtra(isInserted: menuBarItemInserted) { - MenuBarContent( - summary: menuBarSummary, - onOpenLecture: { moduleID in - Self.bringToFront() - navigation.show(module: moduleID, section: .slides, intent: .resumeSlides) - }, - onReviewCards: { - Self.bringToFront() - // Starts with the module that has the most due cards, like „Lernen starten“ on Today. - if let moduleID = todayViewModel.dueCounts.max(by: { $0.value < $1.value })?.key { - navigation.show(module: moduleID, section: .flashcards) - } - }, - onOpenApp: { Self.bringToFront() } - ) - .tint(Theme.accent) - .preferredColorScheme(appearance.colorScheme) + MenuBarExtra(isInserted: $showsMenuBarItem) { + MenuBarSummaryReader(calendar: calendarViewModel, today: todayViewModel) { summary in + MenuBarContent( + summary: summary, + onOpenLecture: { moduleID in + Self.bringToFront() + navigation.show(module: moduleID, section: .slides, intent: .resumeSlides) + }, + onReviewCards: { + Self.bringToFront() + // Starts with the module that has the most due cards, like „Lernen starten“ on Today. + if let moduleID = todayViewModel.dueCounts.max(by: { $0.value < $1.value })?.key { + navigation.show(module: moduleID, section: .flashcards) + } + }, + onOpenApp: { Self.bringToFront() } + ) + .tint(Theme.accent) + .preferredColorScheme(appearance.colorScheme) + } } label: { - MenuBarLabel(summary: menuBarSummary) + MenuBarSummaryReader(calendar: calendarViewModel, today: todayViewModel) { summary in + MenuBarLabel(summary: summary) + } } .menuBarExtraStyle(.window) @@ -193,23 +197,6 @@ struct StudyAppApp: App { await notificationsViewModel.refresh() } - /// Keeps the menu bar item out of the unit test host: on CI runners its status item - /// blocks the launch, so the test runner never connects. - private var menuBarItemInserted: Binding { - Binding( - get: { showsMenuBarItem && !Self.isRunningAsTestHost }, - set: { showsMenuBarItem = $0 } - ) - } - - private static var isRunningAsTestHost: Bool { - ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] != nil - } - - private var menuBarSummary: MenuBarSummary { - MenuBarSummary(calendar: calendarViewModel, dueCards: todayViewModel.totalDue) - } - /// Shows the main window, e.g. after a click in the menu bar. private static func bringToFront() { NSApplication.shared.activate() diff --git a/Presentation/MenuBar/MenuBarContent.swift b/Presentation/MenuBar/MenuBarContent.swift index 9499508..6896019 100644 --- a/Presentation/MenuBar/MenuBarContent.swift +++ b/Presentation/MenuBar/MenuBarContent.swift @@ -1,6 +1,18 @@ import AppKit import SwiftUI +/// Builds the summary inside a view, so only the menu bar item observes the calendar and the due cards. +/// Reading them in the App body would rebuild every scene on each change, already while the app launches. +struct MenuBarSummaryReader: View { + let calendar: StudyCalendarViewModel + let today: TodayViewModel + @ViewBuilder let content: (MenuBarSummary) -> Content + + var body: some View { + content(MenuBarSummary(calendar: calendar, dueCards: today.totalDue)) + } +} + /// The menu bar item's label: an icon, plus the remaining minutes while a lecture runs. struct MenuBarLabel: View { let summary: MenuBarSummary