import SwiftUI /// 与安卓端横屏布局一致:全屏播放 + 半透明句面板 + 退出全屏按钮。 struct FullscreenPlayerView: View { @ObservedObject var controller: PlayerController let onExit: () -> Void var body: some View { ZStack { Color.black.ignoresSafeArea() PlayerSurfaceView(controller: controller) .ignoresSafeArea() VStack { Spacer() VStack(alignment: .leading, spacing: 6) { Text(controller.currentItem?.title ?? "未选择课程") .font(.system(size: 17, weight: .bold)) .foregroundColor(.white) .lineLimit(1) Text(sentenceMetaText) .font(.system(size: 13)) .foregroundColor(Theme.textOverlay) Text(sentenceTextText) .font(.system(size: 15)) .foregroundColor(.white) .lineLimit(2) progressRow Text(statusText) .font(.system(size: 13)) .foregroundColor(Theme.textOverlay) } .padding(18) .frame(maxWidth: .infinity, alignment: .leading) .background(Theme.overlayFaint) } VStack { Spacer() HStack { Spacer() Button(action: onExit) { Image(systemName: "arrow.down.right.and.arrow.up.left") .font(.system(size: 18, weight: .bold)) .foregroundColor(.white) .frame(width: 44, height: 44) .background(Theme.overlay) .clipShape(Circle()) } .buttonStyle(.plain) } } .padding(16) } } @State private var progressDragging = false @State private var dragPositionMs: Int64 = 0 private var progressRow: some View { let duration = controller.snapshot.durationMs return HStack(spacing: 8) { Text(Theme.formatTime(controller.snapshot.positionMs)) .font(.system(size: 13)) .foregroundColor(Theme.textOverlay) Slider( value: progressBinding, in: 0...max(1, Double(duration)), onEditingChanged: { editing in if editing { progressDragging = true } else { progressDragging = false controller.seek(to: dragPositionMs) } } ) .tint(Theme.accentLight) Text(Theme.formatTime(controller.snapshot.durationMs)) .font(.system(size: 13)) .foregroundColor(Theme.textOverlay) } } private var progressBinding: Binding { Binding( get: { if progressDragging { return Double(dragPositionMs) } return Double(controller.snapshot.positionMs) }, set: { newValue in dragPositionMs = Int64(newValue) } ) } private var sentenceMetaText: String { switch controller.boundaryState { case .loading(let isLocal): return isLocal ? "句子边界分析中" : "句子边界加载中" case .failed: return "暂无句子边界" case .idle, .loaded: guard let sentence = controller.currentSentence else { return "暂无句子边界" } let count = controller.currentItem?.sentences.count ?? 0 let countText = count > 0 ? " / \(count)" : "" return "第 \(sentence.index + 1)\(countText) 句 \(Theme.formatTime(sentence.startMs))-\(Theme.formatTime(sentence.endMs))" } } private var sentenceTextText: String { switch controller.boundaryState { case .loading(let isLocal): return isLocal ? "正在匹配服务端句子边界" : (controller.currentItem?.title ?? "加载中") case .failed: return controller.currentItem?.title ?? "未选择课程" case .idle, .loaded: guard let sentence = controller.currentSentence else { return controller.currentItem?.title ?? "未选择课程" } if let text = sentence.text, !text.isEmpty { return text } return "当前句子" } } private var statusText: String { if let message = controller.transientMessage { return message } return controller.playbackStatusText(controller.snapshot) } }