diff --git a/NotchIA.xcodeproj/project.pbxproj b/NotchIA.xcodeproj/project.pbxproj index 31e0426..dd71f50 100644 --- a/NotchIA.xcodeproj/project.pbxproj +++ b/NotchIA.xcodeproj/project.pbxproj @@ -1277,7 +1277,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 20800; + CURRENT_PROJECT_VERSION = 20801; DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = NO; GENERATE_INFOPLIST_FILE = YES; @@ -1285,7 +1285,7 @@ INFOPLIST_KEY_CFBundleDisplayName = NotchIAXPCHelper; INFOPLIST_KEY_NSHumanReadableCopyright = ""; MACOSX_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 2.8.0; + MARKETING_VERSION = 2.8.1; PRODUCT_BUNDLE_IDENTIFIER = com.coaxel2.notchia.NotchIAXPCHelper; PRODUCT_NAME = "$(TARGET_NAME)"; REGISTER_APP_GROUPS = YES; @@ -1303,7 +1303,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 20800; + CURRENT_PROJECT_VERSION = 20801; DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = NO; GENERATE_INFOPLIST_FILE = YES; @@ -1311,7 +1311,7 @@ INFOPLIST_KEY_CFBundleDisplayName = NotchIAXPCHelper; INFOPLIST_KEY_NSHumanReadableCopyright = ""; MACOSX_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 2.8.0; + MARKETING_VERSION = 2.8.1; PRODUCT_BUNDLE_IDENTIFIER = com.coaxel2.notchia.NotchIAXPCHelper; PRODUCT_NAME = "$(TARGET_NAME)"; REGISTER_APP_GROUPS = YES; @@ -1462,7 +1462,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 20800; + CURRENT_PROJECT_VERSION = 20801; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = "\"NotchIA/Preview Content\""; DEVELOPMENT_TEAM = ""; @@ -1490,7 +1490,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 2.8.0; + MARKETING_VERSION = 2.8.1; PRODUCT_BUNDLE_IDENTIFIER = com.coaxel2.notchia; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1518,7 +1518,7 @@ CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = YES; - CURRENT_PROJECT_VERSION = 20800; + CURRENT_PROJECT_VERSION = 20801; DEAD_CODE_STRIPPING = YES; DEPLOYMENT_POSTPROCESSING = YES; DEVELOPMENT_ASSET_PATHS = "\"NotchIA/Preview Content\""; @@ -1546,7 +1546,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 2.8.0; + MARKETING_VERSION = 2.8.1; PRODUCT_BUNDLE_IDENTIFIER = com.coaxel2.notchia; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/NotchIA/ContentView.swift b/NotchIA/ContentView.swift index 58bd590..7622b94 100644 --- a/NotchIA/ContentView.swift +++ b/NotchIA/ContentView.swift @@ -607,6 +607,30 @@ struct ContentView: View { } .foregroundStyle(.gray) .padding(.bottom, 10) + } else if coordinator.sneakPeek.type == .focus { + // Peek 2.5s à l'activation/désactivation d'un Mode Focus. + // value == 1.0 → activé (icône Focus + label), value == 0 → désactivé. + HStack(spacing: 6) { + Image(systemName: coordinator.sneakPeek.icon.isEmpty ? "moon.fill" : coordinator.sneakPeek.icon) + .font(.system(size: 11, weight: .semibold)) + .foregroundStyle(coordinator.sneakPeek.value > 0 ? .purple : .gray) + Text( + coordinator.sneakPeek.value > 0 + ? String(localized: "\(focusModeManager.focusLabel) activé") + : String(localized: "Focus désactivé") + ) + .font(.system(size: 11, weight: .medium, design: .rounded)) + .foregroundStyle(.white.opacity(0.9)) + .lineLimit(1) + } + .padding(.horizontal, 10) + .padding(.vertical, 4) + .background( + Capsule() + .fill(Color.black) + .stroke(Color.white.opacity(0.1), lineWidth: 1) + ) + .padding(.bottom, 10) } } } diff --git a/NotchIA/Localizable.xcstrings b/NotchIA/Localizable.xcstrings index 9811dd6..b006f46 100644 --- a/NotchIA/Localizable.xcstrings +++ b/NotchIA/Localizable.xcstrings @@ -42695,7 +42695,63 @@ } } }, - "Σ": {} + "Σ": {}, + "%@ activé": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "%@ on" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "%@ activé" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "%@ activado" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "%@ aktiv" + } + } + } + }, + "Focus désactivé": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Focus off" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Focus désactivé" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Focus desactivado" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Focus aus" + } + } + } + } }, "version": "1.0" } diff --git a/NotchIA/NotchIAViewCoordinator.swift b/NotchIA/NotchIAViewCoordinator.swift index 8b500b2..5a2b98c 100644 --- a/NotchIA/NotchIAViewCoordinator.swift +++ b/NotchIA/NotchIAViewCoordinator.swift @@ -23,6 +23,9 @@ enum SneakContentType { case download case pomodoro case clipboard + /// Focus / Ne pas déranger activé ou désactivé. Déclenché par + /// `FocusModeManager` quand `isActive` change. Durée du peek 2.5s. + case focus } struct sneakPeek { @@ -251,7 +254,10 @@ class NotchIAViewCoordinator: ObservableObject { icon: String = "" ) { sneakPeekDuration = duration - if type != .music && type != .pomodoro && type != .screenRecording { + // Le peek `.focus` est une notification d'état (activation/désactivation + // d'un Mode Focus), pas un HUD de remplacement — il s'affiche peu importe + // le réglage hudReplacement, comme music/pomodoro/screenRecording. + if type != .music && type != .pomodoro && type != .screenRecording && type != .focus { // close() if !Defaults[.hudReplacement] { return diff --git a/NotchIA/managers/FocusModeManager.swift b/NotchIA/managers/FocusModeManager.swift index cab1282..517593a 100644 --- a/NotchIA/managers/FocusModeManager.swift +++ b/NotchIA/managers/FocusModeManager.swift @@ -136,6 +136,25 @@ final class FocusModeManager: ObservableObject { } } .store(in: &cancellables) + + // Peek 2.5s quand le mode Focus change (on/off). dropFirst() évite + // de fire au boot quand l'état initial est calculé. removeDuplicates + // dédoublonne les notifications privées qui se déclenchent en rafale. + $isActive + .dropFirst() + .removeDuplicates() + .sink { [weak self] active in + guard let self else { return } + guard Defaults[.enableFocusModeDetection] else { return } + NotchIAViewCoordinator.shared.toggleSneakPeek( + status: true, + type: .focus, + duration: 2.5, + value: active ? 1.0 : 0.0, + icon: active ? self.focusIcon : "moon.slash.fill" + ) + } + .store(in: &cancellables) } private func startMonitoring() {