Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions OverflowBar/Services/MenuBarItemStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import Combine
@Published private(set) var selectedIDs: [String]
private let preferences: PreferencesStore
private let engine: MenuBarEngine
private let supportsHiding: Bool
private var records: [String: EngineItemRecord] = [:]
private var dividerFrame: CGRect?
private var refreshTask: Task<Void, Never>?
Expand All @@ -32,11 +33,13 @@ import Combine
private var observers: [NSObjectProtocol] = []
private var timer: Timer?
var onLayoutStateChanged: (() -> Void)?
init(preferences: PreferencesStore = PreferencesStore(), engine: MenuBarEngine? = nil) {
init(preferences: PreferencesStore = PreferencesStore(), engine: MenuBarEngine? = nil, supportsHiding: Bool = MenuBarGroupGeometry.supportsHiding(osMajorVersion: ProcessInfo.processInfo.operatingSystemVersion.majorVersion)) {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
self.preferences = preferences
self.supportsHiding = supportsHiding
self.engine = engine ?? NativeMenuBarEngine()
selectedIDs = preferences.selectedNativeItems
layoutManagementEnabled = preferences.layoutManagementEnabled
layoutManagementEnabled = supportsHiding && preferences.layoutManagementEnabled
if !supportsHiding { preferences.layoutManagementEnabled = false }
self.engine.visibility = { [weak self] hidden in
self?.groupHidden = hidden; self?.onLayoutStateChanged?()
}
Expand Down Expand Up @@ -139,20 +142,28 @@ import Combine
guard layoutTask == nil, operationProgress == nil else { return }
selectedIDs = ids
preferences.selectedNativeItems = ids
layoutManagementEnabled = hideSelectedIcons && !ids.isEmpty
layoutManagementEnabled = supportsHiding && hideSelectedIcons && !ids.isEmpty
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
preferences.layoutManagementEnabled = layoutManagementEnabled
automaticManagementSuspended = !layoutManagementEnabled
if layoutManagementEnabled || groupHidden { applyLayout(automatic: false) }
else { layoutOperationMessage = "Choose the icons to place in the second row." }
else { layoutOperationMessage = supportsHiding ? "Choose the icons to place in the second row." : "Hiding icons is unavailable on this macOS version." }
}
func setLayoutManagementEnabled(_ enabled: Bool) {
if !supportsHiding {
layoutManagementEnabled = false
preferences.layoutManagementEnabled = false
automaticManagementSuspended = true
layoutOperationMessage = "Hiding icons is unavailable on this macOS version."
return
}
if !enabled { cancelOperation() }
layoutManagementEnabled = enabled && !selectedIDs.isEmpty
automaticManagementSuspended = !layoutManagementEnabled
preferences.layoutManagementEnabled = layoutManagementEnabled
applyLayout(automatic: false)
}
private func applyLayout(automatic: Bool) {
guard supportsHiding else { return }
guard layoutTask == nil else { layoutOperationMessage = "A layout operation is already running. Cancel it before changing selection."; return }
operationProgress = "Finding menu bar icons"
let selection = selectedIDs
Expand Down
35 changes: 28 additions & 7 deletions OverflowBarTests/MenuBarItemStoreTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ final class MenuBarItemStoreTests: XCTestCase {
defer { UserDefaults.standard.removePersistentDomain(forName: suite) }
preferences.layoutManagementEnabled = true
let engine = StoreTestEngine(items: [item("existing")])
let store = MenuBarItemStore(preferences: preferences, engine: engine)
let store = MenuBarItemStore(preferences: preferences, engine: engine, supportsHiding: true)

store.refresh()
await waitUntil { preferences.knownNativeItems != nil }
Expand All @@ -25,7 +25,7 @@ final class MenuBarItemStoreTests: XCTestCase {
preferences.layoutManagementEnabled = true
preferences.verifiedNativeOrder = ["existing"]
let engine = StoreTestEngine(items: [item("existing"), item("new")])
let store = MenuBarItemStore(preferences: preferences, engine: engine)
let store = MenuBarItemStore(preferences: preferences, engine: engine, supportsHiding: true)

store.refresh()
await waitUntil { engine.appliedSelections.count == 1 }
Expand All @@ -36,7 +36,7 @@ final class MenuBarItemStoreTests: XCTestCase {
XCTAssertEqual(engine.appliedSelections, [["new"]])

let relaunchedEngine = StoreTestEngine(items: [item("existing"), item("new")])
let relaunchedStore = MenuBarItemStore(preferences: preferences, engine: relaunchedEngine)
let relaunchedStore = MenuBarItemStore(preferences: preferences, engine: relaunchedEngine, supportsHiding: true)
relaunchedStore.refresh()
await waitUntil { relaunchedStore.items.count == 2 }
XCTAssertTrue(relaunchedEngine.appliedSelections.isEmpty)
Expand All @@ -46,7 +46,7 @@ final class MenuBarItemStoreTests: XCTestCase {
let (preferences, suite) = makePreferences()
defer { UserDefaults.standard.removePersistentDomain(forName: suite) }
let engine = StoreTestEngine(items: [item("existing")])
let store = MenuBarItemStore(preferences: preferences, engine: engine)
let store = MenuBarItemStore(preferences: preferences, engine: engine, supportsHiding: true)
store.refresh()
await waitUntil { preferences.knownNativeItems != nil }

Expand All @@ -63,7 +63,7 @@ final class MenuBarItemStoreTests: XCTestCase {
defer { UserDefaults.standard.removePersistentDomain(forName: suite) }
preferences.layoutManagementEnabled = true
let engine = StoreTestEngine(items: [item("existing")])
let store = MenuBarItemStore(preferences: preferences, engine: engine)
let store = MenuBarItemStore(preferences: preferences, engine: engine, supportsHiding: true)
store.refresh()
await waitUntil { preferences.knownNativeItems != nil }

Expand All @@ -83,7 +83,7 @@ final class MenuBarItemStoreTests: XCTestCase {
preferences.layoutManagementEnabled = true
let engine = StoreTestEngine(items: [])
engine.waitForCancel = true
let store = MenuBarItemStore(preferences: preferences, engine: engine)
let store = MenuBarItemStore(preferences: preferences, engine: engine, supportsHiding: true)

store.setLayoutManagementEnabled(true)
await waitUntil { engine.applyCount == 1 }
Expand All @@ -96,6 +96,26 @@ final class MenuBarItemStoreTests: XCTestCase {
XCTAssertTrue(preferences.layoutManagementEnabled)
}

func testUnsupportedSystemDisablesSavedLayoutWithoutReentrantCancellation() {
let (preferences, suite) = makePreferences()
defer { UserDefaults.standard.removePersistentDomain(forName: suite) }
preferences.selectedNativeItems = ["selected"]
preferences.layoutManagementEnabled = true
let engine = StoreTestEngine(items: [])
let store = MenuBarItemStore(preferences: preferences, engine: engine, supportsHiding: false)
store.onLayoutStateChanged = { store.setLayoutManagementEnabled(false) }
engine.visibility?(false)
store.setLayoutManagementEnabled(false)
store.setLayoutManagementEnabled(true)
store.configureSelection(["selected"], hideSelectedIcons: true)
XCTAssertEqual(engine.cancelCount, 0)
XCTAssertEqual(store.layoutOperationMessage, "Hiding icons is unavailable on this macOS version.")
XCTAssertFalse(store.layoutManagementEnabled)
XCTAssertFalse(preferences.layoutManagementEnabled)
XCTAssertFalse(store.groupHidden)
XCTAssertTrue(engine.appliedSelections.isEmpty)
}

private func makePreferences() -> (PreferencesStore, String) {
let suite = "OverflowBarTests.\(UUID().uuidString)"
return (PreferencesStore(defaults: UserDefaults(suiteName: suite)!), suite)
Expand Down Expand Up @@ -123,6 +143,7 @@ private final class StoreTestEngine: MenuBarEngine {
private(set) var maximumConcurrentApplies = 0
private var activeApplies = 0
var waitForCancel = false
private(set) var cancelCount = 0
private var applyContinuation: CheckedContinuation<Void, Never>?
var visibility: ((Bool) -> Void)?
var message: ((String) -> Void)?
Expand All @@ -142,5 +163,5 @@ private final class StoreTestEngine: MenuBarEngine {
}
func capture(_ items: [EngineItemRecord]) async -> [EngineCaptureFrame] { [] }
func activate(_ id: String, button: CGMouseButton) async -> MenuBarActivationResult { .activated }
func cancel() { applyContinuation?.resume(); applyContinuation = nil; visibility?(false) }
func cancel() { cancelCount += 1; applyContinuation?.resume(); applyContinuation = nil; visibility?(false) }
}
4 changes: 2 additions & 2 deletions OverflowBarTests/OnboardingFlowTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ final class OnboardingFlowTests: XCTestCase {
let defaults = UserDefaults(suiteName: suite)!
defer { defaults.removePersistentDomain(forName: suite) }
let preferences = PreferencesStore(defaults: defaults)
let store = MenuBarItemStore(preferences: preferences)
let store = MenuBarItemStore(preferences: preferences, supportsHiding: true)

OnboardingCompletion.finish(selectedIDs: ["menu-item-1", "menu-item-2"], hideSelectedIcons: false).apply(to: store)

Expand All @@ -60,7 +60,7 @@ final class OnboardingFlowTests: XCTestCase {
let preferences = PreferencesStore(defaults: defaults)
preferences.selectedNativeItems = ["previously-hidden"]
preferences.layoutManagementEnabled = true
let store = MenuBarItemStore(preferences: preferences)
let store = MenuBarItemStore(preferences: preferences, supportsHiding: true)

OnboardingCompletion.setUpLater.apply(to: store)

Expand Down
Loading