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 } }