BusyMirror 1.2.1: fix calendar loading after grant, attendee status filter, main-actor; add Makefile
This commit is contained in:
@@ -410,7 +410,7 @@
|
|||||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
|
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 3;
|
CURRENT_PROJECT_VERSION = 4;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = BusyMirror/Info.plist;
|
INFOPLIST_FILE = BusyMirror/Info.plist;
|
||||||
@@ -421,7 +421,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.2.0;
|
MARKETING_VERSION = 1.2.1;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.cqrenet.BusyMirror;
|
PRODUCT_BUNDLE_IDENTIFIER = com.cqrenet.BusyMirror;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
REGISTER_APP_GROUPS = YES;
|
REGISTER_APP_GROUPS = YES;
|
||||||
@@ -440,7 +440,7 @@
|
|||||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
|
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 3;
|
CURRENT_PROJECT_VERSION = 4;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = BusyMirror/Info.plist;
|
INFOPLIST_FILE = BusyMirror/Info.plist;
|
||||||
@@ -451,7 +451,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.2.0;
|
MARKETING_VERSION = 1.2.1;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.cqrenet.BusyMirror;
|
PRODUCT_BUNDLE_IDENTIFIER = com.cqrenet.BusyMirror;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
REGISTER_APP_GROUPS = YES;
|
REGISTER_APP_GROUPS = YES;
|
||||||
|
@@ -132,6 +132,7 @@ struct ContentView: View {
|
|||||||
@AppStorage("workHoursStart") private var workHoursStart: Int = 9
|
@AppStorage("workHoursStart") private var workHoursStart: Int = 9
|
||||||
@AppStorage("workHoursEnd") private var workHoursEnd: Int = 17
|
@AppStorage("workHoursEnd") private var workHoursEnd: Int = 17
|
||||||
@AppStorage("excludedTitleFilters") private var excludedTitleFiltersRaw: String = ""
|
@AppStorage("excludedTitleFilters") private var excludedTitleFiltersRaw: String = ""
|
||||||
|
@AppStorage("mirrorAcceptedOnly") private var mirrorAcceptedOnly: Bool = false
|
||||||
var overlapMode: OverlapMode {
|
var overlapMode: OverlapMode {
|
||||||
get { OverlapMode(rawValue: overlapModeRaw) ?? .allow }
|
get { OverlapMode(rawValue: overlapModeRaw) ?? .allow }
|
||||||
nonmutating set { overlapModeRaw = newValue.rawValue }
|
nonmutating set { overlapModeRaw = newValue.rawValue }
|
||||||
@@ -418,6 +419,8 @@ struct ContentView: View {
|
|||||||
.disabled(isRunning || hideDetails)
|
.disabled(isRunning || hideDetails)
|
||||||
Toggle("Mirror all-day events", isOn: $mirrorAllDay)
|
Toggle("Mirror all-day events", isOn: $mirrorAllDay)
|
||||||
.disabled(isRunning)
|
.disabled(isRunning)
|
||||||
|
Toggle("Mirror accepted events only", isOn: $mirrorAcceptedOnly)
|
||||||
|
.disabled(isRunning)
|
||||||
Picker("Overlap mode", selection: $overlapModeRaw) {
|
Picker("Overlap mode", selection: $overlapModeRaw) {
|
||||||
ForEach(OverlapMode.allCases) { mode in
|
ForEach(OverlapMode.allCases) { mode in
|
||||||
Text(mode.rawValue).tag(mode.rawValue)
|
Text(mode.rawValue).tag(mode.rawValue)
|
||||||
@@ -755,13 +758,18 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Permissions & Calendars
|
// MARK: - Permissions & Calendars
|
||||||
|
@MainActor
|
||||||
func requestAccess() {
|
func requestAccess() {
|
||||||
log("Requesting calendar access…")
|
log("Requesting calendar access…")
|
||||||
if #available(macOS 14.0, *) {
|
if #available(macOS 14.0, *) {
|
||||||
store.requestFullAccessToEvents { granted, _ in
|
store.requestFullAccessToEvents { granted, _ in
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
hasAccess = granted
|
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.")
|
log(granted ? "Access granted." : "Access denied.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -769,13 +777,18 @@ struct ContentView: View {
|
|||||||
store.requestAccess(to: .event) { granted, _ in
|
store.requestAccess(to: .event) { granted, _ in
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
hasAccess = granted
|
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.")
|
log(granted ? "Access granted." : "Access denied.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func reloadCalendars() {
|
func reloadCalendars() {
|
||||||
let fetched = store.calendars(for: .event)
|
let fetched = store.calendars(for: .event)
|
||||||
calendars = sortedCalendars(fetched)
|
calendars = sortedCalendars(fetched)
|
||||||
@@ -835,7 +848,22 @@ struct ContentView: View {
|
|||||||
let allowedEndMinutes = workHoursEnd * 60
|
let allowedEndMinutes = workHoursEnd * 60
|
||||||
var skippedWorkHours = 0
|
var skippedWorkHours = 0
|
||||||
var skippedTitles = 0
|
var skippedTitles = 0
|
||||||
|
var skippedStatus = 0
|
||||||
for ev in srcEvents {
|
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,
|
if enforceWorkHours, !ev.isAllDay, let start = ev.startDate,
|
||||||
isOutsideWorkHours(start, calendar: cal, startMinutes: allowedStartMinutes, endMinutes: allowedEndMinutes) {
|
isOutsideWorkHours(start, calendar: cal, startMinutes: allowedStartMinutes, endMinutes: allowedEndMinutes) {
|
||||||
skippedWorkHours += 1
|
skippedWorkHours += 1
|
||||||
@@ -866,6 +894,9 @@ struct ContentView: View {
|
|||||||
if skippedTitles > 0 {
|
if skippedTitles > 0 {
|
||||||
log("- SKIP title filter: \(skippedTitles) event(s)")
|
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)
|
// Deduplicate source blocks to avoid duplicates from EventKit (recurrences / sync races)
|
||||||
srcBlocks = uniqueBlocks(srcBlocks, trackByID: mergeGapMin == 0)
|
srcBlocks = uniqueBlocks(srcBlocks, trackByID: mergeGapMin == 0)
|
||||||
|
|
||||||
@@ -1128,6 +1159,7 @@ private struct SettingsPayload: Codable {
|
|||||||
var workHoursStart: Int = 9
|
var workHoursStart: Int = 9
|
||||||
var workHoursEnd: Int = 17
|
var workHoursEnd: Int = 17
|
||||||
var excludedTitleFilters: [String] = []
|
var excludedTitleFilters: [String] = []
|
||||||
|
var mirrorAcceptedOnly: Bool = false
|
||||||
var overlapMode: String
|
var overlapMode: String
|
||||||
var titlePrefix: String
|
var titlePrefix: String
|
||||||
var placeholderTitle: String
|
var placeholderTitle: String
|
||||||
@@ -1150,6 +1182,7 @@ private struct SettingsPayload: Codable {
|
|||||||
workHoursStart: workHoursStart,
|
workHoursStart: workHoursStart,
|
||||||
workHoursEnd: workHoursEnd,
|
workHoursEnd: workHoursEnd,
|
||||||
excludedTitleFilters: excludedTitleFilterList,
|
excludedTitleFilters: excludedTitleFilterList,
|
||||||
|
mirrorAcceptedOnly: mirrorAcceptedOnly,
|
||||||
overlapMode: overlapMode.rawValue,
|
overlapMode: overlapMode.rawValue,
|
||||||
titlePrefix: titlePrefix,
|
titlePrefix: titlePrefix,
|
||||||
placeholderTitle: placeholderTitle,
|
placeholderTitle: placeholderTitle,
|
||||||
@@ -1172,6 +1205,7 @@ private struct SettingsPayload: Codable {
|
|||||||
workHoursStart = s.workHoursStart
|
workHoursStart = s.workHoursStart
|
||||||
workHoursEnd = s.workHoursEnd
|
workHoursEnd = s.workHoursEnd
|
||||||
excludedTitleFiltersRaw = s.excludedTitleFilters.joined(separator: "\n")
|
excludedTitleFiltersRaw = s.excludedTitleFilters.joined(separator: "\n")
|
||||||
|
mirrorAcceptedOnly = s.mirrorAcceptedOnly
|
||||||
overlapMode = OverlapMode(rawValue: s.overlapMode) ?? .allow
|
overlapMode = OverlapMode(rawValue: s.overlapMode) ?? .allow
|
||||||
titlePrefix = s.titlePrefix
|
titlePrefix = s.titlePrefix
|
||||||
placeholderTitle = s.placeholderTitle
|
placeholderTitle = s.placeholderTitle
|
||||||
|
46
Makefile
Normal file
46
Makefile
Normal file
@@ -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"
|
||||||
|
|
Reference in New Issue
Block a user