import SwiftUI import SwiftBricks @main struct MiniPlayerApp: App { @StateObject private var bridge = PlayerBridge() var body: some Scene { WindowGroup { ContentView(bridge: bridge) .frame(minWidth: 900, minHeight: 600) .onAppear { bridge.setup() } } #if os(macOS) .commands { CommandGroup(replacing: .newItem) {} CommandMenu("播放") { Button("播放/暂停") { bridge.togglePlayPause() } .keyboardShortcut(" ", modifiers: []) Divider() Button("上一首") { bridge.playPrev() } .keyboardShortcut("[", modifiers: []) Button("下一首") { bridge.playNext() } .keyboardShortcut("]", modifiers: []) Divider() Button("全屏") { bridge.toggleFullscreen() } .keyboardShortcut("f", modifiers: .command) Divider() Button("循环模式") { bridge.cycleRepeatMode() } .keyboardShortcut("r", modifiers: .command) } CommandMenu("文件") { Button("打开文件...") { bridge.openFileDialog() } .keyboardShortcut("o", modifiers: .command) Divider() Button("添加URL...") { bridge.showURLDialog = true } .keyboardShortcut("u", modifiers: .command) } } #endif } } /// 主内容视图 — 加载BricksJSON + 注册自定义widget struct ContentView: View { @ObservedObject var bridge: PlayerBridge var body: some View { Group { if let engine = bridge.engine, let schema = bridge.schema { BricksView(schema: schema, engine: engine) .overlay(alignment: .top) { if let error = bridge.toastMessage { Text(error) .padding(8) .background(.black.opacity(0.7)) .foregroundColor(.white) .cornerRadius(6) .padding(.top, 8) .transition(.opacity) } } .sheet(isPresented: $bridge.showURLDialog) { URLInputDialog(bridge: bridge) } #if os(macOS) .sheet(isPresented: $bridge.showTrackDialog) { TrackSelectDialog(bridge: bridge) } #endif } else { ProgressView("初始化...") } } } } /// URL输入弹窗 struct URLInputDialog: View { @ObservedObject var bridge: PlayerBridge @State private var url = "" @Environment(\.dismiss) private var dismiss var body: some View { VStack(spacing: 16) { Text("添加媒体URL").font(.headline) TextField("https://example.com/video.m3u8", text: $url) .textFieldStyle(.roundedBorder) HStack { Button("取消") { dismiss() } Spacer() Button("添加") { if !url.isEmpty { bridge.addURL(url) dismiss() } } .keyboardShortcut(.defaultAction) .disabled(url.isEmpty) } } .padding(20) .frame(width: 400) } } /// 音轨选择弹窗 struct TrackSelectDialog: View { @ObservedObject var bridge: PlayerBridge @Environment(\.dismiss) private var dismiss var body: some View { VStack(spacing: 12) { Text("选择音轨").font(.headline) ForEach(Array(bridge.availableTracks.enumerated()), id: \.offset) { idx, track in Button(action: { bridge.selectTrack(index: idx) dismiss() }) { HStack { Text("Track \(idx + 1)") Spacer() if idx == bridge.currentTrackIndex { Text("✓").foregroundColor(.green) } } .padding(8) .contentShape(Rectangle()) } .buttonStyle(.plain) } Divider() Button("关闭") { dismiss() } } .padding(20) .frame(minWidth: 250) } }