Update Live Activities Tab

This commit is contained in:
Hariharan Mudaliar 2025-12-03 21:05:03 +05:30
commit 43204ff93f
2 changed files with 198 additions and 140 deletions

View file

@ -5692,6 +5692,10 @@
} }
} }
}, },
"Configure countdown style and lock screen widgets in the Calendar tab." : {
"comment" : "A footer for the \"Enable reminder live activity\" toggle in the Live Activities settings.",
"isCommentAutoGenerated" : true
},
"Configure how the timer looks inside the closed notch. Progress can render as a ring around the icon or as horizontal bars." : { "Configure how the timer looks inside the closed notch. Progress can render as a ring around the icon or as horizontal bars." : {
}, },
@ -10831,6 +10835,10 @@
"comment" : "A label indicating that the music is live.", "comment" : "A label indicating that the music is live.",
"isCommentAutoGenerated" : true "isCommentAutoGenerated" : true
}, },
"Live Activities" : {
"comment" : "The title of the \"Live Activities\" screen.",
"isCommentAutoGenerated" : true
},
"Live Activity & Feedback" : { "Live Activity & Feedback" : {
"comment" : "The header for the \"Live Activity & Feedback\" section in the lock screen settings.", "comment" : "The header for the \"Live Activity & Feedback\" section in the lock screen settings.",
"isCommentAutoGenerated" : true "isCommentAutoGenerated" : true
@ -11944,6 +11952,10 @@
} }
} }
}, },
"Media Live Activity" : {
"comment" : "A section header that lists the media live activity settings.",
"isCommentAutoGenerated" : true
},
"Media output" : { "Media output" : {
"comment" : "Accessibility label describing the control that opens the media output selector.", "comment" : "Accessibility label describing the control that opens the media output selector.",
"isCommentAutoGenerated" : true "isCommentAutoGenerated" : true
@ -19687,6 +19699,10 @@
} }
} }
}, },
"Use the Media tab to configure sneak peek, lyrics, and floating media controls." : {
"comment" : "A footer for the \"Media Live Activity\" section, explaining how to configure the media controls.",
"isCommentAutoGenerated" : true
},
"Used" : { "Used" : {
"comment" : "A label describing the percentage of memory that is currently in use.", "comment" : "A label describing the percentage of memory that is currently in use.",
"isCommentAutoGenerated" : true, "isCommentAutoGenerated" : true,

View file

@ -18,6 +18,7 @@ import UniformTypeIdentifiers
private enum SettingsTab: String, CaseIterable, Identifiable { private enum SettingsTab: String, CaseIterable, Identifiable {
case general case general
case liveActivities
case appearance case appearance
case lockScreen case lockScreen
case media case media
@ -41,6 +42,7 @@ private enum SettingsTab: String, CaseIterable, Identifiable {
var title: String { var title: String {
switch self { switch self {
case .general: return "General" case .general: return "General"
case .liveActivities: return "Live Activities"
case .appearance: return "Appearance" case .appearance: return "Appearance"
case .lockScreen: return "Lock Screen" case .lockScreen: return "Lock Screen"
case .media: return "Media" case .media: return "Media"
@ -64,6 +66,7 @@ private enum SettingsTab: String, CaseIterable, Identifiable {
var systemImage: String { var systemImage: String {
switch self { switch self {
case .general: return "gear" case .general: return "gear"
case .liveActivities: return "waveform.path.ecg"
case .appearance: return "paintpalette" case .appearance: return "paintpalette"
case .lockScreen: return "lock.laptopcomputer" case .lockScreen: return "lock.laptopcomputer"
case .media: return "play.laptopcomputer" case .media: return "play.laptopcomputer"
@ -87,6 +90,7 @@ private enum SettingsTab: String, CaseIterable, Identifiable {
var tint: Color { var tint: Color {
switch self { switch self {
case .general: return .blue case .general: return .blue
case .liveActivities: return .pink
case .appearance: return .purple case .appearance: return .purple
case .lockScreen: return .orange case .lockScreen: return .orange
case .media: return .green case .media: return .green
@ -362,6 +366,7 @@ struct SettingsView: View {
private var availableTabs: [SettingsTab] { private var availableTabs: [SettingsTab] {
let ordered: [SettingsTab] = [ let ordered: [SettingsTab] = [
.general, .general,
.liveActivities,
.appearance, .appearance,
.lockScreen, .lockScreen,
.media, .media,
@ -554,13 +559,6 @@ struct SettingsView: View {
SettingsSearchEntry(tab: .general, title: "Show on a specific display", keywords: ["preferred screen", "display picker"], highlightID: SettingsTab.general.highlightID(for: "Show on a specific display")), SettingsSearchEntry(tab: .general, title: "Show on a specific display", keywords: ["preferred screen", "display picker"], highlightID: SettingsTab.general.highlightID(for: "Show on a specific display")),
SettingsSearchEntry(tab: .general, title: "Automatically switch displays", keywords: ["auto switch", "displays"], highlightID: SettingsTab.general.highlightID(for: "Automatically switch displays")), SettingsSearchEntry(tab: .general, title: "Automatically switch displays", keywords: ["auto switch", "displays"], highlightID: SettingsTab.general.highlightID(for: "Automatically switch displays")),
SettingsSearchEntry(tab: .general, title: "Hide Dynamic Island during screenshots & recordings", keywords: ["privacy", "screenshot", "recording"], highlightID: SettingsTab.general.highlightID(for: "Hide Dynamic Island during screenshots & recordings")), SettingsSearchEntry(tab: .general, title: "Hide Dynamic Island during screenshots & recordings", keywords: ["privacy", "screenshot", "recording"], highlightID: SettingsTab.general.highlightID(for: "Hide Dynamic Island during screenshots & recordings")),
SettingsSearchEntry(tab: .general, title: "Enable Screen Recording Detection", keywords: ["screen recording", "indicator"], highlightID: SettingsTab.general.highlightID(for: "Enable Screen Recording Detection")),
SettingsSearchEntry(tab: .general, title: "Show Recording Indicator", keywords: ["recording indicator", "red dot"], highlightID: SettingsTab.general.highlightID(for: "Show Recording Indicator")),
SettingsSearchEntry(tab: .general, title: "Enable Focus Detection", keywords: ["focus", "do not disturb", "dnd"], highlightID: SettingsTab.general.highlightID(for: "Enable Focus Detection")),
SettingsSearchEntry(tab: .general, title: "Show Focus Indicator", keywords: ["focus icon", "moon"], highlightID: SettingsTab.general.highlightID(for: "Show Focus Indicator")),
SettingsSearchEntry(tab: .general, title: "Show Focus Label", keywords: ["focus label", "text"], highlightID: SettingsTab.general.highlightID(for: "Show Focus Label")),
SettingsSearchEntry(tab: .general, title: "Enable Camera Detection", keywords: ["camera", "privacy indicator"], highlightID: SettingsTab.general.highlightID(for: "Enable Camera Detection")),
SettingsSearchEntry(tab: .general, title: "Enable Microphone Detection", keywords: ["microphone", "privacy"], highlightID: SettingsTab.general.highlightID(for: "Enable Microphone Detection")),
SettingsSearchEntry(tab: .general, title: "Enable gestures", keywords: ["gestures", "trackpad"], highlightID: SettingsTab.general.highlightID(for: "Enable gestures")), SettingsSearchEntry(tab: .general, title: "Enable gestures", keywords: ["gestures", "trackpad"], highlightID: SettingsTab.general.highlightID(for: "Enable gestures")),
SettingsSearchEntry(tab: .general, title: "Close gesture", keywords: ["pinch", "swipe"], highlightID: SettingsTab.general.highlightID(for: "Close gesture")), SettingsSearchEntry(tab: .general, title: "Close gesture", keywords: ["pinch", "swipe"], highlightID: SettingsTab.general.highlightID(for: "Close gesture")),
SettingsSearchEntry(tab: .general, title: "Extend hover area", keywords: ["hover", "cursor"], highlightID: SettingsTab.general.highlightID(for: "Extend hover area")), SettingsSearchEntry(tab: .general, title: "Extend hover area", keywords: ["hover", "cursor"], highlightID: SettingsTab.general.highlightID(for: "Extend hover area")),
@ -568,6 +566,17 @@ struct SettingsView: View {
SettingsSearchEntry(tab: .general, title: "Open notch on hover", keywords: ["hover to open", "auto open"], highlightID: SettingsTab.general.highlightID(for: "Open notch on hover")), SettingsSearchEntry(tab: .general, title: "Open notch on hover", keywords: ["hover to open", "auto open"], highlightID: SettingsTab.general.highlightID(for: "Open notch on hover")),
SettingsSearchEntry(tab: .general, title: "Notch display height", keywords: ["display height", "menu bar size"], highlightID: SettingsTab.general.highlightID(for: "Notch display height")), SettingsSearchEntry(tab: .general, title: "Notch display height", keywords: ["display height", "menu bar size"], highlightID: SettingsTab.general.highlightID(for: "Notch display height")),
// Live Activities
SettingsSearchEntry(tab: .liveActivities, title: "Enable Screen Recording Detection", keywords: ["screen recording", "indicator"], highlightID: SettingsTab.liveActivities.highlightID(for: "Enable Screen Recording Detection")),
SettingsSearchEntry(tab: .liveActivities, title: "Show Recording Indicator", keywords: ["recording indicator", "red dot"], highlightID: SettingsTab.liveActivities.highlightID(for: "Show Recording Indicator")),
SettingsSearchEntry(tab: .liveActivities, title: "Enable Focus Detection", keywords: ["focus", "do not disturb", "dnd"], highlightID: SettingsTab.liveActivities.highlightID(for: "Enable Focus Detection")),
SettingsSearchEntry(tab: .liveActivities, title: "Show Focus Indicator", keywords: ["focus icon", "moon"], highlightID: SettingsTab.liveActivities.highlightID(for: "Show Focus Indicator")),
SettingsSearchEntry(tab: .liveActivities, title: "Show Focus Label", keywords: ["focus label", "text"], highlightID: SettingsTab.liveActivities.highlightID(for: "Show Focus Label")),
SettingsSearchEntry(tab: .liveActivities, title: "Enable Camera Detection", keywords: ["camera", "privacy indicator"], highlightID: SettingsTab.liveActivities.highlightID(for: "Enable Camera Detection")),
SettingsSearchEntry(tab: .liveActivities, title: "Enable Microphone Detection", keywords: ["microphone", "privacy"], highlightID: SettingsTab.liveActivities.highlightID(for: "Enable Microphone Detection")),
SettingsSearchEntry(tab: .liveActivities, title: "Enable music live activity", keywords: ["music", "now playing"], highlightID: SettingsTab.liveActivities.highlightID(for: "Enable music live activity")),
SettingsSearchEntry(tab: .liveActivities, title: "Enable reminder live activity", keywords: ["reminder", "live activity"], highlightID: SettingsTab.liveActivities.highlightID(for: "Enable reminder live activity")),
// Battery (Charge) // Battery (Charge)
SettingsSearchEntry(tab: .battery, title: "Show battery indicator", keywords: ["battery hud", "charge"], highlightID: SettingsTab.battery.highlightID(for: "Show battery indicator")), SettingsSearchEntry(tab: .battery, title: "Show battery indicator", keywords: ["battery hud", "charge"], highlightID: SettingsTab.battery.highlightID(for: "Show battery indicator")),
SettingsSearchEntry(tab: .battery, title: "Show battery percentage", keywords: ["battery percent"], highlightID: SettingsTab.battery.highlightID(for: "Show battery percentage")), SettingsSearchEntry(tab: .battery, title: "Show battery percentage", keywords: ["battery percent"], highlightID: SettingsTab.battery.highlightID(for: "Show battery percentage")),
@ -719,6 +728,10 @@ struct SettingsView: View {
SettingsForm(tab: .general) { SettingsForm(tab: .general) {
GeneralSettings() GeneralSettings()
} }
case .liveActivities:
SettingsForm(tab: .liveActivities) {
LiveActivitiesSettings()
}
case .appearance: case .appearance:
SettingsForm(tab: .appearance) { SettingsForm(tab: .appearance) {
Appearance() Appearance()
@ -819,10 +832,6 @@ struct GeneralSettings: View {
@State private var screens: [String] = NSScreen.screens.compactMap { $0.localizedName } @State private var screens: [String] = NSScreen.screens.compactMap { $0.localizedName }
@EnvironmentObject var vm: DynamicIslandViewModel @EnvironmentObject var vm: DynamicIslandViewModel
@ObservedObject var coordinator = DynamicIslandViewCoordinator.shared @ObservedObject var coordinator = DynamicIslandViewCoordinator.shared
@ObservedObject var recordingManager = ScreenRecordingManager.shared
@ObservedObject var privacyManager = PrivacyIndicatorManager.shared
@ObservedObject var doNotDisturbManager = DoNotDisturbManager.shared
@Default(.mirrorShape) var mirrorShape @Default(.mirrorShape) var mirrorShape
@Default(.showEmojis) var showEmojis @Default(.showEmojis) var showEmojis
@Default(.gestureSensitivity) var gestureSensitivity @Default(.gestureSensitivity) var gestureSensitivity
@ -835,11 +844,6 @@ struct GeneralSettings: View {
@Default(.automaticallySwitchDisplay) var automaticallySwitchDisplay @Default(.automaticallySwitchDisplay) var automaticallySwitchDisplay
@Default(.enableGestures) var enableGestures @Default(.enableGestures) var enableGestures
@Default(.openNotchOnHover) var openNotchOnHover @Default(.openNotchOnHover) var openNotchOnHover
@Default(.enableScreenRecordingDetection) var enableScreenRecordingDetection
@Default(.showRecordingIndicator) var showRecordingIndicator
@Default(.enableDoNotDisturbDetection) var enableDoNotDisturbDetection
@Default(.showDoNotDisturbIndicator) var showDoNotDisturbIndicator
@Default(.showDoNotDisturbLabel) var showDoNotDisturbLabel
@Default(.enableMinimalisticUI) var enableMinimalisticUI @Default(.enableMinimalisticUI) var enableMinimalisticUI
private func highlightID(_ title: String) -> String { private func highlightID(_ title: String) -> String {
@ -897,130 +901,6 @@ struct GeneralSettings: View {
Text("System features") Text("System features")
} }
Section {
Defaults.Toggle("Enable Screen Recording Detection", key: .enableScreenRecordingDetection)
.settingsHighlight(id: highlightID("Enable Screen Recording Detection"))
Defaults.Toggle("Show Recording Indicator", key: .showRecordingIndicator)
.disabled(!enableScreenRecordingDetection)
.settingsHighlight(id: highlightID("Show Recording Indicator"))
// Note: Polling removed - now uses event-driven private API detection
if recordingManager.isMonitoring {
HStack {
Text("Detection Status")
Spacer()
if recordingManager.isRecording {
HStack(spacing: 4) {
Circle()
.fill(Color.red)
.frame(width: 8, height: 8)
Text("Recording Detected")
.foregroundColor(.red)
}
} else {
Text("Active - No Recording")
.foregroundColor(.green)
}
}
}
} header: {
Text("Screen Recording")
} footer: {
Text("Uses event-driven private API for real-time screen recording detection")
}
Section {
Defaults.Toggle("Enable Focus Detection", key: .enableDoNotDisturbDetection)
.settingsHighlight(id: highlightID("Enable Focus Detection"))
Defaults.Toggle("Show Focus Indicator", key: .showDoNotDisturbIndicator)
.disabled(!enableDoNotDisturbDetection)
.settingsHighlight(id: highlightID("Show Focus Indicator"))
Defaults.Toggle("Show Focus Label", key: .showDoNotDisturbLabel)
.disabled(!enableDoNotDisturbDetection)
.settingsHighlight(id: highlightID("Show Focus Label"))
if doNotDisturbManager.isMonitoring {
HStack {
Text("Focus Status")
Spacer()
if doNotDisturbManager.isDoNotDisturbActive {
HStack(spacing: 4) {
Circle()
.fill(Color.purple)
.frame(width: 8, height: 8)
Text(doNotDisturbManager.currentFocusModeName.isEmpty ? "Focus Enabled" : doNotDisturbManager.currentFocusModeName)
.foregroundColor(.purple)
}
} else {
Text("Active - No Focus")
.foregroundColor(.green)
}
}
} else {
HStack {
Text("Focus Status")
Spacer()
Text("Disabled")
.foregroundColor(.secondary)
}
}
} header: {
Text("Do Not Disturb")
} footer: {
Text("Listens for Focus session changes via distributed notifications")
}
Section {
Defaults.Toggle("Enable Camera Detection", key: .enableCameraDetection)
.settingsHighlight(id: highlightID("Enable Camera Detection"))
Defaults.Toggle("Enable Microphone Detection", key: .enableMicrophoneDetection)
.settingsHighlight(id: highlightID("Enable Microphone Detection"))
if privacyManager.isMonitoring {
HStack {
Text("Camera Status")
Spacer()
if privacyManager.cameraActive {
HStack(spacing: 4) {
Circle()
.fill(Color.green)
.frame(width: 8, height: 8)
Text("Camera Active")
.foregroundColor(.green)
}
} else {
Text("Inactive")
.foregroundColor(.secondary)
}
}
HStack {
Text("Microphone Status")
Spacer()
if privacyManager.microphoneActive {
HStack(spacing: 4) {
Circle()
.fill(Color.yellow)
.frame(width: 8, height: 8)
Text("Microphone Active")
.foregroundColor(.yellow)
}
} else {
Text("Inactive")
.foregroundColor(.secondary)
}
}
}
} header: {
Text("Privacy Indicators")
} footer: {
Text("Shows green camera icon and yellow microphone icon when in use. Uses event-driven CoreAudio and CoreMediaIO APIs.")
}
Section { Section {
Picker(selection: $notchHeightMode, label: Picker(selection: $notchHeightMode, label:
Text("Notch display height")) { Text("Notch display height")) {
@ -2167,6 +2047,168 @@ struct Extensions: View {
} }
} }
struct LiveActivitiesSettings: View {
@ObservedObject var coordinator = DynamicIslandViewCoordinator.shared
@ObservedObject var recordingManager = ScreenRecordingManager.shared
@ObservedObject var privacyManager = PrivacyIndicatorManager.shared
@ObservedObject var doNotDisturbManager = DoNotDisturbManager.shared
@Default(.enableScreenRecordingDetection) var enableScreenRecordingDetection
@Default(.enableDoNotDisturbDetection) var enableDoNotDisturbDetection
private func highlightID(_ title: String) -> String {
SettingsTab.liveActivities.highlightID(for: title)
}
var body: some View {
Form {
Section {
Defaults.Toggle("Enable Screen Recording Detection", key: .enableScreenRecordingDetection)
.settingsHighlight(id: highlightID("Enable Screen Recording Detection"))
Defaults.Toggle("Show Recording Indicator", key: .showRecordingIndicator)
.disabled(!enableScreenRecordingDetection)
.settingsHighlight(id: highlightID("Show Recording Indicator"))
if recordingManager.isMonitoring {
HStack {
Text("Detection Status")
Spacer()
if recordingManager.isRecording {
HStack(spacing: 4) {
Circle()
.fill(Color.red)
.frame(width: 8, height: 8)
Text("Recording Detected")
.foregroundColor(.red)
}
} else {
Text("Active - No Recording")
.foregroundColor(.green)
}
}
}
} header: {
Text("Screen Recording")
} footer: {
Text("Uses event-driven private API for real-time screen recording detection")
}
Section {
Defaults.Toggle("Enable Focus Detection", key: .enableDoNotDisturbDetection)
.settingsHighlight(id: highlightID("Enable Focus Detection"))
Defaults.Toggle("Show Focus Indicator", key: .showDoNotDisturbIndicator)
.disabled(!enableDoNotDisturbDetection)
.settingsHighlight(id: highlightID("Show Focus Indicator"))
Defaults.Toggle("Show Focus Label", key: .showDoNotDisturbLabel)
.disabled(!enableDoNotDisturbDetection)
.settingsHighlight(id: highlightID("Show Focus Label"))
if doNotDisturbManager.isMonitoring {
HStack {
Text("Focus Status")
Spacer()
if doNotDisturbManager.isDoNotDisturbActive {
HStack(spacing: 4) {
Circle()
.fill(Color.purple)
.frame(width: 8, height: 8)
Text(doNotDisturbManager.currentFocusModeName.isEmpty ? "Focus Enabled" : doNotDisturbManager.currentFocusModeName)
.foregroundColor(.purple)
}
} else {
Text("Active - No Focus")
.foregroundColor(.green)
}
}
} else {
HStack {
Text("Focus Status")
Spacer()
Text("Disabled")
.foregroundColor(.secondary)
}
}
} header: {
Text("Do Not Disturb")
} footer: {
Text("Listens for Focus session changes via distributed notifications")
}
Section {
Defaults.Toggle("Enable Camera Detection", key: .enableCameraDetection)
.settingsHighlight(id: highlightID("Enable Camera Detection"))
Defaults.Toggle("Enable Microphone Detection", key: .enableMicrophoneDetection)
.settingsHighlight(id: highlightID("Enable Microphone Detection"))
if privacyManager.isMonitoring {
HStack {
Text("Camera Status")
Spacer()
if privacyManager.cameraActive {
HStack(spacing: 4) {
Circle()
.fill(Color.green)
.frame(width: 8, height: 8)
Text("Camera Active")
.foregroundColor(.green)
}
} else {
Text("Inactive")
.foregroundColor(.secondary)
}
}
HStack {
Text("Microphone Status")
Spacer()
if privacyManager.microphoneActive {
HStack(spacing: 4) {
Circle()
.fill(Color.yellow)
.frame(width: 8, height: 8)
Text("Microphone Active")
.foregroundColor(.yellow)
}
} else {
Text("Inactive")
.foregroundColor(.secondary)
}
}
}
} header: {
Text("Privacy Indicators")
} footer: {
Text("Shows green camera icon and yellow microphone icon when in use. Uses event-driven CoreAudio and CoreMediaIO APIs.")
}
Section {
Toggle(
"Enable music live activity",
isOn: $coordinator.musicLiveActivityEnabled.animation()
)
.settingsHighlight(id: highlightID("Enable music live activity"))
} header: {
Text("Media Live Activity")
} footer: {
Text("Use the Media tab to configure sneak peek, lyrics, and floating media controls.")
}
Section {
Defaults.Toggle("Enable reminder live activity", key: .enableReminderLiveActivity)
.settingsHighlight(id: highlightID("Enable reminder live activity"))
} header: {
Text("Reminder Live Activity")
} footer: {
Text("Configure countdown style and lock screen widgets in the Calendar tab.")
}
}
.navigationTitle("Live Activities")
}
}
struct Appearance: View { struct Appearance: View {
@ObservedObject var coordinator = DynamicIslandViewCoordinator.shared @ObservedObject var coordinator = DynamicIslandViewCoordinator.shared
@Default(.mirrorShape) var mirrorShape @Default(.mirrorShape) var mirrorShape