// // PrivacyIndicatorIcon.swift // DynamicIsland // // Created for privacy indicator feature // Visual component for camera and microphone privacy indicators // import SwiftUI // MARK: - Privacy Indicator Type enum PrivacyIndicatorType { case camera case microphone var icon: String { switch self { case .camera: return "video.fill" case .microphone: return "mic.fill" } } var label: String { switch self { case .camera: return "Camera Active" case .microphone: return "Microphone Active" } } var accessibilityLabel: String { switch self { case .camera: return "Camera is being used by an application" case .microphone: return "Microphone is being used by an application" } } } // MARK: - Privacy Indicator Icon View struct PrivacyIndicatorIcon: View { // MARK: - Properties let type: PrivacyIndicatorType let showLabel: Bool @State private var isAnimating: Bool = false // MARK: - Initialization init(type: PrivacyIndicatorType, showLabel: Bool = false) { self.type = type self.showLabel = showLabel } // MARK: - Body var body: some View { HStack(spacing: 4) { // Icon with glow effect ZStack { // Background glow Circle() .fill( RadialGradient( colors: [ Color.orange.opacity(0.4), Color.orange.opacity(0.2), Color.clear ], center: .center, startRadius: 0, endRadius: 12 ) ) .frame(width: 24, height: 24) .opacity(isAnimating ? 0.8 : 0.5) // Icon background circle Circle() .fill(Color.orange.opacity(0.2)) .frame(width: 18, height: 18) // Icon Image(systemName: type.icon) .font(.system(size: 9, weight: .semibold)) .foregroundColor(.orange) } .frame(width: 24, height: 24) // Optional label if showLabel { Text(type.label) .font(.system(size: 11, weight: .medium)) .foregroundColor(.primary.opacity(0.8)) } } .accessibilityLabel(type.accessibilityLabel) .onAppear { startPulseAnimation() } } // MARK: - Animation private func startPulseAnimation() { withAnimation( .easeInOut(duration: 1.5) .repeatForever(autoreverses: true) ) { isAnimating = true } } } // MARK: - Privacy Indicator Container /// Container view that shows both camera and microphone indicators based on layout struct PrivacyIndicatorContainer: View { // MARK: - Environment @EnvironmentObject var privacyManager: PrivacyIndicatorManager // MARK: - Properties let showLabels: Bool let spacing: CGFloat // MARK: - Initialization init(showLabels: Bool = false, spacing: CGFloat = 8) { self.showLabels = showLabels self.spacing = spacing } // MARK: - Body var body: some View { HStack(spacing: spacing) { // Microphone indicator (on the left when both are shown) if privacyManager.indicatorLayout.showsMicrophoneIndicator { PrivacyIndicatorIcon(type: .microphone, showLabel: showLabels) .transition(.scale.combined(with: .opacity)) } // Camera indicator (on the right when both are shown) if privacyManager.indicatorLayout.showsCameraIndicator { PrivacyIndicatorIcon(type: .camera, showLabel: showLabels) .transition(.scale.combined(with: .opacity)) } } .animation(.smooth(duration: 0.3), value: privacyManager.indicatorLayout) } } // MARK: - Privacy Indicator Detail View /// Detailed view showing privacy indicator status (for settings/tips) struct PrivacyIndicatorDetailView: View { @EnvironmentObject var privacyManager: PrivacyIndicatorManager var body: some View { VStack(alignment: .leading, spacing: 12) { Text("Privacy Indicators") .font(.headline) // Camera status HStack(spacing: 12) { PrivacyIndicatorIcon(type: .camera) VStack(alignment: .leading, spacing: 2) { Text("Camera") .font(.subheadline.weight(.medium)) Text(privacyManager.cameraActive ? "Currently active" : "Not active") .font(.caption) .foregroundColor(.secondary) } Spacer() Circle() .fill(privacyManager.cameraActive ? Color.green : Color.gray.opacity(0.3)) .frame(width: 8, height: 8) } .padding(12) .background(Color.primary.opacity(0.05)) .cornerRadius(10) // Microphone status HStack(spacing: 12) { PrivacyIndicatorIcon(type: .microphone) VStack(alignment: .leading, spacing: 2) { Text("Microphone") .font(.subheadline.weight(.medium)) Text(privacyManager.microphoneActive ? "Currently active" : "Not active") .font(.caption) .foregroundColor(.secondary) } Spacer() Circle() .fill(privacyManager.microphoneActive ? Color.green : Color.gray.opacity(0.3)) .frame(width: 8, height: 8) } .padding(12) .background(Color.primary.opacity(0.05)) .cornerRadius(10) // Info text Text("These indicators appear in the notch when apps are using your camera or microphone, matching macOS's native privacy indicators.") .font(.caption) .foregroundColor(.secondary) .padding(.top, 4) } } } // MARK: - Preview #Preview("Single Indicators") { VStack(spacing: 20) { PrivacyIndicatorIcon(type: .camera) PrivacyIndicatorIcon(type: .microphone) PrivacyIndicatorIcon(type: .camera, showLabel: true) PrivacyIndicatorIcon(type: .microphone, showLabel: true) } .padding() .background(Color.black) } #Preview("Container") { let manager = PrivacyIndicatorManager.shared VStack(spacing: 20) { Text("Both Active") PrivacyIndicatorContainer() .onAppear { manager.cameraActive = true manager.microphoneActive = true } Text("Camera Only") PrivacyIndicatorContainer() .onAppear { manager.cameraActive = true manager.microphoneActive = false } Text("Microphone Only") PrivacyIndicatorContainer() .onAppear { manager.cameraActive = false manager.microphoneActive = true } } .padding() .background(Color.black) .environmentObject(manager) } #Preview("Detail View") { let manager = PrivacyIndicatorManager.shared PrivacyIndicatorDetailView() .padding() .environmentObject(manager) .onAppear { manager.cameraActive = true manager.microphoneActive = false } }