Align iOS player UI with new glass design
Some checks failed
CI / UI / Build (pull_request) Failing after 6s
CI / Scraper / Test (pull_request) Failing after 7s
CI / Scraper / Lint (pull_request) Failing after 11s
CI / Scraper / Build (pull_request) Has been skipped
iOS CI / Build (push) Has been cancelled
iOS CI / Test (push) Has been cancelled
iOS CI / Test (pull_request) Has been skipped
iOS CI / Build (pull_request) Failing after 2s
Some checks failed
CI / UI / Build (pull_request) Failing after 6s
CI / Scraper / Test (pull_request) Failing after 7s
CI / Scraper / Lint (pull_request) Failing after 11s
CI / Scraper / Build (pull_request) Has been skipped
iOS CI / Build (push) Has been cancelled
iOS CI / Test (push) Has been cancelled
iOS CI / Test (pull_request) Has been skipped
iOS CI / Build (pull_request) Failing after 2s
This commit is contained in:
@@ -23,19 +23,19 @@ struct RootTabView: View {
|
|||||||
ZStack(alignment: .bottom) {
|
ZStack(alignment: .bottom) {
|
||||||
TabView(selection: $selectedTab) {
|
TabView(selection: $selectedTab) {
|
||||||
HomeView()
|
HomeView()
|
||||||
.tabItem { Label("Home", systemImage: "house.fill") }
|
.tabItem { Label("Home", systemImage: "house") }
|
||||||
.tag(Tab.home)
|
.tag(Tab.home)
|
||||||
|
|
||||||
LibraryView()
|
LibraryView()
|
||||||
.tabItem { Label("Library", systemImage: "books.vertical.fill") }
|
.tabItem { Label("Library", systemImage: "books.vertical") }
|
||||||
.tag(Tab.library)
|
.tag(Tab.library)
|
||||||
|
|
||||||
BrowseView()
|
BrowseView()
|
||||||
.tabItem { Label("Discover", systemImage: "globe") }
|
.tabItem { Label("Discover", systemImage: "sparkles") }
|
||||||
.tag(Tab.browse)
|
.tag(Tab.browse)
|
||||||
|
|
||||||
ProfileView()
|
ProfileView()
|
||||||
.tabItem { Label("Profile", systemImage: "person.fill") }
|
.tabItem { Label("Profile", systemImage: "person.crop.circle") }
|
||||||
.tag(Tab.profile)
|
.tag(Tab.profile)
|
||||||
}
|
}
|
||||||
// Reserve space for the mini-player above the tab bar so scroll content
|
// Reserve space for the mini-player above the tab bar so scroll content
|
||||||
@@ -49,7 +49,7 @@ struct RootTabView: View {
|
|||||||
// Mini-player pinned above the tab bar (hidden while full player is open)
|
// Mini-player pinned above the tab bar (hidden while full player is open)
|
||||||
if audioPlayer.isActive && !showFullPlayer {
|
if audioPlayer.isActive && !showFullPlayer {
|
||||||
MiniPlayerView(showFullPlayer: $showFullPlayer)
|
MiniPlayerView(showFullPlayer: $showFullPlayer)
|
||||||
.padding(.bottom, tabBarHeight)
|
.padding(.bottom, max(tabBarHeight - 6, 0))
|
||||||
.transition(.move(edge: .bottom).combined(with: .opacity))
|
.transition(.move(edge: .bottom).combined(with: .opacity))
|
||||||
.animation(.spring(response: 0.35, dampingFraction: 0.8), value: audioPlayer.isActive)
|
.animation(.spring(response: 0.35, dampingFraction: 0.8), value: audioPlayer.isActive)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,28 +7,30 @@ struct MiniPlayerView: View {
|
|||||||
@Binding var showFullPlayer: Bool
|
@Binding var showFullPlayer: Bool
|
||||||
@EnvironmentObject var audioPlayer: AudioPlayerService
|
@EnvironmentObject var audioPlayer: AudioPlayerService
|
||||||
|
|
||||||
|
private let capsuleRadius: CGFloat = 24
|
||||||
|
|
||||||
/// Live drag offset while the user is swiping up (negative = moving up).
|
/// Live drag offset while the user is swiping up (negative = moving up).
|
||||||
@State private var dragOffset: CGFloat = 0
|
@State private var dragOffset: CGFloat = 0
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 6) {
|
||||||
// Progress line — thin amber strip at the very top
|
// Progress line — thin amber strip at the very top
|
||||||
GeometryReader { geo in
|
GeometryReader { geo in
|
||||||
ZStack(alignment: .leading) {
|
ZStack(alignment: .leading) {
|
||||||
Rectangle().fill(Color.white.opacity(0.08)).frame(height: 2)
|
Capsule().fill(Color.white.opacity(0.12)).frame(height: 2)
|
||||||
Rectangle()
|
Capsule()
|
||||||
.fill(Color.amber)
|
.fill(Color.amber)
|
||||||
.frame(width: geo.size.width * progress, height: 2)
|
.frame(width: geo.size.width * progress, height: 2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(height: 2)
|
.frame(height: 2)
|
||||||
|
|
||||||
HStack(spacing: 14) {
|
HStack(spacing: 12) {
|
||||||
// Cover thumbnail
|
// Cover thumbnail
|
||||||
Button { showFullPlayer = true } label: {
|
Button { showFullPlayer = true } label: {
|
||||||
AsyncCoverImage(url: audioPlayer.coverURL)
|
AsyncCoverImage(url: audioPlayer.coverURL)
|
||||||
.frame(width: 44, height: 44)
|
.frame(width: 46, height: 46)
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 8))
|
.clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous))
|
||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
|
|
||||||
@@ -39,7 +41,7 @@ struct MiniPlayerView: View {
|
|||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
Text(chapterLabel)
|
Text(chapterLabel)
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.white.opacity(0.65))
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
@@ -52,19 +54,20 @@ struct MiniPlayerView: View {
|
|||||||
ProgressView()
|
ProgressView()
|
||||||
.tint(.amber)
|
.tint(.amber)
|
||||||
.scaleEffect(0.85)
|
.scaleEffect(0.85)
|
||||||
.frame(width: 36, height: 36)
|
.frame(width: 34, height: 34)
|
||||||
case .ready:
|
case .ready:
|
||||||
Button { audioPlayer.togglePlayPause() } label: {
|
Button { audioPlayer.togglePlayPause() } label: {
|
||||||
Image(systemName: audioPlayer.isPlaying ? "pause.fill" : "play.fill")
|
Image(systemName: audioPlayer.isPlaying ? "pause.circle.fill" : "play.circle.fill")
|
||||||
.font(.system(size: 18, weight: .semibold))
|
.font(.system(size: 16, weight: .semibold))
|
||||||
.foregroundStyle(.primary)
|
.foregroundStyle(.white)
|
||||||
.frame(width: 36, height: 36)
|
.frame(width: 34, height: 34)
|
||||||
|
.background(.white.opacity(0.12), in: Circle())
|
||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
case .error:
|
case .error:
|
||||||
Image(systemName: "exclamationmark.circle.fill")
|
Image(systemName: "exclamationmark.circle.fill")
|
||||||
.foregroundStyle(.red)
|
.foregroundStyle(.red)
|
||||||
.frame(width: 36, height: 36)
|
.frame(width: 34, height: 34)
|
||||||
default:
|
default:
|
||||||
EmptyView()
|
EmptyView()
|
||||||
}
|
}
|
||||||
@@ -73,17 +76,34 @@ struct MiniPlayerView: View {
|
|||||||
// Dismiss
|
// Dismiss
|
||||||
Button { audioPlayer.stop() } label: {
|
Button { audioPlayer.stop() } label: {
|
||||||
Image(systemName: "xmark")
|
Image(systemName: "xmark")
|
||||||
.font(.system(size: 12, weight: .bold))
|
.font(.system(size: 11, weight: .bold))
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.white.opacity(0.7))
|
||||||
.frame(width: 30, height: 30)
|
.frame(width: 28, height: 28)
|
||||||
|
.background(.white.opacity(0.12), in: Circle())
|
||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 16)
|
.padding(.horizontal, 16)
|
||||||
.padding(.vertical, 10)
|
.padding(.vertical, 6)
|
||||||
}
|
}
|
||||||
.background(.ultraThinMaterial)
|
.padding(.horizontal, 10)
|
||||||
.shadow(color: .black.opacity(0.15), radius: 6, y: -2)
|
.padding(.vertical, 4)
|
||||||
|
.frame(maxWidth: 520)
|
||||||
|
.background(
|
||||||
|
RoundedRectangle(cornerRadius: capsuleRadius, style: .continuous)
|
||||||
|
.fill(.ultraThinMaterial)
|
||||||
|
.overlay(Color.black.opacity(0.18))
|
||||||
|
)
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: capsuleRadius, style: .continuous)
|
||||||
|
.stroke(Color.white.opacity(0.12), lineWidth: 1)
|
||||||
|
)
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: capsuleRadius, style: .continuous)
|
||||||
|
.stroke(Color.white.opacity(0.06), lineWidth: 1)
|
||||||
|
.blur(radius: 0.8)
|
||||||
|
)
|
||||||
|
.shadow(color: .black.opacity(0.25), radius: 16, y: 8)
|
||||||
// Follow finger upward while dragging, spring back or expand on release
|
// Follow finger upward while dragging, spring back or expand on release
|
||||||
.offset(y: min(dragOffset, 0))
|
.offset(y: min(dragOffset, 0))
|
||||||
.gesture(
|
.gesture(
|
||||||
@@ -131,69 +151,82 @@ struct FullPlayerView: View {
|
|||||||
/// Called when the view wants to close itself (Done button or drag-to-dismiss).
|
/// Called when the view wants to close itself (Done button or drag-to-dismiss).
|
||||||
var onDismiss: () -> Void = {}
|
var onDismiss: () -> Void = {}
|
||||||
|
|
||||||
|
@State private var isShuffled = false
|
||||||
|
@State private var isRepeating = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
// ── Background: blurred cover art ──────────────────────────────
|
LinearGradient(
|
||||||
GeometryReader { geo in
|
colors: [
|
||||||
KFImage(URL(string: audioPlayer.coverURL))
|
Color(red: 73 / 255, green: 55 / 255, blue: 102 / 255),
|
||||||
.resizable()
|
Color(red: 34 / 255, green: 24 / 255, blue: 49 / 255)
|
||||||
.scaledToFill()
|
],
|
||||||
.frame(width: geo.size.width, height: geo.size.height)
|
startPoint: .top,
|
||||||
.clipped()
|
endPoint: .bottom
|
||||||
.blur(radius: 40, opaque: true)
|
)
|
||||||
.overlay(Color.black.opacity(0.55))
|
|
||||||
.ignoresSafeArea()
|
|
||||||
}
|
|
||||||
.ignoresSafeArea()
|
.ignoresSafeArea()
|
||||||
|
|
||||||
// ── Content ────────────────────────────────────────────────────
|
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
// Drag handle pill — visual cue that you can swipe down to close
|
// Drag handle pill — visual cue that you can swipe down to close
|
||||||
Capsule()
|
Capsule()
|
||||||
.fill(Color.white.opacity(0.35))
|
.fill(Color.white.opacity(0.35))
|
||||||
.frame(width: 36, height: 4)
|
.frame(width: 46, height: 5)
|
||||||
.padding(.top, 12)
|
.padding(.top, 12)
|
||||||
.padding(.bottom, 8)
|
.padding(.bottom, 14)
|
||||||
|
|
||||||
Spacer(minLength: 0)
|
|
||||||
|
|
||||||
// Cover art
|
// Cover art
|
||||||
KFImage(URL(string: audioPlayer.coverURL))
|
KFImage(URL(string: audioPlayer.coverURL))
|
||||||
.resizable()
|
.resizable()
|
||||||
.placeholder {
|
.placeholder {
|
||||||
RoundedRectangle(cornerRadius: 18)
|
RoundedRectangle(cornerRadius: 22, style: .continuous)
|
||||||
.fill(.white.opacity(0.1))
|
.fill(.white.opacity(0.12))
|
||||||
.overlay(
|
.overlay(
|
||||||
Image(systemName: "book.closed")
|
Image(systemName: "music.note")
|
||||||
.font(.system(size: 48))
|
.font(.system(size: 52))
|
||||||
.foregroundStyle(.white.opacity(0.4))
|
.foregroundStyle(.white.opacity(0.35))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.scaledToFill()
|
.scaledToFill()
|
||||||
.frame(width: 260, height: 260)
|
.frame(width: 310, height: 310)
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 18))
|
.clipShape(RoundedRectangle(cornerRadius: 22, style: .continuous))
|
||||||
.shadow(color: .black.opacity(0.5), radius: 24, y: 12)
|
.shadow(color: .black.opacity(0.45), radius: 26, y: 16)
|
||||||
.padding(.horizontal, 48)
|
.padding(.horizontal, 32)
|
||||||
|
|
||||||
Spacer(minLength: 0)
|
|
||||||
|
|
||||||
// Title block
|
// Title block
|
||||||
VStack(spacing: 6) {
|
VStack(spacing: 8) {
|
||||||
Text((audioPlayer.chapterTitle.isEmpty ? "Chapter \(audioPlayer.chapter)" : audioPlayer.chapterTitle).strippingTrailingDate())
|
Text((audioPlayer.chapterTitle.isEmpty ? "Chapter \(audioPlayer.chapter)" : audioPlayer.chapterTitle).strippingTrailingDate())
|
||||||
.font(.title3.weight(.bold))
|
.font(.title3.weight(.bold))
|
||||||
.foregroundStyle(.white)
|
.foregroundStyle(.white)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
.lineLimit(2)
|
.lineLimit(2)
|
||||||
Text(audioPlayer.bookTitle)
|
Text(audioPlayer.bookTitle)
|
||||||
.font(.subheadline)
|
.font(.title3.weight(.regular))
|
||||||
.foregroundStyle(.white.opacity(0.65))
|
.foregroundStyle(.white.opacity(0.7))
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 32)
|
.padding(.horizontal, 32)
|
||||||
.padding(.top, 28)
|
.padding(.top, 22)
|
||||||
|
|
||||||
|
// Actions row
|
||||||
|
HStack(spacing: 22) {
|
||||||
|
Button {} label: {
|
||||||
|
Image(systemName: "heart")
|
||||||
|
.font(.system(size: 20, weight: .regular))
|
||||||
|
}
|
||||||
|
Button {} label: {
|
||||||
|
Image(systemName: "star")
|
||||||
|
.font(.system(size: 20, weight: .regular))
|
||||||
|
}
|
||||||
|
Button {} label: {
|
||||||
|
Image(systemName: "ellipsis")
|
||||||
|
.font(.system(size: 20, weight: .semibold))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.foregroundStyle(.white.opacity(0.9))
|
||||||
|
.padding(.top, 18)
|
||||||
|
|
||||||
// Seek bar
|
// Seek bar
|
||||||
VStack(spacing: 6) {
|
VStack(spacing: 8) {
|
||||||
PlayerSlider(
|
PlayerSlider(
|
||||||
value: Binding(
|
value: Binding(
|
||||||
get: { audioPlayer.currentTime },
|
get: { audioPlayer.currentTime },
|
||||||
@@ -204,40 +237,41 @@ struct FullPlayerView: View {
|
|||||||
HStack {
|
HStack {
|
||||||
Text(formatTime(audioPlayer.currentTime))
|
Text(formatTime(audioPlayer.currentTime))
|
||||||
Spacer()
|
Spacer()
|
||||||
Text(formatTime(audioPlayer.duration))
|
Text("-\(formatTime(max(audioPlayer.duration - audioPlayer.currentTime, 0)))")
|
||||||
}
|
}
|
||||||
.font(.caption.monospacedDigit())
|
.font(.caption.monospacedDigit())
|
||||||
.foregroundStyle(.white.opacity(0.5))
|
.foregroundStyle(.white.opacity(0.65))
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 28)
|
.padding(.horizontal, 28)
|
||||||
.padding(.top, 24)
|
.padding(.top, 18)
|
||||||
|
|
||||||
// Controls
|
// Controls
|
||||||
HStack(spacing: 0) {
|
HStack(spacing: 26) {
|
||||||
// ← skip back 15s
|
Button { isShuffled.toggle() } label: {
|
||||||
|
Image(systemName: "shuffle")
|
||||||
|
.font(.system(size: 18, weight: .semibold))
|
||||||
|
.foregroundStyle(isShuffled ? .white : .white.opacity(0.45))
|
||||||
|
}
|
||||||
|
|
||||||
Button { audioPlayer.skip(by: -15) } label: {
|
Button { audioPlayer.skip(by: -15) } label: {
|
||||||
Image(systemName: "gobackward.15")
|
Image(systemName: "gobackward.15")
|
||||||
.font(.system(size: 26, weight: .regular))
|
.font(.system(size: 24, weight: .regular))
|
||||||
.foregroundStyle(.white.opacity(0.9))
|
.foregroundStyle(.white)
|
||||||
.frame(maxWidth: .infinity)
|
|
||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
|
||||||
|
|
||||||
// play / pause
|
|
||||||
Button { audioPlayer.togglePlayPause() } label: {
|
Button { audioPlayer.togglePlayPause() } label: {
|
||||||
ZStack {
|
ZStack {
|
||||||
Circle()
|
Circle()
|
||||||
.fill(Color.amber)
|
.fill(Color.white)
|
||||||
.frame(width: 72, height: 72)
|
.frame(width: 72, height: 72)
|
||||||
.shadow(color: Color.amber.opacity(0.45), radius: 14, y: 4)
|
.shadow(color: .black.opacity(0.3), radius: 10, y: 6)
|
||||||
if audioPlayer.status == .generating {
|
if audioPlayer.status == .generating {
|
||||||
ProgressView()
|
ProgressView()
|
||||||
.tint(.white)
|
.tint(Color.black.opacity(0.7))
|
||||||
.scaleEffect(1.1)
|
|
||||||
} else {
|
} else {
|
||||||
Image(systemName: audioPlayer.isPlaying ? "pause.fill" : "play.fill")
|
Image(systemName: audioPlayer.isPlaying ? "pause.fill" : "play.fill")
|
||||||
.font(.system(size: 28, weight: .semibold))
|
.font(.system(size: 28, weight: .semibold))
|
||||||
.foregroundStyle(.white)
|
.foregroundStyle(Color.black.opacity(0.85))
|
||||||
.offset(x: audioPlayer.isPlaying ? 0 : 2)
|
.offset(x: audioPlayer.isPlaying ? 0 : 2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -245,76 +279,35 @@ struct FullPlayerView: View {
|
|||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
.disabled(audioPlayer.status == .generating)
|
.disabled(audioPlayer.status == .generating)
|
||||||
|
|
||||||
// → skip forward 30s
|
|
||||||
Button { audioPlayer.skip(by: 30) } label: {
|
Button { audioPlayer.skip(by: 30) } label: {
|
||||||
Image(systemName: "goforward.30")
|
Image(systemName: "goforward.30")
|
||||||
.font(.system(size: 26, weight: .regular))
|
.font(.system(size: 24, weight: .regular))
|
||||||
.foregroundStyle(.white.opacity(0.9))
|
.foregroundStyle(.white)
|
||||||
.frame(maxWidth: .infinity)
|
}
|
||||||
|
|
||||||
|
Button { isRepeating.toggle() } label: {
|
||||||
|
Image(systemName: "repeat")
|
||||||
|
.font(.system(size: 18, weight: .semibold))
|
||||||
|
.foregroundStyle(isRepeating ? .white : .white.opacity(0.45))
|
||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 20)
|
.padding(.horizontal, 20)
|
||||||
.padding(.top, 32)
|
.padding(.top, 18)
|
||||||
|
|
||||||
// Speed + Auto-next
|
// Utility row
|
||||||
HStack(spacing: 20) {
|
HStack(spacing: 30) {
|
||||||
Menu {
|
Button {} label: { Image(systemName: "airplayaudio") }
|
||||||
ForEach([0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0], id: \.self) { s in
|
Button {} label: { Image(systemName: "gearshape") }
|
||||||
Button {
|
Button { onDismiss() } label: { Image(systemName: "chevron.down") }
|
||||||
audioPlayer.setSpeed(s)
|
Button {} label: { Image(systemName: "quote.bubble") }
|
||||||
} label: {
|
Button {} label: { Image(systemName: "list.bullet") }
|
||||||
if s == audioPlayer.speed {
|
Button {} label: { Image(systemName: "moon.zzz") }
|
||||||
Label("\(s, specifier: "%.2g")×", systemImage: "checkmark")
|
|
||||||
} else {
|
|
||||||
Text("\(s, specifier: "%.2g")×")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} label: {
|
|
||||||
Text("\(audioPlayer.speed, specifier: "%.2g")×")
|
|
||||||
.font(.subheadline.weight(.bold))
|
|
||||||
.foregroundStyle(.white)
|
|
||||||
.padding(.horizontal, 16)
|
|
||||||
.padding(.vertical, 8)
|
|
||||||
.background(.white.opacity(0.18), in: Capsule())
|
|
||||||
}
|
}
|
||||||
|
.font(.system(size: 18, weight: .semibold))
|
||||||
|
.foregroundStyle(.white.opacity(0.75))
|
||||||
|
.padding(.top, 24)
|
||||||
|
|
||||||
Spacer()
|
Spacer(minLength: 26)
|
||||||
|
|
||||||
// Auto-next toggle
|
|
||||||
Button {
|
|
||||||
audioPlayer.autoNext.toggle()
|
|
||||||
} label: {
|
|
||||||
HStack(spacing: 6) {
|
|
||||||
Image(systemName: "forward.end")
|
|
||||||
.font(.system(size: 13, weight: .semibold))
|
|
||||||
Text("Auto-next")
|
|
||||||
.font(.subheadline.weight(.semibold))
|
|
||||||
}
|
|
||||||
.foregroundStyle(audioPlayer.autoNext ? Color.amber : .white.opacity(0.55))
|
|
||||||
.padding(.horizontal, 16)
|
|
||||||
.padding(.vertical, 8)
|
|
||||||
.background(
|
|
||||||
audioPlayer.autoNext
|
|
||||||
? Color.amber.opacity(0.18)
|
|
||||||
: Color.white.opacity(0.1),
|
|
||||||
in: Capsule()
|
|
||||||
)
|
|
||||||
.overlay(
|
|
||||||
Capsule()
|
|
||||||
.stroke(
|
|
||||||
audioPlayer.autoNext ? Color.amber.opacity(0.5) : Color.clear,
|
|
||||||
lineWidth: 1
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.buttonStyle(.plain)
|
|
||||||
}
|
|
||||||
.padding(.horizontal, 28)
|
|
||||||
.padding(.top, 28)
|
|
||||||
|
|
||||||
Spacer(minLength: 32)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user