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,143 @@
import SwiftUI
import UIKit
import UniformTypeIdentifiers
struct RootView: View {
@StateObject private var controller = PlayerController()
@StateObject private var catalogVM = CatalogViewModel()
@State private var activeModule: Module = .train
@State private var continuousPlayback = false
@State private var showImporter = false
@State private var showFullscreenPlayer = false
enum Module {
case train
case test
}
var body: some View {
ZStack {
Theme.background.ignoresSafeArea()
VStack(spacing: 0) {
header
moduleTabs
if activeModule == .train {
TrainingView(
controller: controller,
catalogVM: catalogVM,
continuousPlayback: $continuousPlayback,
onFullscreen: enterFullscreen
)
} else {
TestView(controller: controller, onFullscreen: enterFullscreen)
}
}
}
.preferredColorScheme(.dark)
.onAppear {
catalogVM.controller = controller
controller.setContinuousPlayback(continuousPlayback)
}
.onChange(of: activeModule) { module in
if module == .train {
controller.setContinuousPlayback(continuousPlayback)
} else {
controller.setContinuousPlayback(true)
}
}
.fileImporter(
isPresented: $showImporter,
allowedContentTypes: [.movie],
allowsMultipleSelection: false
) { result in
if case .success(let urls) = result, let url = urls.first {
Task {
await catalogVM.importVideo(at: url)
}
}
}
.fullScreenCover(isPresented: $showFullscreenPlayer) {
FullscreenPlayerView(controller: controller) {
exitFullscreen()
}
.onAppear(perform: enterFullscreen)
}
}
private var header: some View {
HStack(spacing: 0) {
VStack(alignment: .leading, spacing: 2) {
Text("跟读虫")
.font(.system(size: 32, weight: .bold))
.foregroundColor(.white)
Text("外语跟读训练神器")
.font(.system(size: 15))
.foregroundColor(Theme.textMuted)
}
Spacer()
headerButton("导入") {
showImporter = true
}
headerButton("刷新") {
Task {
await catalogVM.load()
}
}
}
.padding(.horizontal, 18)
.padding(.top, 8)
.padding(.bottom, 10)
}
private func headerButton(_ label: String, action: @escaping () -> Void) -> some View {
Button(action: action) {
Text(label)
.font(.system(size: 14, weight: .bold))
.foregroundColor(.white)
.padding(.horizontal, 14)
.frame(height: 40)
.background(Theme.buttonBlue)
.clipShape(RoundedRectangle(cornerRadius: 8))
}
.buttonStyle(.plain)
.padding(.leading, 8)
}
private var moduleTabs: some View {
HStack(spacing: 0) {
moduleTab("训练", module: .train)
moduleTab("测试", module: .test)
}
.padding(.horizontal, 16)
.padding(.bottom, 8)
}
private func moduleTab(_ label: String, module: Module) -> some View {
let selected = activeModule == module
return Button {
activeModule = module
} label: {
Text(label)
.font(.system(size: 15, weight: .bold))
.foregroundColor(selected ? .white : Theme.textMuted)
.frame(maxWidth: .infinity)
.frame(height: 38)
.background(selected ? Theme.buttonBlue : Theme.surface)
.clipShape(RoundedRectangle(cornerRadius: 8))
}
.buttonStyle(.plain)
.padding(2)
}
private func enterFullscreen() {
AppDelegate.orientationLock = UIDevice.current.userInterfaceIdiom == .pad ? .all : .landscape
UIViewController.attemptRotationToDeviceOrientation()
showFullscreenPlayer = true
}
private func exitFullscreen() {
AppDelegate.orientationLock = UIDevice.current.userInterfaceIdiom == .pad ? .all : .portrait
UIViewController.attemptRotationToDeviceOrientation()
showFullscreenPlayer = false
}
}