mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-27 15:14:33 +00:00
feat: add camera selector for mirror mode and fix hover glitch
This commit is contained in:
parent
74d641c135
commit
edd8ecc3bb
4 changed files with 64 additions and 8 deletions
|
|
@ -124,6 +124,17 @@ struct ContentView: View {
|
||||||
return baseSize
|
return baseSize
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if coordinator.currentView == .home && Defaults[.mediaController] == .all && !musicManager.secondarySources.isEmpty {
|
||||||
|
let toggleBarHeight: CGFloat = 34
|
||||||
|
let rowHeight: CGFloat = 58
|
||||||
|
let safetyPadding: CGFloat = 16
|
||||||
|
var extraHeight = toggleBarHeight + safetyPadding
|
||||||
|
if musicManager.isMultiSourceListExpanded {
|
||||||
|
extraHeight += CGFloat(musicManager.secondarySources.count) * rowHeight
|
||||||
|
}
|
||||||
|
return CGSize(width: baseSize.width, height: baseSize.height + extraHeight)
|
||||||
|
}
|
||||||
|
|
||||||
if enableMinimalisticUI,
|
if enableMinimalisticUI,
|
||||||
coordinator.currentView == .home,
|
coordinator.currentView == .home,
|
||||||
let preferredHeight = extensionMinimalisticPreferredHeight(baseSize: baseSize) {
|
let preferredHeight = extensionMinimalisticPreferredHeight(baseSize: baseSize) {
|
||||||
|
|
@ -1669,9 +1680,13 @@ struct ContentView: View {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
let horizontalPadding: CGFloat = 8
|
let horizontalPadding: CGFloat = 12
|
||||||
let activationWidth = vm.closedNotchSize.width + horizontalPadding * 2
|
let activationWidth = vm.closedNotchSize.width + horizontalPadding * 2
|
||||||
let activationHeight = max(vm.closedNotchSize.height + zeroHeightHoverPadding, 14)
|
|
||||||
|
// Hysteresis: Use a taller activation area when already hovering to prevent rapid flickering
|
||||||
|
let baseHeight = max(vm.closedNotchSize.height + zeroHeightHoverPadding, 16)
|
||||||
|
let activationHeight = isHovering ? baseHeight + 20 : baseHeight
|
||||||
|
|
||||||
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,
|
||||||
|
|
|
||||||
|
|
@ -2487,7 +2487,11 @@ struct Media: View {
|
||||||
} header: {
|
} header: {
|
||||||
Text("Media Source")
|
Text("Media Source")
|
||||||
} footer: {
|
} footer: {
|
||||||
if MusicManager.shared.isNowPlayingDeprecated {
|
if mediaController == .all {
|
||||||
|
Text("All Music detects every active media source and shows them in a switchable list below the main player.")
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
.font(.caption)
|
||||||
|
} else if MusicManager.shared.isNowPlayingDeprecated {
|
||||||
HStack {
|
HStack {
|
||||||
Text("YouTube Music requires this third-party app to be installed: ")
|
Text("YouTube Music requires this third-party app to be installed: ")
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
|
|
@ -3830,7 +3834,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 +4243,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