import SwiftUI import SwiftData import AVKit extension Notification.Name { static let navigateToMediaViewer = Notification.Name("navigateToMediaViewer") } // MARK: - 主视图 struct BlindBoxView: View { @State private var showLottieAnimation = true // 控制Lottie动画显示 @State private var showScalingOverlay = false @State private var scale: CGFloat = 0.1 @State private var mediaToShow: MediaType? @State private var videoPlayer: AVPlayer? @State private var showControls = false // Add this line for controlling visibility private func startScalingAnimation() { // Start from 10% size self.scale = 0.1 self.showScalingOverlay = true // Slower animation with spring effect withAnimation(.spring(response: 2.0, dampingFraction: 0.5, blendDuration: 0.8)) { self.scale = 1.0 // Scale enough to cover the screen } } private func loadVideo() { // Replace with your actual video URL if let url = URL(string: "https://cdn.fairclip.cn/files/7342843896868769793/飞书20250617-144935.mp4") { let player = AVPlayer(url: url) player.isMuted = true // Mute the video self.videoPlayer = player } } var body: some View { ZStack { // 全局背景颜色背景色 Color.themeTextWhiteSecondary.ignoresSafeArea() // 主内容区域 VStack { VStack(spacing: 20) { // 标题 VStack(alignment: .leading, spacing: 4) { Text("Hi! Click And") Text("Open Your First Box~") } .font(Typography.font(for: .smallLargeTitle)) .fontWeight(.bold) .foregroundColor(Color.themeTextMessageMain) .frame(maxWidth: .infinity, alignment: .leading) .padding(.horizontal) .opacity(showScalingOverlay ? 0 : 1) .offset(y: showScalingOverlay ? -UIScreen.main.bounds.height * 0.2 : 0) .animation(.easeInOut(duration: 0.5), value: showScalingOverlay) // 盲盒 ZStack { // 1. 背景SVG if !showScalingOverlay { SVGImage(svgName: "BlindBg") .frame( width: UIScreen.main.bounds.width * 1.8, height: UIScreen.main.bounds.height * 0.85 ) .position(x: UIScreen.main.bounds.width / 2, y: UIScreen.main.bounds.height * 0.325) .opacity(showScalingOverlay ? 0 : 1) .animation(.easeOut(duration: 0.5), value: showScalingOverlay) } if !showScalingOverlay { LottieView(name: "data", loopMode: .loop) .frame(width: 200, height: 200) .position(x: UIScreen.main.bounds.width / 2, y: UIScreen.main.bounds.height * 0.325) .opacity(showScalingOverlay ? 0 : 1) .animation(.easeOut(duration: 0.5), value: showScalingOverlay) } } .frame( maxWidth: .infinity, maxHeight: UIScreen.main.bounds.height * 0.65 ) .opacity(showScalingOverlay ? 0 : 1) .animation(.easeOut(duration: 0.5), value: showScalingOverlay) .offset(y: showScalingOverlay ? -100 : 0) .animation(.easeInOut(duration: 0.5), value: showScalingOverlay) } .frame(maxWidth: .infinity, maxHeight: .infinity) .background(Color.themeTextWhiteSecondary) .edgesIgnoringSafeArea(.all) } // Scaling Overlay if showScalingOverlay { ZStack { // Video Player if let player = videoPlayer { ZStack(alignment: .topLeading) { VideoPlayer(player: player) .aspectRatio(contentMode: .fill) .frame(width: UIScreen.main.bounds.width * scale, height: UIScreen.main.bounds.height * scale) .clipped() .opacity(scale == 1 ? 1 : 0.7) .contentShape(Rectangle()) // Make entire area tappable .onTapGesture { withAnimation(.easeInOut(duration: 0.3)) { showControls.toggle() } } .onAppear { player.seek(to: .zero) player.play() NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: player.currentItem, queue: .main) { _ in player.seek(to: .zero) player.play() } } // Back Button - Always on top if showControls { Button(action: { if let media = mediaToShow { if let url = URL(string: "https://cdn.fairclip.cn/files/7348219809961742336/c5ca6151-91d3-483e-b7e7-c37f2cb69dc0.png") { URLSession.shared.dataTask(with: url) { data, response, error in if let data = data, let image = UIImage(data: data) { let media = MediaType.image(image) DispatchQueue.main.async { Router.shared.navigate(to: .blindOutcome(media: media)) } } }.resume() } } }) { Image(systemName: "chevron.left.circle.fill") .font(.system(size: 36)) .foregroundColor(.white) .padding(12) .clipShape(Circle()) } .padding(.top, 50) .padding(.leading, 20) .zIndex(1000) // Ensure it's on top .transition(.opacity) } } } else { // Fallback color in case video fails to load Color.red .frame(width: UIScreen.main.bounds.width * scale, height: UIScreen.main.bounds.height * scale) } } .frame(maxWidth: .infinity, maxHeight: .infinity) .animation(.easeInOut(duration: 3.0), value: scale) .ignoresSafeArea() .onTapGesture { withAnimation(.easeInOut(duration: 0.3)) { showControls.toggle() } } .onAppear { // Start animation after a small delay to ensure view is loaded DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { withAnimation(.spring(response: 2.5, dampingFraction: 0.6, blendDuration: 1.0)) { self.scale = 1.0 } } } } } .navigationBarBackButtonHidden(true) .onAppear { // Load video first self.loadVideo() // Then load image if let url = URL(string: "https://cdn.fairclip.cn/files/7348219809961742336/c5ca6151-91d3-483e-b7e7-c37f2cb69dc0.png") { URLSession.shared.dataTask(with: url) { data, response, error in if let data = data, let image = UIImage(data: data) { let media = MediaType.image(image) self.mediaToShow = media // Start scaling animation after 5 seconds DispatchQueue.main.asyncAfter(deadline: .now() + 5) { self.startScalingAnimation() } } }.resume() } } } } // MARK: - 预览 #Preview { BlindBoxView() }