146 lines
5.0 KiB
Swift
146 lines
5.0 KiB
Swift
//
|
||
// ScenicPromotionView.swift
|
||
// suixinkan
|
||
//
|
||
// Created by Codex on 2026/7/2.
|
||
//
|
||
|
||
import SwiftUI
|
||
|
||
/// 宣传页主视图,负责页面状态切换与隐藏设置入口。
|
||
struct ScenicPromotionView: View {
|
||
@StateObject private var viewModel = ScenicPromotionViewModel()
|
||
@State private var settingsRoute: ScenicPromotionSettingsRoute?
|
||
@State private var qrTapCount = 0
|
||
@State private var qrTapResetTask: Task<Void, Never>?
|
||
@State private var showPasswordPrompt = false
|
||
@State private var settingsPassword = ""
|
||
@State private var showPasswordError = false
|
||
@State private var showDemoStateMenu = false
|
||
@State private var showPlayToast = false
|
||
|
||
var body: some View {
|
||
ZStack {
|
||
ScenicPromotionSkyBackground()
|
||
.ignoresSafeArea()
|
||
|
||
Group {
|
||
switch viewModel.pageState {
|
||
case .idle, .loading:
|
||
ScenicPromotionLoadingView()
|
||
.transition(.opacity.combined(with: .scale(scale: 0.98)))
|
||
case .empty:
|
||
ScenicPromotionFullStateView(
|
||
icon: "doc.text.image",
|
||
title: "暂无宣传页数据",
|
||
message: "当前景区还没有配置排队导览宣传内容",
|
||
buttonTitle: "重新加载"
|
||
) {
|
||
Task { await viewModel.load() }
|
||
}
|
||
.transition(.opacity.combined(with: .move(edge: .bottom)))
|
||
case .error(let message):
|
||
ScenicPromotionFullStateView(
|
||
icon: "wifi.exclamationmark",
|
||
title: "加载失败",
|
||
message: message,
|
||
buttonTitle: "重试"
|
||
) {
|
||
Task { await viewModel.load() }
|
||
}
|
||
.transition(.opacity.combined(with: .move(edge: .bottom)))
|
||
case .normal:
|
||
ScenicPromotionContentView(
|
||
viewModel: viewModel,
|
||
onQRCodeTap: handleQRCodeTap,
|
||
onQRCodeLongPress: { showDemoStateMenu = true },
|
||
onVideoTap: { showPlayToast = true }
|
||
)
|
||
.transition(.opacity.combined(with: .move(edge: .bottom)))
|
||
}
|
||
}
|
||
.animation(.easeInOut(duration: 0.28), value: viewModel.pageState)
|
||
}
|
||
.ignoresSafeArea(.container, edges: .top)
|
||
.toolbar(.hidden, for: .navigationBar)
|
||
.appNavigationDestination(item: $settingsRoute) { _ in
|
||
ScenicPromotionSettingsView()
|
||
}
|
||
.alert("输入管理密码", isPresented: $showPasswordPrompt) {
|
||
SecureField("请输入密码", text: $settingsPassword)
|
||
Button("取消", role: .cancel) {
|
||
settingsPassword = ""
|
||
}
|
||
Button("进入") {
|
||
verifySettingsPassword()
|
||
}
|
||
} message: {
|
||
Text("连续点击二维码 3 次后可进入宣传页设置")
|
||
}
|
||
.alert("密码错误", isPresented: $showPasswordError) {
|
||
Button("知道了", role: .cancel) {}
|
||
} message: {
|
||
Text("请输入正确密码")
|
||
}
|
||
.alert("视频播放", isPresented: $showPlayToast) {
|
||
Button("知道了", role: .cancel) {}
|
||
} message: {
|
||
Text("Demo 已模拟播放入口,后续接入真实视频地址即可播放。")
|
||
}
|
||
.confirmationDialog("演示状态", isPresented: $showDemoStateMenu, titleVisibility: .visible) {
|
||
ForEach(ScenicPromotionDemoScenario.allCases) { scenario in
|
||
Button(scenario.title) {
|
||
viewModel.applyScenario(scenario)
|
||
}
|
||
}
|
||
Button("取消", role: .cancel) {}
|
||
}
|
||
.onDisappear {
|
||
qrTapResetTask?.cancel()
|
||
}
|
||
.task {
|
||
await viewModel.loadIfNeeded()
|
||
}
|
||
}
|
||
|
||
/// 二维码快速点击 3 次后弹出设置密码输入框。
|
||
private func handleQRCodeTap() {
|
||
qrTapCount += 1
|
||
qrTapResetTask?.cancel()
|
||
|
||
if qrTapCount >= 3 {
|
||
qrTapCount = 0
|
||
settingsPassword = ""
|
||
showPasswordPrompt = true
|
||
return
|
||
}
|
||
|
||
qrTapResetTask = Task {
|
||
try? await Task.sleep(nanoseconds: 1_100_000_000)
|
||
guard !Task.isCancelled else { return }
|
||
await MainActor.run {
|
||
qrTapCount = 0
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 当前 Mock 密码为 1,通过后进入宣传页设置页。
|
||
private func verifySettingsPassword() {
|
||
if settingsPassword == "1" {
|
||
settingsPassword = ""
|
||
settingsRoute = ScenicPromotionSettingsRoute()
|
||
} else {
|
||
settingsPassword = ""
|
||
showPasswordError = true
|
||
}
|
||
}
|
||
}
|
||
|
||
#if DEBUG
|
||
#Preview("宣传页") {
|
||
NavigationStack {
|
||
ScenicPromotionView()
|
||
}
|
||
}
|
||
#endif
|