mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-27 15:14:33 +00:00
Add Gesture controls for media skip and forward
This commit is contained in:
parent
48b3266e76
commit
d70569e32f
9 changed files with 267 additions and 21 deletions
|
|
@ -8,9 +8,14 @@ struct MusicControlOverlay: View {
|
|||
@ObservedObject private var musicManager = MusicManager.shared
|
||||
@Default(.musicSkipBehavior) private var musicSkipBehavior
|
||||
|
||||
private let seekInterval: TimeInterval = 10
|
||||
private let seekInterval: TimeInterval = MusicManager.skipGestureSeekInterval
|
||||
private let skipPressMagnitude: CGFloat = 8
|
||||
|
||||
private var trackBackwardPressEffect: FloatingMediaButton.PressEffect { .nudge(-skipPressMagnitude) }
|
||||
private var trackForwardPressEffect: FloatingMediaButton.PressEffect { .nudge(skipPressMagnitude) }
|
||||
private var tenSecondBackwardPressEffect: FloatingMediaButton.PressEffect { .wiggle(.counterClockwise) }
|
||||
private var tenSecondForwardPressEffect: FloatingMediaButton.PressEffect { .wiggle(.clockwise) }
|
||||
|
||||
private var controlsEnabled: Bool {
|
||||
!musicManager.isPlayerIdle || musicManager.bundleIdentifier != nil
|
||||
}
|
||||
|
|
@ -39,14 +44,14 @@ struct MusicControlOverlay: View {
|
|||
case .track:
|
||||
return ButtonConfig(
|
||||
icon: "backward.fill",
|
||||
pressEffect: .nudge(-skipPressMagnitude),
|
||||
pressEffect: trackBackwardPressEffect,
|
||||
symbolEffect: .replace,
|
||||
action: { MusicManager.shared.previousTrack() }
|
||||
)
|
||||
case .tenSecond:
|
||||
return ButtonConfig(
|
||||
icon: "gobackward.10",
|
||||
pressEffect: .wiggle(.counterClockwise),
|
||||
pressEffect: tenSecondBackwardPressEffect,
|
||||
symbolEffect: .wiggle,
|
||||
action: { MusicManager.shared.seek(by: -seekInterval) }
|
||||
)
|
||||
|
|
@ -58,14 +63,14 @@ struct MusicControlOverlay: View {
|
|||
case .track:
|
||||
return ButtonConfig(
|
||||
icon: "forward.fill",
|
||||
pressEffect: .nudge(skipPressMagnitude),
|
||||
pressEffect: trackForwardPressEffect,
|
||||
symbolEffect: .replace,
|
||||
action: { MusicManager.shared.nextTrack() }
|
||||
)
|
||||
case .tenSecond:
|
||||
return ButtonConfig(
|
||||
icon: "goforward.10",
|
||||
pressEffect: .wiggle(.clockwise),
|
||||
pressEffect: tenSecondForwardPressEffect,
|
||||
symbolEffect: .wiggle,
|
||||
action: { MusicManager.shared.seek(by: seekInterval) }
|
||||
)
|
||||
|
|
@ -84,6 +89,9 @@ struct MusicControlOverlay: View {
|
|||
var body: some View {
|
||||
let verticalPadding = max(8, notchHeight * 0.12)
|
||||
|
||||
let backwardGestureTrigger = skipGestureTrigger(for: .backward)
|
||||
let forwardGestureTrigger = skipGestureTrigger(for: .forward)
|
||||
|
||||
HStack(spacing: 18) {
|
||||
FloatingMediaButton(
|
||||
icon: backwardConfig.icon,
|
||||
|
|
@ -92,6 +100,8 @@ struct MusicControlOverlay: View {
|
|||
foregroundColor: .white.opacity(controlsEnabled ? 0.9 : 0.35),
|
||||
pressEffect: backwardConfig.pressEffect,
|
||||
symbolEffectStyle: backwardConfig.symbolEffect,
|
||||
externalTriggerToken: backwardGestureTrigger?.token,
|
||||
externalTriggerEffect: backwardGestureTrigger?.pressEffect,
|
||||
isEnabled: controlsEnabled,
|
||||
action: backwardConfig.action
|
||||
)
|
||||
|
|
@ -103,6 +113,8 @@ struct MusicControlOverlay: View {
|
|||
foregroundColor: .white,
|
||||
pressEffect: playPauseConfig.pressEffect,
|
||||
symbolEffectStyle: playPauseConfig.symbolEffect,
|
||||
externalTriggerToken: nil,
|
||||
externalTriggerEffect: nil,
|
||||
isEnabled: controlsEnabled,
|
||||
action: playPauseConfig.action
|
||||
)
|
||||
|
|
@ -114,6 +126,8 @@ struct MusicControlOverlay: View {
|
|||
foregroundColor: .white.opacity(controlsEnabled ? 0.9 : 0.35),
|
||||
pressEffect: forwardConfig.pressEffect,
|
||||
symbolEffectStyle: forwardConfig.symbolEffect,
|
||||
externalTriggerToken: forwardGestureTrigger?.token,
|
||||
externalTriggerEffect: forwardGestureTrigger?.pressEffect,
|
||||
isEnabled: controlsEnabled,
|
||||
action: forwardConfig.action
|
||||
)
|
||||
|
|
@ -147,6 +161,8 @@ private struct FloatingMediaButton: View {
|
|||
let foregroundColor: Color
|
||||
let pressEffect: PressEffect
|
||||
let symbolEffectStyle: SymbolEffectStyle
|
||||
let externalTriggerToken: Int?
|
||||
let externalTriggerEffect: PressEffect?
|
||||
let isEnabled: Bool
|
||||
let action: () -> Void
|
||||
|
||||
|
|
@ -154,6 +170,7 @@ private struct FloatingMediaButton: View {
|
|||
@State private var pressOffset: CGFloat = 0
|
||||
@State private var rotationAngle: Double = 0
|
||||
@State private var wiggleToken: Int = 0
|
||||
@State private var lastExternalTriggerToken: Int?
|
||||
|
||||
var body: some View {
|
||||
Button {
|
||||
|
|
@ -178,6 +195,12 @@ private struct FloatingMediaButton: View {
|
|||
isHovering = hovering
|
||||
}
|
||||
}
|
||||
.onChange(of: externalTriggerToken) { _, newToken in
|
||||
guard isEnabled, let newToken else { return }
|
||||
guard newToken != lastExternalTriggerToken else { return }
|
||||
lastExternalTriggerToken = newToken
|
||||
triggerPressEffect(override: externalTriggerEffect)
|
||||
}
|
||||
}
|
||||
|
||||
private var iconView: some View {
|
||||
|
|
@ -219,8 +242,10 @@ private struct FloatingMediaButton: View {
|
|||
}
|
||||
}
|
||||
|
||||
private func triggerPressEffect() {
|
||||
switch pressEffect {
|
||||
private func triggerPressEffect(override: PressEffect? = nil) {
|
||||
let activeEffect = override ?? pressEffect
|
||||
|
||||
switch activeEffect {
|
||||
case .none:
|
||||
return
|
||||
case .nudge(let amount):
|
||||
|
|
@ -269,6 +294,28 @@ private struct FloatingMediaButton: View {
|
|||
}
|
||||
}
|
||||
|
||||
private extension MusicControlOverlay {
|
||||
func skipGestureTrigger(for direction: MusicManager.SkipDirection) -> (token: Int, pressEffect: FloatingMediaButton.PressEffect)? {
|
||||
guard let pulse = musicManager.skipGesturePulse, pulse.direction == direction else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let effect: FloatingMediaButton.PressEffect
|
||||
switch (pulse.behavior, direction) {
|
||||
case (.track, .backward):
|
||||
effect = trackBackwardPressEffect
|
||||
case (.track, .forward):
|
||||
effect = trackForwardPressEffect
|
||||
case (.tenSecond, .backward):
|
||||
effect = tenSecondBackwardPressEffect
|
||||
case (.tenSecond, .forward):
|
||||
effect = tenSecondForwardPressEffect
|
||||
}
|
||||
|
||||
return (token: pulse.token, pressEffect: effect)
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
MusicControlOverlay(notchHeight: 34, cornerRadius: 14)
|
||||
.padding()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue