mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-27 15:14:33 +00:00
Merge pull request #412 from Ebullioscopic/feature/camera-mirror-mode
Multiple camera choice
This commit is contained in:
commit
dbdeabdd83
5 changed files with 46 additions and 23 deletions
|
|
@ -1672,6 +1672,7 @@ struct ContentView: View {
|
||||||
let horizontalPadding: CGFloat = 8
|
let horizontalPadding: CGFloat = 8
|
||||||
let activationWidth = vm.closedNotchSize.width + horizontalPadding * 2
|
let activationWidth = vm.closedNotchSize.width + horizontalPadding * 2
|
||||||
let activationHeight = max(vm.closedNotchSize.height + zeroHeightHoverPadding, 14)
|
let activationHeight = max(vm.closedNotchSize.height + zeroHeightHoverPadding, 14)
|
||||||
|
|
||||||
let activationRect = CGRect(
|
let activationRect = CGRect(
|
||||||
x: screen.frame.midX - activationWidth / 2,
|
x: screen.frame.midX - activationWidth / 2,
|
||||||
y: screen.frame.maxY - activationHeight,
|
y: screen.frame.maxY - activationHeight,
|
||||||
|
|
|
||||||
|
|
@ -2314,7 +2314,6 @@
|
||||||
},
|
},
|
||||||
"±10 Seconds" : {
|
"±10 Seconds" : {
|
||||||
"comment" : "Display name for the \"±10 Seconds\" music skip behavior option.",
|
"comment" : "Display name for the \"±10 Seconds\" music skip behavior option.",
|
||||||
"extractionState" : "stale",
|
|
||||||
"isCommentAutoGenerated" : true,
|
"isCommentAutoGenerated" : true,
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"ko" : {
|
"ko" : {
|
||||||
|
|
@ -3138,7 +3137,6 @@
|
||||||
},
|
},
|
||||||
"Accent" : {
|
"Accent" : {
|
||||||
"comment" : "Display name for the caps lock indicator tint mode.",
|
"comment" : "Display name for the caps lock indicator tint mode.",
|
||||||
"extractionState" : "stale",
|
|
||||||
"isCommentAutoGenerated" : true,
|
"isCommentAutoGenerated" : true,
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"ko" : {
|
"ko" : {
|
||||||
|
|
@ -3696,7 +3694,6 @@
|
||||||
},
|
},
|
||||||
"Adaptive gradient" : {
|
"Adaptive gradient" : {
|
||||||
"comment" : "Display name for the timer icon color mode: adaptive gradient.",
|
"comment" : "Display name for the timer icon color mode: adaptive gradient.",
|
||||||
"extractionState" : "stale",
|
|
||||||
"isCommentAutoGenerated" : true,
|
"isCommentAutoGenerated" : true,
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"ko" : {
|
"ko" : {
|
||||||
|
|
@ -6016,7 +6013,6 @@
|
||||||
},
|
},
|
||||||
"Apple Music" : {
|
"Apple Music" : {
|
||||||
"comment" : "Media controller type for Apple Music",
|
"comment" : "Media controller type for Apple Music",
|
||||||
"extractionState" : "stale",
|
|
||||||
"isCommentAutoGenerated" : true,
|
"isCommentAutoGenerated" : true,
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"ko" : {
|
"ko" : {
|
||||||
|
|
@ -6920,7 +6916,6 @@
|
||||||
},
|
},
|
||||||
"Bar" : {
|
"Bar" : {
|
||||||
"comment" : "Name of the timer progress style that shows a horizontal bar.",
|
"comment" : "Name of the timer progress style that shows a horizontal bar.",
|
||||||
"extractionState" : "stale",
|
|
||||||
"isCommentAutoGenerated" : true,
|
"isCommentAutoGenerated" : true,
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"ko" : {
|
"ko" : {
|
||||||
|
|
@ -14521,7 +14516,6 @@
|
||||||
},
|
},
|
||||||
"Digital" : {
|
"Digital" : {
|
||||||
"comment" : "The display name for the \"Digital\" reminder presentation style.",
|
"comment" : "The display name for the \"Digital\" reminder presentation style.",
|
||||||
"extractionState" : "stale",
|
|
||||||
"isCommentAutoGenerated" : true,
|
"isCommentAutoGenerated" : true,
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"ko" : {
|
"ko" : {
|
||||||
|
|
@ -20829,7 +20823,6 @@
|
||||||
},
|
},
|
||||||
"Green" : {
|
"Green" : {
|
||||||
"comment" : "Display name for the green caps lock indicator tint mode.",
|
"comment" : "Display name for the green caps lock indicator tint mode.",
|
||||||
"extractionState" : "stale",
|
|
||||||
"isCommentAutoGenerated" : true,
|
"isCommentAutoGenerated" : true,
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"ko" : {
|
"ko" : {
|
||||||
|
|
@ -26874,6 +26867,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Mirror Camera" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Mirror macOS Clock timers" : {
|
"Mirror macOS Clock timers" : {
|
||||||
"comment" : "A toggle that enables or disables the app's ability to mirror macOS Clock timers.",
|
"comment" : "A toggle that enables or disables the app's ability to mirror macOS Clock timers.",
|
||||||
|
|
@ -29941,7 +29937,6 @@
|
||||||
},
|
},
|
||||||
"Now Playing" : {
|
"Now Playing" : {
|
||||||
"comment" : "Name of the media controller type for the \"Now Playing\" option.",
|
"comment" : "Name of the media controller type for the \"Now Playing\" option.",
|
||||||
"extractionState" : "stale",
|
|
||||||
"isCommentAutoGenerated" : true,
|
"isCommentAutoGenerated" : true,
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"ko" : {
|
"ko" : {
|
||||||
|
|
@ -30687,7 +30682,6 @@
|
||||||
},
|
},
|
||||||
"Panel" : {
|
"Panel" : {
|
||||||
"comment" : "Display name for the clipboard display mode \"Panel\".",
|
"comment" : "Display name for the clipboard display mode \"Panel\".",
|
||||||
"extractionState" : "stale",
|
|
||||||
"isCommentAutoGenerated" : true,
|
"isCommentAutoGenerated" : true,
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"ko" : {
|
"ko" : {
|
||||||
|
|
@ -34230,7 +34224,6 @@
|
||||||
},
|
},
|
||||||
"Ring" : {
|
"Ring" : {
|
||||||
"comment" : "The name of the timer progress style that shows a ring shape.",
|
"comment" : "The name of the timer progress style that shows a ring shape.",
|
||||||
"extractionState" : "stale",
|
|
||||||
"isCommentAutoGenerated" : true,
|
"isCommentAutoGenerated" : true,
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"ko" : {
|
"ko" : {
|
||||||
|
|
@ -35752,7 +35745,6 @@
|
||||||
},
|
},
|
||||||
"Separate Tab" : {
|
"Separate Tab" : {
|
||||||
"comment" : "Display name for the clipboard display mode option to open the clipboard in a separate tab.",
|
"comment" : "Display name for the clipboard display mode option to open the clipboard in a separate tab.",
|
||||||
"extractionState" : "stale",
|
|
||||||
"isCommentAutoGenerated" : true,
|
"isCommentAutoGenerated" : true,
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"ko" : {
|
"ko" : {
|
||||||
|
|
@ -38185,7 +38177,6 @@
|
||||||
},
|
},
|
||||||
"Shows screen assistant as a dropdown attached to the AI button" : {
|
"Shows screen assistant as a dropdown attached to the AI button" : {
|
||||||
"comment" : "Description of the screen assistant display mode option that shows the screen assistant as a dropdown attached to the AI button.",
|
"comment" : "Description of the screen assistant display mode option that shows the screen assistant as a dropdown attached to the AI button.",
|
||||||
"extractionState" : "stale",
|
|
||||||
"isCommentAutoGenerated" : true,
|
"isCommentAutoGenerated" : true,
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"ko" : {
|
"ko" : {
|
||||||
|
|
@ -38210,7 +38201,6 @@
|
||||||
},
|
},
|
||||||
"Shows screen assistant in a floating panel near the notch" : {
|
"Shows screen assistant in a floating panel near the notch" : {
|
||||||
"comment" : "Description of the screen assistant display mode option that shows the screen assistant in a floating panel near the notch.",
|
"comment" : "Description of the screen assistant display mode option that shows the screen assistant in a floating panel near the notch.",
|
||||||
"extractionState" : "stale",
|
|
||||||
"isCommentAutoGenerated" : true,
|
"isCommentAutoGenerated" : true,
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"ko" : {
|
"ko" : {
|
||||||
|
|
@ -38438,7 +38428,6 @@
|
||||||
},
|
},
|
||||||
"Skip forward or backward by ten seconds" : {
|
"Skip forward or backward by ten seconds" : {
|
||||||
"comment" : "Description of a music skip behavior option.",
|
"comment" : "Description of a music skip behavior option.",
|
||||||
"extractionState" : "stale",
|
|
||||||
"isCommentAutoGenerated" : true,
|
"isCommentAutoGenerated" : true,
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"ko" : {
|
"ko" : {
|
||||||
|
|
@ -39162,7 +39151,6 @@
|
||||||
},
|
},
|
||||||
"Spotify" : {
|
"Spotify" : {
|
||||||
"comment" : "Spotify media controller type.",
|
"comment" : "Spotify media controller type.",
|
||||||
"extractionState" : "stale",
|
|
||||||
"isCommentAutoGenerated" : true,
|
"isCommentAutoGenerated" : true,
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"ko" : {
|
"ko" : {
|
||||||
|
|
@ -39399,7 +39387,6 @@
|
||||||
},
|
},
|
||||||
"Standard previous/next track controls" : {
|
"Standard previous/next track controls" : {
|
||||||
"comment" : "Description of the music skip behavior option to use standard previous/next track controls.",
|
"comment" : "Description of the music skip behavior option to use standard previous/next track controls.",
|
||||||
"extractionState" : "stale",
|
|
||||||
"isCommentAutoGenerated" : true,
|
"isCommentAutoGenerated" : true,
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"ko" : {
|
"ko" : {
|
||||||
|
|
@ -42227,7 +42214,6 @@
|
||||||
},
|
},
|
||||||
"Track Skip" : {
|
"Track Skip" : {
|
||||||
"comment" : "Display name for the \"Track Skip\" music skip behavior.",
|
"comment" : "Display name for the \"Track Skip\" music skip behavior.",
|
||||||
"extractionState" : "stale",
|
|
||||||
"isCommentAutoGenerated" : true,
|
"isCommentAutoGenerated" : true,
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"ko" : {
|
"ko" : {
|
||||||
|
|
@ -45585,7 +45571,6 @@
|
||||||
},
|
},
|
||||||
"Youtube Music" : {
|
"Youtube Music" : {
|
||||||
"comment" : "Spotify music service.",
|
"comment" : "Spotify music service.",
|
||||||
"extractionState" : "stale",
|
|
||||||
"isCommentAutoGenerated" : true,
|
"isCommentAutoGenerated" : true,
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"ko" : {
|
"ko" : {
|
||||||
|
|
|
||||||
|
|
@ -3830,7 +3830,9 @@ struct LiveActivitiesSettings: View {
|
||||||
|
|
||||||
struct Appearance: View {
|
struct Appearance: View {
|
||||||
@ObservedObject var coordinator = DynamicIslandViewCoordinator.shared
|
@ObservedObject var coordinator = DynamicIslandViewCoordinator.shared
|
||||||
|
@ObservedObject var webcamManager = WebcamManager.shared
|
||||||
@Default(.mirrorShape) var mirrorShape
|
@Default(.mirrorShape) var mirrorShape
|
||||||
|
@Default(.selectedCameraID) var selectedCameraID
|
||||||
@Default(.sliderColor) var sliderColor
|
@Default(.sliderColor) var sliderColor
|
||||||
@Default(.useMusicVisualizer) var useMusicVisualizer
|
@Default(.useMusicVisualizer) var useMusicVisualizer
|
||||||
@Default(.customVisualizers) var customVisualizers
|
@Default(.customVisualizers) var customVisualizers
|
||||||
|
|
@ -4237,6 +4239,22 @@ struct Appearance: View {
|
||||||
.tag(MirrorShapeEnum.rectangle)
|
.tag(MirrorShapeEnum.rectangle)
|
||||||
}
|
}
|
||||||
.settingsHighlight(id: highlightID("Mirror shape"))
|
.settingsHighlight(id: highlightID("Mirror shape"))
|
||||||
|
|
||||||
|
if webcamManager.cameraAvailable {
|
||||||
|
Picker("Mirror Camera", selection: $selectedCameraID) {
|
||||||
|
ForEach(webcamManager.availableCameras, id: \.uniqueID) { device in
|
||||||
|
Text(device.localizedName)
|
||||||
|
.tag(device.uniqueID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onChange(of: selectedCameraID) { _, _ in
|
||||||
|
if Defaults[.showMirror] {
|
||||||
|
webcamManager.stopSession()
|
||||||
|
webcamManager.startSession()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.settingsHighlight(id: highlightID("Mirror Camera"))
|
||||||
|
}
|
||||||
Defaults.Toggle(key: .showNotHumanFace) {
|
Defaults.Toggle(key: .showNotHumanFace) {
|
||||||
Text("Idle Animation")
|
Text("Idle Animation")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@
|
||||||
|
|
||||||
import AVFoundation
|
import AVFoundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import Defaults
|
||||||
|
|
||||||
class WebcamManager: NSObject, ObservableObject {
|
class WebcamManager: NSObject, ObservableObject {
|
||||||
static let shared = WebcamManager()
|
static let shared = WebcamManager()
|
||||||
|
|
@ -51,6 +52,12 @@ class WebcamManager: NSObject, ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Published var availableCameras: [AVCaptureDevice] = [] {
|
||||||
|
didSet {
|
||||||
|
objectWillChange.send()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private let sessionQueue = DispatchQueue(label: "DynamicIsland.WebcamManager.SessionQueue", qos: .userInitiated)
|
private let sessionQueue = DispatchQueue(label: "DynamicIsland.WebcamManager.SessionQueue", qos: .userInitiated)
|
||||||
|
|
||||||
private var isCleaningUp: Bool = false
|
private var isCleaningUp: Bool = false
|
||||||
|
|
@ -132,7 +139,7 @@ class WebcamManager: NSObject, ObservableObject {
|
||||||
/// Checks if any camera devices are available and sets up capture session if needed
|
/// Checks if any camera devices are available and sets up capture session if needed
|
||||||
func checkCameraAvailability() {
|
func checkCameraAvailability() {
|
||||||
let availableDevices = AVCaptureDevice.DiscoverySession(
|
let availableDevices = AVCaptureDevice.DiscoverySession(
|
||||||
deviceTypes: [.external, .builtInWideAngleCamera],
|
deviceTypes: [.external, .builtInWideAngleCamera, .deskViewCamera, .externalUnknown],
|
||||||
mediaType: .video,
|
mediaType: .video,
|
||||||
position: .unspecified
|
position: .unspecified
|
||||||
).devices
|
).devices
|
||||||
|
|
@ -140,6 +147,7 @@ class WebcamManager: NSObject, ObservableObject {
|
||||||
let hasAvailableDevices = !availableDevices.isEmpty
|
let hasAvailableDevices = !availableDevices.isEmpty
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
|
self.availableCameras = availableDevices
|
||||||
self.cameraAvailable = hasAvailableDevices
|
self.cameraAvailable = hasAvailableDevices
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -158,14 +166,24 @@ class WebcamManager: NSObject, ObservableObject {
|
||||||
let session = AVCaptureSession()
|
let session = AVCaptureSession()
|
||||||
|
|
||||||
do {
|
do {
|
||||||
// Get available devices and prefer external camera if available
|
// Get available devices
|
||||||
let discoverySession = AVCaptureDevice.DiscoverySession(
|
let discoverySession = AVCaptureDevice.DiscoverySession(
|
||||||
deviceTypes: [.external, .builtInWideAngleCamera],
|
deviceTypes: [.external, .builtInWideAngleCamera, .deskViewCamera, .externalUnknown],
|
||||||
mediaType: .video,
|
mediaType: .video,
|
||||||
position: .unspecified
|
position: .unspecified
|
||||||
)
|
)
|
||||||
|
|
||||||
guard let videoDevice = discoverySession.devices.first else {
|
let devices = discoverySession.devices
|
||||||
|
let selectedID = Defaults[.selectedCameraID]
|
||||||
|
|
||||||
|
let videoDevice: AVCaptureDevice?
|
||||||
|
if !selectedID.isEmpty {
|
||||||
|
videoDevice = devices.first(where: { $0.uniqueID == selectedID }) ?? devices.first
|
||||||
|
} else {
|
||||||
|
videoDevice = devices.first
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let videoDevice = videoDevice else {
|
||||||
NSLog("No video devices available")
|
NSLog("No video devices available")
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.isSessionRunning = false
|
self.isSessionRunning = false
|
||||||
|
|
@ -175,7 +193,7 @@ class WebcamManager: NSObject, ObservableObject {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
NSLog("Using camera: \(videoDevice.localizedName)")
|
NSLog("Using camera: \(videoDevice.localizedName) (ID: \(videoDevice.uniqueID))")
|
||||||
|
|
||||||
// Lock device for configuration
|
// Lock device for configuration
|
||||||
try videoDevice.lockForConfiguration()
|
try videoDevice.lockForConfiguration()
|
||||||
|
|
|
||||||
|
|
@ -794,6 +794,7 @@ extension Defaults.Keys {
|
||||||
//static let alwaysShowTabs = Key<Bool>("alwaysShowTabs", default: true)
|
//static let alwaysShowTabs = Key<Bool>("alwaysShowTabs", default: true)
|
||||||
static let showMirror = Key<Bool>("showMirror", default: false)
|
static let showMirror = Key<Bool>("showMirror", default: false)
|
||||||
static let mirrorShape = Key<MirrorShapeEnum>("mirrorShape", default: MirrorShapeEnum.rectangle)
|
static let mirrorShape = Key<MirrorShapeEnum>("mirrorShape", default: MirrorShapeEnum.rectangle)
|
||||||
|
static let selectedCameraID = Key<String>("selectedCameraID", default: "")
|
||||||
static let settingsIconInNotch = Key<Bool>("settingsIconInNotch", default: true)
|
static let settingsIconInNotch = Key<Bool>("settingsIconInNotch", default: true)
|
||||||
static let lightingEffect = Key<Bool>("lightingEffect", default: true)
|
static let lightingEffect = Key<Bool>("lightingEffect", default: true)
|
||||||
static let accentColor = Key<Color>("accentColor", default: Color.blue)
|
static let accentColor = Key<Color>("accentColor", default: Color.blue)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue