diff --git a/airsync-mac/Screens/HomeScreen/PhoneView/DeviceStatusView.swift b/airsync-mac/Screens/HomeScreen/PhoneView/DeviceStatusView.swift index 56048a0e..c61750cb 100644 --- a/airsync-mac/Screens/HomeScreen/PhoneView/DeviceStatusView.swift +++ b/airsync-mac/Screens/HomeScreen/PhoneView/DeviceStatusView.swift @@ -6,6 +6,7 @@ // import SwiftUI +import AppKit struct DeviceStatusView: View { @ObservedObject var appState = AppState.shared @@ -74,10 +75,23 @@ struct DeviceStatusView: View { if !editing { WebSocketServer.shared.setVolume(Int(tempVolume)) } + if editing { + NSHapticFeedbackManager.defaultPerformer.perform(.generic, performanceTime: .default) + } else { + NSHapticFeedbackManager.defaultPerformer.perform(.generic, performanceTime: .default) + } isDragging = editing } ) .focusable(false) + .onChange(of: tempVolume) { oldValue, newValue in + guard isDragging else { return } + let lastTick = floor(oldValue / 5.0) * 5.0 + let currentTick = floor(newValue / 5.0) * 5.0 + if currentTick != lastTick { + NSHapticFeedbackManager.defaultPerformer.perform(.levelChange, performanceTime: .default) + } + } Image(systemName: "speaker.wave.3.fill") } diff --git a/airsync-mac/Screens/HomeScreen/PhoneView/MediaPlayerView.swift b/airsync-mac/Screens/HomeScreen/PhoneView/MediaPlayerView.swift index e45e2d94..742ac2b3 100644 --- a/airsync-mac/Screens/HomeScreen/PhoneView/MediaPlayerView.swift +++ b/airsync-mac/Screens/HomeScreen/PhoneView/MediaPlayerView.swift @@ -7,6 +7,7 @@ import SwiftUI import Combine +import AppKit // MARK: - Seekbar sub-view @@ -22,13 +23,25 @@ private struct MediaSeekbarView: View { in: 0...max(music.duration, 1), onEditingChanged: { editing in appState.isDraggingMedia = editing - if !editing { + if editing { + NSHapticFeedbackManager.defaultPerformer.perform(.generic, performanceTime: .default) + } else { + NSHapticFeedbackManager.defaultPerformer.perform(.generic, performanceTime: .default) appState.handleMediaSeek(to: appState.mediaPosition) } } ) .accentColor(.primary) .padding(.horizontal, 2) + .onChange(of: appState.mediaPosition) { oldValue, newValue in + guard appState.isDraggingMedia else { return } + let tickInterval: Double = 1.0 + let lastTick = floor(oldValue / tickInterval) * tickInterval + let currentTick = floor(newValue / tickInterval) * tickInterval + if currentTick != lastTick { + NSHapticFeedbackManager.defaultPerformer.perform(.levelChange, performanceTime: .default) + } + } // Time labels HStack { diff --git a/airsync-mac/Screens/Settings/MenubarSettingsView.swift b/airsync-mac/Screens/Settings/MenubarSettingsView.swift index b46fa5fe..c711f17b 100644 --- a/airsync-mac/Screens/Settings/MenubarSettingsView.swift +++ b/airsync-mac/Screens/Settings/MenubarSettingsView.swift @@ -1,10 +1,13 @@ import SwiftUI +import AppKit struct MenubarSettingsView: View { @ObservedObject var appState = AppState.shared @State private var showingPlusPopover = false @State private var plusPopoverMessage = "" @State private var showMarqueeInfo = false + @State private var isDraggingFontSize = false + @State private var isDraggingTextLength = false var body: some View { ScrollView { @@ -17,10 +20,20 @@ struct MenubarSettingsView: View { Slider( value: $appState.menubarFontSize, in: 10...16, - step: 1 + step: 1, + onEditingChanged: { editing in + isDraggingFontSize = editing + NSHapticFeedbackManager.defaultPerformer.perform(.generic, performanceTime: .default) + } ) .frame(width: 150) .controlSize(.small) + .onChange(of: appState.menubarFontSize) { oldValue, newValue in + guard isDraggingFontSize else { return } + if newValue != oldValue { + NSHapticFeedbackManager.defaultPerformer.perform(.levelChange, performanceTime: .default) + } + } Text("\(Int(appState.menubarFontSize))") .font(.system(size: 11, design: .monospaced)) @@ -53,10 +66,20 @@ struct MenubarSettingsView: View { set: { appState.menubarTextMaxLength = Int($0) } ), in: 50...300, - step: 10 + step: 10, + onEditingChanged: { editing in + isDraggingTextLength = editing + NSHapticFeedbackManager.defaultPerformer.perform(.generic, performanceTime: .default) + } ) .frame(width: 150) .controlSize(.small) + .onChange(of: appState.menubarTextMaxLength) { oldValue, newValue in + guard isDraggingTextLength else { return } + if newValue != oldValue { + NSHapticFeedbackManager.defaultPerformer.perform(.levelChange, performanceTime: .default) + } + } Text("\(appState.menubarTextMaxLength)pt") .font(.system(size: 11, design: .monospaced)) diff --git a/airsync-mac/Screens/Settings/MirroringSettingsView.swift b/airsync-mac/Screens/Settings/MirroringSettingsView.swift index c5382107..7d841fb5 100644 --- a/airsync-mac/Screens/Settings/MirroringSettingsView.swift +++ b/airsync-mac/Screens/Settings/MirroringSettingsView.swift @@ -6,6 +6,7 @@ // import SwiftUI +import AppKit struct MirroringSettingsView: View { @ObservedObject var appState = AppState.shared @@ -21,6 +22,8 @@ struct MirroringSettingsView: View { @State private var tempBitrate: Double = 4.00 @State private var tempResolution: Double = 1200.00 @State private var isDragging = false + @State private var isDraggingBitrate = false + @State private var isDraggingResolution = false @State private var xCoords: String = "0" @State private var yCoords: String = "0" @@ -88,11 +91,23 @@ struct MirroringSettingsView: View { if !editing { AppState.shared.scrcpyBitrate = Int(tempBitrate) } + if editing { + NSHapticFeedbackManager.defaultPerformer.perform(.generic, performanceTime: .default) + } else { + NSHapticFeedbackManager.defaultPerformer.perform(.generic, performanceTime: .default) + } + isDraggingBitrate = editing isDragging = editing } ) .focusable(false) .frame(maxWidth: 150) + .onChange(of: tempBitrate) { oldValue, newValue in + guard isDraggingBitrate else { return } + if newValue != oldValue { + NSHapticFeedbackManager.defaultPerformer.perform(.levelChange, performanceTime: .default) + } + } Text(String(format: L("settings.mirroring.bitrateFormat"), AppState.shared.scrcpyBitrate)) .monospacedDigit() @@ -112,11 +127,23 @@ struct MirroringSettingsView: View { if !editing { AppState.shared.scrcpyResolution = Int(tempResolution) } + if editing { + NSHapticFeedbackManager.defaultPerformer.perform(.generic, performanceTime: .default) + } else { + NSHapticFeedbackManager.defaultPerformer.perform(.generic, performanceTime: .default) + } + isDraggingResolution = editing isDragging = editing } ) .focusable(false) .frame(maxWidth: 150) + .onChange(of: tempResolution) { oldValue, newValue in + guard isDraggingResolution else { return } + if newValue != oldValue { + NSHapticFeedbackManager.defaultPerformer.perform(.levelChange, performanceTime: .default) + } + } Text("\(AppState.shared.scrcpyResolution)") .monospacedDigit()