iOS 1.0.1: mini player, Now Playing spacing, play spinner fix
- Mini player floats as a solid card and every screen leaves room for it, so it no longer covers the bottom of lists - More space between title and scrubber; panel switches animate - Play button no longer spins forever after changing tracks: the engine now always reports buffering on load - Springier button presses and play/pause symbol transitions Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
fdf9e970b2
commit
2ddfe051d8
5 files changed
+61
-24
No files matched your search
@@ -108,8 +108,7 @@ struct NowPlayingScreen: View {
|
||||
}
|
||||
}
|
||||
.frame(maxHeight: .infinity)
|
||||
.transition(.opacity)
|
||||
.animation(.easeInOut(duration: 0.18), value: panel)
|
||||
.transition(.opacity.combined(with: .scale(scale: 0.96)))
|
||||
controls(track)
|
||||
} else {
|
||||
Text("Nothing Playing").font(.title3.weight(.semibold)).foregroundStyle(c.label2).frame(maxHeight: .infinity)
|
||||
@@ -147,9 +146,10 @@ struct NowPlayingScreen: View {
|
||||
EllipsisButton(items: ui.menu.track(track))
|
||||
}
|
||||
.padding(.top, 14)
|
||||
.transition(.opacity.combined(with: .move(edge: .top)))
|
||||
}
|
||||
ProgressVolumeBar(played: c.label, unplayed: c.label3, centerLabel: badge(track))
|
||||
.padding(.top, 18)
|
||||
.padding(.top, panel == .Art ? 34 : 18)
|
||||
HStack {
|
||||
Spacer()
|
||||
TransportButton(symbol: "backward.fill", size: 34, action: pm.previous)
|
||||
@@ -157,8 +157,9 @@ struct NowPlayingScreen: View {
|
||||
Button(action: pm.togglePlay) {
|
||||
ZStack {
|
||||
if pm.buffering && pm.playWhenReady { ProgressView().controlSize(.large).tint(c.label) }
|
||||
else { Image(systemName: pm.playWhenReady ? "pause.fill" : "play.fill").font(.system(size: 48)).foregroundStyle(c.label) }
|
||||
else { Image(systemName: pm.playWhenReady ? "pause.fill" : "play.fill").font(.system(size: 48)).foregroundStyle(c.label).contentTransition(.symbolEffect(.replace)) }
|
||||
}
|
||||
.animation(.snappy(duration: 0.25), value: pm.playWhenReady)
|
||||
.frame(width: 80, height: 80)
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
@@ -169,15 +170,20 @@ struct NowPlayingScreen: View {
|
||||
}
|
||||
.frame(height: 84)
|
||||
HStack {
|
||||
PanelToggle(symbol: "slider.vertical.3", on: panel == .Mixer) { panel = panel == .Mixer ? .Art : .Mixer }
|
||||
PanelToggle(symbol: "slider.vertical.3", on: panel == .Mixer) { show(.Mixer) }
|
||||
Button { ui.showInSettings(.Eq) } label: { routeLabel(c) }.buttonStyle(PressFade()).frame(maxWidth: .infinity)
|
||||
PanelToggle(symbol: "list.bullet", on: panel == .Queue) { panel = panel == .Queue ? .Art : .Queue }
|
||||
PanelToggle(symbol: "list.bullet", on: panel == .Queue) { show(.Queue) }
|
||||
}
|
||||
.frame(height: 48)
|
||||
}
|
||||
.padding(.horizontal, 28)
|
||||
}
|
||||
|
||||
/// Opens a panel, or goes back to the art if it's already open.
|
||||
private func show(_ to: NpPanel) {
|
||||
withAnimation(.spring(duration: 0.38, bounce: 0.12)) { panel = panel == to ? .Art : to }
|
||||
}
|
||||
|
||||
private func routeLabel(_ c: Palette) -> some View {
|
||||
let eq = g.eq
|
||||
let device = eq.device
|
||||
@@ -209,8 +215,8 @@ struct NowPlayingScreen: View {
|
||||
/// Fades a little while pressed, like Apple Music's transport.
|
||||
struct PressFade: ButtonStyle {
|
||||
func makeBody(configuration: Configuration) -> some View {
|
||||
configuration.label.opacity(configuration.isPressed ? 0.45 : 1).scaleEffect(configuration.isPressed ? 0.92 : 1)
|
||||
.animation(.easeOut(duration: 0.12), value: configuration.isPressed)
|
||||
configuration.label.opacity(configuration.isPressed ? 0.6 : 1).scaleEffect(configuration.isPressed ? 0.9 : 1)
|
||||
.animation(configuration.isPressed ? .easeOut(duration: 0.1) : .spring(duration: 0.32, bounce: 0.45), value: configuration.isPressed)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -758,37 +764,49 @@ struct MiniPlayer: View {
|
||||
let pm = g.playback
|
||||
if let t = pm.current?.track {
|
||||
HStack(spacing: 0) {
|
||||
Artwork(thumb: t.thumb, size: 46, corner: 6).padding(.trailing, 12)
|
||||
Artwork(thumb: t.thumb, size: 44, corner: 8).padding(.trailing, 12)
|
||||
VStack(alignment: .leading, spacing: 1) {
|
||||
Text(t.title).font(.subheadline.weight(.medium)).foregroundStyle(p.label).lineLimit(1)
|
||||
Text(t.title).font(.subheadline.weight(.semibold)).foregroundStyle(p.label).lineLimit(1)
|
||||
Text(t.artist).font(.footnote).foregroundStyle(p.label2).lineLimit(1)
|
||||
}
|
||||
Spacer(minLength: 6)
|
||||
Button(action: pm.togglePlay) {
|
||||
ZStack {
|
||||
if pm.buffering && pm.playWhenReady { ProgressView() }
|
||||
else { Image(systemName: pm.playWhenReady ? "pause.fill" : "play.fill").font(.system(size: 24)) }
|
||||
else { Image(systemName: pm.playWhenReady ? "pause.fill" : "play.fill").font(.system(size: 22)).contentTransition(.symbolEffect(.replace)) }
|
||||
}
|
||||
.foregroundStyle(p.label).frame(width: 48, height: 48).contentShape(Rectangle())
|
||||
.animation(.snappy(duration: 0.25), value: pm.playWhenReady)
|
||||
.foregroundStyle(p.label).frame(width: 44, height: 44).contentShape(Rectangle())
|
||||
}
|
||||
.buttonStyle(PressFade())
|
||||
Button(action: pm.next) {
|
||||
Image(systemName: "forward.fill").font(.system(size: 22)).foregroundStyle(p.label).frame(width: 48, height: 48).contentShape(Rectangle())
|
||||
Image(systemName: "forward.fill").font(.system(size: 20)).foregroundStyle(p.label).frame(width: 44, height: 44).contentShape(Rectangle())
|
||||
}
|
||||
.buttonStyle(PressFade())
|
||||
}
|
||||
.padding(.leading, 16).padding(.trailing, 8)
|
||||
.frame(height: 64)
|
||||
.background(.bar)
|
||||
.overlay(alignment: .top) { p.separator.frame(height: 1 / scale) }
|
||||
.padding(.leading, 8).padding(.trailing, 6)
|
||||
.frame(height: 60)
|
||||
.background {
|
||||
// A solid card in the app's own colours instead of system glass.
|
||||
let shape = RoundedRectangle(cornerRadius: 14, style: .continuous)
|
||||
ZStack {
|
||||
shape.fill(p.cell)
|
||||
shape.fill(p.fill.opacity(p.dark ? 0.5 : 0))
|
||||
shape.strokeBorder(p.separator, lineWidth: 1 / scale)
|
||||
}
|
||||
.shadow(color: .black.opacity(p.dark ? 0.5 : 0.12), radius: 12, y: 4)
|
||||
}
|
||||
.overlay(alignment: .bottom) {
|
||||
// Hairline progress along the bottom edge.
|
||||
// Thin progress line along the bottom of the card.
|
||||
TimelineView(.animation(minimumInterval: 0.25, paused: !pm.playWhenReady)) { _ in
|
||||
let d = pm.durationMs
|
||||
let f = d > 0 ? min(max(Double(pm.positionMs()) / Double(d), 0), 1) : 0
|
||||
GeometryReader { geo in p.accent.frame(width: geo.size.width * f, height: 2) }.frame(height: 2)
|
||||
}
|
||||
.padding(.horizontal, 14)
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.bottom, 6)
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture { ui.openNowPlaying() }
|
||||
.gesture(DragGesture(minimumDistance: 20).onEnded { v in if v.translation.height < -30 { ui.openNowPlaying() } })
|
||||
|
||||
Reference in new issue
Block a user