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
|
||||
|
||||
@Published var flipAngle: Double = 0
|
||||
@Published var lastFlipDirection: SkipDirection = .forward
|
||||
private let flipAnimationDuration: TimeInterval = 0.45
|
||||
private var flipCooldownActive: Bool = false
|
||||
|
||||
@Published var isTransitioning: Bool = false
|
||||
private var transitionWorkItem: DispatchWorkItem?
|
||||
|
|
@ -380,8 +382,22 @@ class MusicManager: ObservableObject {
|
|||
}
|
||||
|
||||
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)) {
|
||||
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 {
|
||||
case .track:
|
||||
if direction == .forward {
|
||||
lastFlipDirection = .forward
|
||||
nextTrack()
|
||||
} else {
|
||||
lastFlipDirection = .backward
|
||||
previousTrack()
|
||||
}
|
||||
case .tenSecond:
|
||||
|
|
@ -932,24 +950,29 @@ extension MusicManager {
|
|||
private struct AlbumArtFlipModifier: ViewModifier {
|
||||
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 {
|
||||
content
|
||||
.rotation3DEffect(
|
||||
.degrees(angle),
|
||||
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