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
|
||||
}
|
||||
|
||||
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,
|
||||
coordinator.currentView == .home,
|
||||
let preferredHeight = extensionMinimalisticPreferredHeight(baseSize: baseSize) {
|
||||
|
|
@ -1669,9 +1680,13 @@ struct ContentView: View {
|
|||
return false
|
||||
}
|
||||
|
||||
let horizontalPadding: CGFloat = 8
|
||||
let horizontalPadding: CGFloat = 12
|
||||
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(
|
||||
x: screen.frame.midX - activationWidth / 2,
|
||||
y: screen.frame.maxY - activationHeight,
|
||||
|
|
|
|||
|
|
@ -2487,7 +2487,11 @@ struct Media: View {
|
|||
} header: {
|
||||
Text("Media Source")
|
||||
} 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 {
|
||||
Text("YouTube Music requires this third-party app to be installed: ")
|
||||
.foregroundStyle(.secondary)
|
||||
|
|
@ -3830,7 +3834,9 @@ struct LiveActivitiesSettings: View {
|
|||
|
||||
struct Appearance: View {
|
||||
@ObservedObject var coordinator = DynamicIslandViewCoordinator.shared
|
||||
@ObservedObject var webcamManager = WebcamManager.shared
|
||||
@Default(.mirrorShape) var mirrorShape
|
||||
@Default(.selectedCameraID) var selectedCameraID
|
||||
@Default(.sliderColor) var sliderColor
|
||||
@Default(.useMusicVisualizer) var useMusicVisualizer
|
||||
@Default(.customVisualizers) var customVisualizers
|
||||
|
|
@ -4237,6 +4243,22 @@ struct Appearance: View {
|
|||
.tag(MirrorShapeEnum.rectangle)
|
||||
}
|
||||
.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) {
|
||||
Text("Idle Animation")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
|
||||
import AVFoundation
|
||||
import SwiftUI
|
||||
import Defaults
|
||||
|
||||
class WebcamManager: NSObject, ObservableObject {
|
||||
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 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
|
||||
func checkCameraAvailability() {
|
||||
let availableDevices = AVCaptureDevice.DiscoverySession(
|
||||
deviceTypes: [.external, .builtInWideAngleCamera],
|
||||
deviceTypes: [.external, .builtInWideAngleCamera, .deskViewCamera, .externalUnknown],
|
||||
mediaType: .video,
|
||||
position: .unspecified
|
||||
).devices
|
||||
|
|
@ -140,6 +147,7 @@ class WebcamManager: NSObject, ObservableObject {
|
|||
let hasAvailableDevices = !availableDevices.isEmpty
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.availableCameras = availableDevices
|
||||
self.cameraAvailable = hasAvailableDevices
|
||||
}
|
||||
}
|
||||
|
|
@ -158,14 +166,24 @@ class WebcamManager: NSObject, ObservableObject {
|
|||
let session = AVCaptureSession()
|
||||
|
||||
do {
|
||||
// Get available devices and prefer external camera if available
|
||||
// Get available devices
|
||||
let discoverySession = AVCaptureDevice.DiscoverySession(
|
||||
deviceTypes: [.external, .builtInWideAngleCamera],
|
||||
deviceTypes: [.external, .builtInWideAngleCamera, .deskViewCamera, .externalUnknown],
|
||||
mediaType: .video,
|
||||
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")
|
||||
DispatchQueue.main.async {
|
||||
self.isSessionRunning = false
|
||||
|
|
@ -175,7 +193,7 @@ class WebcamManager: NSObject, ObservableObject {
|
|||
return
|
||||
}
|
||||
|
||||
NSLog("Using camera: \(videoDevice.localizedName)")
|
||||
NSLog("Using camera: \(videoDevice.localizedName) (ID: \(videoDevice.uniqueID))")
|
||||
|
||||
// Lock device for configuration
|
||||
try videoDevice.lockForConfiguration()
|
||||
|
|
|
|||
|
|
@ -794,6 +794,7 @@ extension Defaults.Keys {
|
|||
//static let alwaysShowTabs = Key<Bool>("alwaysShowTabs", default: true)
|
||||
static let showMirror = Key<Bool>("showMirror", default: false)
|
||||
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 lightingEffect = Key<Bool>("lightingEffect", default: true)
|
||||
static let accentColor = Key<Color>("accentColor", default: Color.blue)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue