mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-27 15:14:33 +00:00
Update: Album art flip
This commit is contained in:
parent
2085815ab5
commit
2db3fc9aaf
1 changed files with 36 additions and 13 deletions
|
|
@ -137,7 +137,9 @@ class MusicManager: ObservableObject {
|
||||||
private var lastArtworkBundleIdentifier: String? = nil
|
private var lastArtworkBundleIdentifier: String? = nil
|
||||||
|
|
||||||
@Published var flipAngle: Double = 0
|
@Published var flipAngle: Double = 0
|
||||||
|
@Published var lastFlipDirection: SkipDirection = .forward
|
||||||
private let flipAnimationDuration: TimeInterval = 0.45
|
private let flipAnimationDuration: TimeInterval = 0.45
|
||||||
|
private var flipCooldownActive: Bool = false
|
||||||
|
|
||||||
@Published var isTransitioning: Bool = false
|
@Published var isTransitioning: Bool = false
|
||||||
private var transitionWorkItem: DispatchWorkItem?
|
private var transitionWorkItem: DispatchWorkItem?
|
||||||
|
|
@ -380,8 +382,22 @@ class MusicManager: ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func triggerFlipAnimation() {
|
private func triggerFlipAnimation() {
|
||||||
|
// Debounce: rapid metadata updates (title, artwork, bundle arriving
|
||||||
|
// separately for one track change) should only produce a single flip.
|
||||||
|
guard !flipCooldownActive else { return }
|
||||||
|
flipCooldownActive = true
|
||||||
|
|
||||||
|
// Direction: positive rotation = next (page turn forward),
|
||||||
|
// negative rotation = previous (page turn backward).
|
||||||
|
let delta: Double = lastFlipDirection == .forward ? 180 : -180
|
||||||
withAnimation(.easeInOut(duration: flipAnimationDuration)) {
|
withAnimation(.easeInOut(duration: flipAnimationDuration)) {
|
||||||
flipAngle += 180
|
flipAngle += delta
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset cooldown after the animation completes so the next
|
||||||
|
// genuine track change can flip again.
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + flipAnimationDuration + 0.15) { [weak self] in
|
||||||
|
self?.flipCooldownActive = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -624,8 +640,10 @@ class MusicManager: ObservableObject {
|
||||||
switch behavior {
|
switch behavior {
|
||||||
case .track:
|
case .track:
|
||||||
if direction == .forward {
|
if direction == .forward {
|
||||||
|
lastFlipDirection = .forward
|
||||||
nextTrack()
|
nextTrack()
|
||||||
} else {
|
} else {
|
||||||
|
lastFlipDirection = .backward
|
||||||
previousTrack()
|
previousTrack()
|
||||||
}
|
}
|
||||||
case .tenSecond:
|
case .tenSecond:
|
||||||
|
|
@ -932,24 +950,29 @@ extension MusicManager {
|
||||||
private struct AlbumArtFlipModifier: ViewModifier {
|
private struct AlbumArtFlipModifier: ViewModifier {
|
||||||
let angle: Double
|
let angle: Double
|
||||||
|
|
||||||
private var normalizedAngle: Double {
|
|
||||||
var value = angle.truncatingRemainder(dividingBy: 360)
|
|
||||||
if value < 0 { value += 360 }
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
private var mirrorScale: CGFloat {
|
|
||||||
(normalizedAngle > 90 && normalizedAngle < 270) ? -1 : 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func body(content: Content) -> some View {
|
func body(content: Content) -> some View {
|
||||||
content
|
content
|
||||||
.rotation3DEffect(
|
.rotation3DEffect(
|
||||||
.degrees(angle),
|
.degrees(angle),
|
||||||
axis: (x: 0, y: 1, z: 0),
|
axis: (x: 0, y: 1, z: 0),
|
||||||
perspective: 0.65
|
anchor: .center,
|
||||||
|
anchorZ: 0,
|
||||||
|
perspective: 0.5
|
||||||
)
|
)
|
||||||
.scaleEffect(x: mirrorScale, y: 1)
|
// Counter-rotate the content so the image never appears mirrored.
|
||||||
|
// At odd multiples of 180° the 3D rotation mirrors along X;
|
||||||
|
// applying an opposite scaleEffect cancels that out.
|
||||||
|
.scaleEffect(x: cosineSign(for: angle), y: 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns +1 when the front face is showing, −1 when the back face is showing.
|
||||||
|
private func cosineSign(for degrees: Double) -> CGFloat {
|
||||||
|
let cos = Darwin.cos(degrees * .pi / 180)
|
||||||
|
// Use a small tolerance to avoid flickering exactly at 90°/270°.
|
||||||
|
if cos > 0.001 { return 1 }
|
||||||
|
if cos < -0.001 { return -1 }
|
||||||
|
// At the exact edge, prefer the side we're animating toward.
|
||||||
|
return degrees.truncatingRemainder(dividingBy: 360) >= 0 ? -1 : 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue