added ios module

This commit is contained in:
2026-08-17 20:13:28 +08:00
parent a8031e2e3b
commit 7c81976b2b
27 changed files with 2767 additions and 0 deletions

View File

@@ -0,0 +1,194 @@
import SwiftUI
/// + + /
struct PlayerSectionView: View {
@ObservedObject var controller: PlayerController
var showsContinuousToggle: Bool
@Binding var continuousPlayback: Bool
var onFullscreen: () -> Void
@State private var progressDragging = false
@State private var dragPositionMs: Int64 = 0
var body: some View {
VStack(spacing: 0) {
playerSurface
sentencePanel
}
.padding(.horizontal, 16)
}
private var playerSurface: some View {
ZStack {
PlayerSurfaceView(controller: controller)
.aspectRatio(16.0 / 9.0, contentMode: .fit)
.frame(maxWidth: .infinity)
VStack {
HStack(spacing: 8) {
overlayPill(timeText)
Spacer()
Button(action: cycleSpeed) {
overlayPill(Theme.formatSpeed(controller.snapshot.speed))
}
.buttonStyle(.plain)
}
Spacer()
}
.padding(12)
VStack {
Spacer()
HStack {
Spacer()
Button(action: onFullscreen) {
Image(systemName: "arrow.up.left.and.arrow.down.right")
.font(.system(size: 18, weight: .bold))
.foregroundColor(.white)
.frame(width: 44, height: 44)
.background(Theme.overlay)
.clipShape(Circle())
}
.buttonStyle(.plain)
}
}
.padding(12)
}
.background(Color.black)
.clipShape(RoundedRectangle(cornerRadius: 8))
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(Theme.border, lineWidth: 1)
)
.padding(.bottom, 10)
}
private var sentencePanel: some View {
VStack(alignment: .leading, spacing: 6) {
Text(controller.currentItem?.title ?? "未选择课程")
.font(.system(size: 17, weight: .bold))
.foregroundColor(.white)
.lineLimit(1)
if showsContinuousToggle {
HStack(spacing: 0) {
Text("连续播放")
.font(.system(size: 13, weight: .bold))
.foregroundColor(Theme.textSubtle)
Spacer()
Toggle("", isOn: $continuousPlayback)
.labelsHidden()
.tint(Theme.accent)
}
.padding(.vertical, 2)
}
Text(sentenceMetaText)
.font(.system(size: 13))
.foregroundColor(Theme.textSubtle)
Text(sentenceTextText)
.font(.system(size: 15))
.foregroundColor(.white)
.lineLimit(2)
progressRow
Text(statusText)
.font(.system(size: 13))
.foregroundColor(Theme.textSubtle)
}
.padding(.bottom, 10)
}
private var progressRow: some View {
let duration = controller.snapshot.durationMs
return HStack(spacing: 8) {
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.accent)
}
}
private var progressBinding: Binding<Double> {
Binding(
get: {
if progressDragging {
return Double(dragPositionMs)
}
return Double(controller.snapshot.positionMs)
},
set: { newValue in
dragPositionMs = Int64(newValue)
}
)
}
private var timeText: String {
let snapshot = controller.snapshot
return "\(Theme.formatTime(snapshot.positionMs)) / \(Theme.formatTime(snapshot.durationMs))"
}
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):
if isLocal {
return "正在匹配服务端句子边界"
}
return 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)
}
private func overlayPill(_ text: String) -> some View {
Text(text)
.font(.system(size: 13, weight: .bold))
.foregroundColor(.white)
.padding(.horizontal, 10)
.padding(.vertical, 6)
.background(Theme.overlay)
.clipShape(RoundedRectangle(cornerRadius: 8))
}
private func cycleSpeed() {
let current = controller.snapshot.speed
let cycle: [Float] = [1.0, 1.25, 1.5, 1.75, 2.0]
let next = cycle.first { $0 > current + 0.001 } ?? cycle.first ?? 1.0
controller.setPlaybackSpeed(next)
}
}