From aac4de3fb3a6823e6c77fe1f09b4765c9ed84984 Mon Sep 17 00:00:00 2001 From: Tomas Kracmar Date: Fri, 10 Oct 2025 07:45:59 +0200 Subject: [PATCH] BusyMirror 1.2.1: fix calendar loading after grant, attendee status filter, main-actor; add Makefile --- BusyMirror.xcodeproj/project.pbxproj | 8 ++--- BusyMirror/ContentView.swift | 38 +++++++++++++++++++++-- Makefile | 46 ++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 6 deletions(-) create mode 100644 Makefile diff --git a/BusyMirror.xcodeproj/project.pbxproj b/BusyMirror.xcodeproj/project.pbxproj index c54c730..937f96c 100644 --- a/BusyMirror.xcodeproj/project.pbxproj +++ b/BusyMirror.xcodeproj/project.pbxproj @@ -410,7 +410,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 4; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = BusyMirror/Info.plist; @@ -421,7 +421,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MARKETING_VERSION = 1.2.0; + MARKETING_VERSION = 1.2.1; PRODUCT_BUNDLE_IDENTIFIER = com.cqrenet.BusyMirror; PRODUCT_NAME = "$(TARGET_NAME)"; REGISTER_APP_GROUPS = YES; @@ -440,7 +440,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 4; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = BusyMirror/Info.plist; @@ -451,7 +451,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MARKETING_VERSION = 1.2.0; + MARKETING_VERSION = 1.2.1; PRODUCT_BUNDLE_IDENTIFIER = com.cqrenet.BusyMirror; PRODUCT_NAME = "$(TARGET_NAME)"; REGISTER_APP_GROUPS = YES; diff --git a/BusyMirror/ContentView.swift b/BusyMirror/ContentView.swift index 1b0424a..30f337e 100644 --- a/BusyMirror/ContentView.swift +++ b/BusyMirror/ContentView.swift @@ -132,6 +132,7 @@ struct ContentView: View { @AppStorage("workHoursStart") private var workHoursStart: Int = 9 @AppStorage("workHoursEnd") private var workHoursEnd: Int = 17 @AppStorage("excludedTitleFilters") private var excludedTitleFiltersRaw: String = "" + @AppStorage("mirrorAcceptedOnly") private var mirrorAcceptedOnly: Bool = false var overlapMode: OverlapMode { get { OverlapMode(rawValue: overlapModeRaw) ?? .allow } nonmutating set { overlapModeRaw = newValue.rawValue } @@ -418,6 +419,8 @@ struct ContentView: View { .disabled(isRunning || hideDetails) Toggle("Mirror all-day events", isOn: $mirrorAllDay) .disabled(isRunning) + Toggle("Mirror accepted events only", isOn: $mirrorAcceptedOnly) + .disabled(isRunning) Picker("Overlap mode", selection: $overlapModeRaw) { ForEach(OverlapMode.allCases) { mode in Text(mode.rawValue).tag(mode.rawValue) @@ -755,13 +758,18 @@ struct ContentView: View { } // MARK: - Permissions & Calendars + @MainActor func requestAccess() { log("Requesting calendar access…") if #available(macOS 14.0, *) { store.requestFullAccessToEvents { granted, _ in DispatchQueue.main.async { hasAccess = granted - if granted { reloadCalendars() } + if granted { + // Reinitialize the store after permission changes to ensure sources load + store = EKEventStore() + reloadCalendars() + } log(granted ? "Access granted." : "Access denied.") } } @@ -769,13 +777,18 @@ struct ContentView: View { store.requestAccess(to: .event) { granted, _ in DispatchQueue.main.async { hasAccess = granted - if granted { reloadCalendars() } + if granted { + // Reinitialize the store after permission changes to ensure sources load + store = EKEventStore() + reloadCalendars() + } log(granted ? "Access granted." : "Access denied.") } } } } + @MainActor func reloadCalendars() { let fetched = store.calendars(for: .event) calendars = sortedCalendars(fetched) @@ -835,7 +848,22 @@ struct ContentView: View { let allowedEndMinutes = workHoursEnd * 60 var skippedWorkHours = 0 var skippedTitles = 0 + var skippedStatus = 0 for ev in srcEvents { + if mirrorAcceptedOnly, ev.hasAttendees { + // Only include events where the current user's attendee status is Accepted + let attendees = ev.attendees ?? [] + if let me = attendees.first(where: { $0.isCurrentUser }) { + if me.participantStatus != .accepted { + skippedStatus += 1 + continue + } + } else { + // If we cannot determine a self attendee, treat as not accepted + skippedStatus += 1 + continue + } + } if enforceWorkHours, !ev.isAllDay, let start = ev.startDate, isOutsideWorkHours(start, calendar: cal, startMinutes: allowedStartMinutes, endMinutes: allowedEndMinutes) { skippedWorkHours += 1 @@ -866,6 +894,9 @@ struct ContentView: View { if skippedTitles > 0 { log("- SKIP title filter: \(skippedTitles) event(s)") } + if skippedStatus > 0 { + log("- SKIP non-accepted status: \(skippedStatus) event(s)") + } // Deduplicate source blocks to avoid duplicates from EventKit (recurrences / sync races) srcBlocks = uniqueBlocks(srcBlocks, trackByID: mergeGapMin == 0) @@ -1128,6 +1159,7 @@ private struct SettingsPayload: Codable { var workHoursStart: Int = 9 var workHoursEnd: Int = 17 var excludedTitleFilters: [String] = [] + var mirrorAcceptedOnly: Bool = false var overlapMode: String var titlePrefix: String var placeholderTitle: String @@ -1150,6 +1182,7 @@ private struct SettingsPayload: Codable { workHoursStart: workHoursStart, workHoursEnd: workHoursEnd, excludedTitleFilters: excludedTitleFilterList, + mirrorAcceptedOnly: mirrorAcceptedOnly, overlapMode: overlapMode.rawValue, titlePrefix: titlePrefix, placeholderTitle: placeholderTitle, @@ -1172,6 +1205,7 @@ private struct SettingsPayload: Codable { workHoursStart = s.workHoursStart workHoursEnd = s.workHoursEnd excludedTitleFiltersRaw = s.excludedTitleFilters.joined(separator: "\n") + mirrorAcceptedOnly = s.mirrorAcceptedOnly overlapMode = OverlapMode(rawValue: s.overlapMode) ?? .allow titlePrefix = s.titlePrefix placeholderTitle = s.placeholderTitle diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a642c16 --- /dev/null +++ b/Makefile @@ -0,0 +1,46 @@ +# Simple build and package helpers for BusyMirror + +SCHEME ?= BusyMirror +PROJECT ?= BusyMirror.xcodeproj +DERIVED ?= build/DerivedData +DEST := platform=macOS + +# Extract marketing version from project settings +VERSION := $(shell sed -n 's/.*MARKETING_VERSION = \([0-9.]*\);.*/\1/p' $(PROJECT)/project.pbxproj | head -n1) + +.PHONY: all clean build-debug build-release open app package + +all: build-release + +clean: + @echo "Cleaning derived data…" + xcodebuild -scheme $(SCHEME) -project $(PROJECT) -derivedDataPath $(DERIVED) -destination '$(DEST)' CODE_SIGNING_ALLOWED=NO clean >/dev/null + @echo "Done." + +build-debug: + @echo "Building Debug…" + xcodebuild -scheme $(SCHEME) -project $(PROJECT) -configuration Debug -destination '$(DEST)' -derivedDataPath $(DERIVED) CODE_SIGNING_ALLOWED=NO build + +build-release: + @echo "Building Release…" + xcodebuild -scheme $(SCHEME) -project $(PROJECT) -configuration Release -destination '$(DEST)' -derivedDataPath $(DERIVED) CODE_SIGNING_ALLOWED=NO build + +# Convenience to open the built app in Finder +open: app + @open "$<" + +# Path to built app (Release) +APP_PATH := $(DERIVED)/Build/Products/Release/BusyMirror.app + +app: build-release + @# Ensure the app exists + @test -d "$(APP_PATH)" && echo "Built: $(APP_PATH)" || (echo "App not found at $(APP_PATH)" && exit 1) + @echo "Version: $(VERSION)" + @echo "OK" + +package: app + @echo "Packaging BusyMirror $(VERSION)…" + @zip -qry "BusyMirror-$(VERSION)-macOS.zip" "$(APP_PATH)" + @shasum -a 256 "BusyMirror-$(VERSION)-macOS.zip" | awk '{print $$1}' > "BusyMirror-$(VERSION)-macOS.zip.sha256" + @echo "Created BusyMirror-$(VERSION)-macOS.zip and .sha256" +