Update Settings View

This commit is contained in:
Hariharan Mudaliar 2026-02-08 16:03:23 +05:30
commit 105b0466a4
No known key found for this signature in database
GPG key ID: 025583EB0BE5640E
2 changed files with 163 additions and 2 deletions

View file

@ -12419,6 +12419,10 @@
}, },
"shouldTranslate" : false "shouldTranslate" : false
}, },
"HUD icon style" : {
"comment" : "A label describing the style of the 3D or symbol-based Bluetooth HUD icon.",
"isCommentAutoGenerated" : true
},
"HUD Position" : { "HUD Position" : {
"localizations" : { "localizations" : {
"pt-BR" : { "pt-BR" : {

View file

@ -1776,6 +1776,7 @@ private struct HUDSelectionCard<Preview: View>: View {
private struct DevicesSettingsView: View { private struct DevicesSettingsView: View {
@Default(.progressBarStyle) var progressBarStyle @Default(.progressBarStyle) var progressBarStyle
@Default(.useBluetoothHUD3DIcon) private var useBluetoothHUD3DIcon
private func highlightID(_ title: String) -> String { private func highlightID(_ title: String) -> String {
SettingsTab.devices.highlightID(for: title) SettingsTab.devices.highlightID(for: title)
@ -1796,8 +1797,29 @@ private struct DevicesSettingsView: View {
.settingsHighlight(id: highlightID("Show battery percentage text in HUD")) .settingsHighlight(id: highlightID("Show battery percentage text in HUD"))
Defaults.Toggle("Scroll device name in HUD", key: .showBluetoothDeviceNameMarquee) Defaults.Toggle("Scroll device name in HUD", key: .showBluetoothDeviceNameMarquee)
.settingsHighlight(id: highlightID("Scroll device name in HUD")) .settingsHighlight(id: highlightID("Scroll device name in HUD"))
Defaults.Toggle("Use 3D Bluetooth HUD icon", key: .useBluetoothHUD3DIcon) VStack(alignment: .leading, spacing: 12) {
.settingsHighlight(id: highlightID("Use 3D Bluetooth HUD icon")) Text("HUD icon style")
.font(.system(size: 13, weight: .semibold))
.foregroundStyle(.primary)
HStack(spacing: 16) {
Spacer(minLength: 0)
BluetoothHUDIconStyleCard(
style: .symbol,
isSelected: !useBluetoothHUD3DIcon
) {
useBluetoothHUD3DIcon = false
}
BluetoothHUDIconStyleCard(
style: .threeD,
isSelected: useBluetoothHUD3DIcon
) {
useBluetoothHUD3DIcon = true
}
Spacer(minLength: 0)
}
}
.settingsHighlight(id: highlightID("Use 3D Bluetooth HUD icon"))
} header: { } header: {
Text("Bluetooth Audio Devices") Text("Bluetooth Audio Devices")
} footer: { } footer: {
@ -2669,6 +2691,141 @@ struct About: View {
} }
} }
private extension DevicesSettingsView {
enum BluetoothHUDIconStyle: String {
case symbol
case threeD
var title: String {
switch self {
case .symbol:
return "Symbol"
case .threeD:
return "3D"
}
}
}
struct BluetoothHUDIconStyleCard: View {
let style: BluetoothHUDIconStyle
let isSelected: Bool
let action: () -> Void
@State private var isHovering = false
var body: some View {
VStack(spacing: 8) {
ZStack {
RoundedRectangle(cornerRadius: 12)
.fill(backgroundColor)
.overlay(
RoundedRectangle(cornerRadius: 12)
.strokeBorder(borderColor, lineWidth: isSelected ? 2 : 1)
)
preview
}
.frame(width: 90, height: 64)
.onHover { hovering in
withAnimation(.easeInOut(duration: 0.2)) {
isHovering = hovering
}
}
Text(style.title)
.font(.caption)
.fontWeight(.medium)
}
.contentShape(Rectangle())
.onTapGesture { action() }
}
private var preview: some View {
Group {
switch style {
case .symbol:
Image(systemName: BluetoothAudioDeviceType.airpods.sfSymbol)
.font(.system(size: 24, weight: .semibold))
.symbolRenderingMode(.hierarchical)
case .threeD:
if let url = Bundle.main.url(
forResource: BluetoothAudioDeviceType.airpods.inlineHUDAnimationBaseName,
withExtension: "mov"
) {
SettingsLoopingVideoIcon(url: url, size: CGSize(width: 28, height: 28))
.frame(width: 28, height: 28)
} else {
Image(systemName: BluetoothAudioDeviceType.airpods.sfSymbol)
.font(.system(size: 24, weight: .semibold))
.symbolRenderingMode(.hierarchical)
}
}
}
}
private var backgroundColor: Color {
if isSelected { return Color.accentColor.opacity(0.12) }
if isHovering { return Color.primary.opacity(0.05) }
return Color(nsColor: .controlBackgroundColor)
}
private var borderColor: Color {
if isSelected { return Color.accentColor }
if isHovering { return Color.primary.opacity(0.1) }
return Color.clear
}
}
}
private struct SettingsLoopingVideoIcon: NSViewRepresentable {
let url: URL
let size: CGSize
func makeNSView(context: Context) -> NSView {
let view = NSView(frame: NSRect(origin: .zero, size: size))
view.wantsLayer = true
let layer = AVPlayerLayer()
layer.videoGravity = .resizeAspect
layer.frame = view.bounds
view.layer?.addSublayer(layer)
context.coordinator.attach(layer: layer, url: url)
return view
}
func updateNSView(_ nsView: NSView, context: Context) {}
func makeCoordinator() -> Coordinator { Coordinator() }
final class Coordinator {
private var controller: SettingsLoopingPlayerController?
func attach(layer: AVPlayerLayer, url: URL) {
controller = SettingsLoopingPlayerController(url: url)
layer.player = controller?.player
}
}
}
private final class SettingsLoopingPlayerController {
let player: AVQueuePlayer
private var looper: AVPlayerLooper?
init(url: URL) {
let item = AVPlayerItem(url: url)
player = AVQueuePlayer()
player.isMuted = true
player.actionAtItemEnd = .none
looper = AVPlayerLooper(player: player, templateItem: item)
player.play()
}
deinit {
player.pause()
looper = nil
}
}
struct Shelf: View { struct Shelf: View {
@Default(.quickShareProvider) var quickShareProvider @Default(.quickShareProvider) var quickShareProvider
@Default(.expandedDragDetection) var expandedDragDetection @Default(.expandedDragDetection) var expandedDragDetection