Files
stemify/ios/Stemify/UI/Screens/NowPlayingOptions.swift
rootandClaude Opus 5.5 fabca5d1aa iOS: Duo layout fixes, swipe-up option, fuller backup restore
- DuoRail sits at the bottom of the strip, clear of the back buttons
- Setting to turn off swipe up for Playing Next
- Tighter Now Playing on iPhone Duo (folded and docked), regular phones unchanged
- Bigger stacked stem faders in the Duo deck
- Backup v2 restores song mixes, custom presets and stem playlist covers

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 09:07:22 -08:00

102 lines
3.9 KiB
Swift

import SwiftUI
/// The buttons under the transport on Now Playing that the user can hide.
enum NowPlayingButton: String, CaseIterable, Identifiable {
case Mixer, Speed, Route, AirPlay, Karaoke
var id: String { rawValue }
var label: String {
switch self {
case .Mixer: "Stem Mixer"
case .Speed: "Speed"
case .Route: "Output & EQ"
case .AirPlay: "AirPlay"
case .Karaoke: "Karaoke"
}
}
var icon: String {
switch self {
case .Mixer: "slider.vertical.3"
case .Speed: "speedometer"
case .Route: "hifispeaker"
case .AirPlay: "airplayaudio"
case .Karaoke: "music.mic"
}
}
static func hidden(_ s: String) -> Set<NowPlayingButton> { Set(s.split(separator: ",").compactMap { Self(rawValue: String($0)) }) }
}
struct NowPlayingOptionsScreen: View {
private var g: AppGraph { .shared }
@Environment(\.palette) private var p
var body: some View {
@Bindable var s = g.settings
List {
Section {
ForEach(NowPlayingButton.allCases) { b in
Toggle(isOn: Binding(
get: { !NowPlayingButton.hidden(s.hiddenNowPlaying).contains(b) },
set: { on in
var h = NowPlayingButton.hidden(s.hiddenNowPlaying)
if on { h.remove(b) } else { h.insert(b) }
s.hiddenNowPlaying = h.map(\.rawValue).sorted().joined(separator: ",")
})) {
HStack(spacing: 14) {
SettingsIcon(symbol: b.icon, bg: 0x5AC8FA)
Text(b.label).foregroundStyle(p.label)
}
}
.tint(Color(hex: 0x34C759))
.themedRow()
}
} footer: { Text("Choose which buttons show under the playback controls.").foregroundStyle(p.label2) }
Section {
Toggle(isOn: $s.swipeUpQueue) {
HStack(spacing: 14) {
SettingsIcon(symbol: "hand.draw", bg: 0x5856D6)
Text("Swipe Up for Playing Next").foregroundStyle(p.label)
}
}
.tint(Color(hex: 0x34C759))
.themedRow()
} header: { Text("Gestures").foregroundStyle(p.label2) } footer: {
Text("Off, swiping up on the art or the stems does nothing. Tap Playing Next under the controls instead.").foregroundStyle(p.label2)
}
Section {
Toggle(isOn: $s.lockScreenMix) {
HStack(spacing: 14) {
SettingsIcon(symbol: "lock.fill", bg: 0xFF9500)
Text("Back Button Changes Mix").foregroundStyle(p.label)
}
}
.tint(Color(hex: 0x34C759))
.themedRow()
} header: { Text("Lock Screen").foregroundStyle(p.label2) } footer: {
Text("While stems play, the back button on the lock screen, Control Center and headphones moves to the next mix instead of the previous song, like the Mix button in CarPlay. The mix name shows after the artist.").foregroundStyle(p.label2)
}
}
.listStyle(.insetGrouped)
.themedList()
.navigationTitle("Now Playing")
}
}
struct StepperCell: View {
let title: String
@Binding var value: Int
let icon: String
let iconBg: UInt32
@Environment(\.palette) private var p
var body: some View {
Stepper(value: $value, in: 0...50) {
HStack(spacing: 14) {
SettingsIcon(symbol: icon, bg: iconBg)
Text(title).foregroundStyle(p.label)
Spacer(minLength: 8)
Text(value == 0 ? "Off" : "\(value) songs").foregroundStyle(p.label2)
}
}
.themedRow()
}
}