91 lines
4.0 KiB
Swift
91 lines
4.0 KiB
Swift
import Playback
|
|
import SwiftUI
|
|
|
|
struct QueueView: View {
|
|
@Environment(PlayerModel.self) private var player
|
|
|
|
var body: some View {
|
|
VStack(spacing: 0) {
|
|
HStack(spacing: 12) {
|
|
toggle("Shuffle", "shuffle", isOn: player.state.isShuffled) { player.send(.setShuffle(!player.state.isShuffled)) }
|
|
toggle("Repeat", player.state.repeatMode == .one ? "repeat.1" : "repeat", isOn: player.state.repeatMode != .off) {
|
|
let modes = RepeatMode.allCases
|
|
let index = modes.firstIndex(of: player.state.repeatMode) ?? 0
|
|
player.send(.setRepeat(modes[(index + 1) % modes.count]))
|
|
}
|
|
}
|
|
Text("Playing Next")
|
|
.font(.headline)
|
|
.foregroundStyle(.white)
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
.padding(.top, 18)
|
|
.padding(.bottom, 6)
|
|
List {
|
|
ForEach(player.state.upcoming) { item in
|
|
Button {
|
|
player.send(.jump(to: item.id))
|
|
} label: {
|
|
HStack(spacing: 12) {
|
|
ArtworkView(artwork: item.track.artwork, shape: .rounded(5)).frame(width: 48, height: 48)
|
|
VStack(alignment: .leading, spacing: 2) {
|
|
Text(item.track.title).foregroundStyle(.white)
|
|
Text(item.track.artistName).font(.subheadline).foregroundStyle(.white.opacity(0.6))
|
|
}
|
|
.lineLimit(1)
|
|
Spacer(minLength: 0)
|
|
Image(systemName: "line.3.horizontal").foregroundStyle(.white.opacity(0.3))
|
|
}
|
|
.contentShape(.rect)
|
|
}
|
|
.buttonStyle(.plain)
|
|
.listRowBackground(Color.clear)
|
|
.listRowSeparator(.hidden)
|
|
.listRowInsets(EdgeInsets(top: 4, leading: 0, bottom: 4, trailing: 0))
|
|
}
|
|
.onMove(perform: move)
|
|
.onDelete { offsets in
|
|
offsets.map { player.state.upcoming[$0].id }.forEach { player.send(.remove($0)) }
|
|
}
|
|
}
|
|
.listStyle(.plain)
|
|
.scrollContentBackground(.hidden)
|
|
.scrollIndicators(.hidden)
|
|
.overlay {
|
|
if player.state.upcoming.isEmpty {
|
|
Text("Nothing up next").foregroundStyle(.white.opacity(0.5))
|
|
}
|
|
}
|
|
.mask {
|
|
LinearGradient(
|
|
stops: [.init(color: .black, location: 0.9), .init(color: .clear, location: 1)],
|
|
startPoint: .top,
|
|
endPoint: .bottom
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func move(from offsets: IndexSet, to destination: Int) {
|
|
let upcoming = player.state.upcoming
|
|
guard let from = offsets.first else { return }
|
|
var reordered = upcoming
|
|
reordered.move(fromOffsets: offsets, toOffset: destination)
|
|
guard let landed = reordered.firstIndex(where: { $0.id == upcoming[from].id }) else { return }
|
|
player.send(.move(item: upcoming[from].id, after: landed > 0 ? reordered[landed - 1].id : nil))
|
|
}
|
|
|
|
private func toggle(_ title: LocalizedStringKey, _ systemImage: String, isOn: Bool, action: @escaping () -> Void) -> some View {
|
|
Button(action: action) {
|
|
Label(title, systemImage: systemImage)
|
|
.labelStyle(.iconOnly)
|
|
.font(.subheadline.weight(.semibold))
|
|
.foregroundStyle(isOn ? Color.black.opacity(0.8) : .white)
|
|
.frame(maxWidth: .infinity, minHeight: 38)
|
|
.background(.white.opacity(isOn ? 0.55 : 0.12), in: .capsule)
|
|
.contentShape(.rect)
|
|
}
|
|
.buttonStyle(.plain)
|
|
.accessibilityAddTraits(isOn ? .isSelected : [])
|
|
}
|
|
}
|