// // SettingsFlowViews.swift // suixinkan // // Created by Codex on 2026/6/22. // import SwiftUI import UIKit import WebKit /// 协议和说明页面枚举,描述设置中心可打开的 H5 页面。 enum AgreementPage: Hashable, Identifiable { case about case userAgreement case privacyPolicy case walletUserNotice case walletPrivacy var id: String { title } /// 页面导航标题。 var title: String { switch self { case .about: "关于我们" case .userAgreement: "用户协议" case .privacyPolicy: "隐私政策" case .walletUserNotice: "钱包用户须知" case .walletPrivacy: "钱包隐私政策" } } /// 页面对应的 H5 地址。 var url: URL { let path = switch self { case .about: "/h5/app/about-us" case .userAgreement: "/h5/app/user-agreement" case .privacyPolicy: "/h5/app/privacy-policy" case .walletUserNotice: "/h5/app/wallet-user-notice" case .walletPrivacy: "/h5/app/wallet-privacy" } return APIEnvironment.current.baseURL.appending(path: path) } } /// 设置展示策略,集中处理版本号等纯展示逻辑。 enum SettingsDisplayPolicy { /// 生成页面显示的版本号。 nonisolated static func versionText(infoDictionary: [String: Any]? = Bundle.main.infoDictionary) -> String { AppClientInfo.appVersion(infoDictionary: infoDictionary) } } /// 设置中心页面,展示关于我们、版本、下载链接和协议入口。 struct SettingsCenterView: View { @Environment(RouterPath.self) private var router @State private var copiedDownloadLink = false private var versionText: String { SettingsDisplayPolicy.versionText() } private var downloadLink: String { APIEnvironment.current.baseURL.appending(path: "/h5/app/download").absoluteString } var body: some View { ScrollView { VStack(spacing: 0) { settingsGroup { settingsButton(title: "关于我们") { router.navigate(to: .profile(.agreement(.about))) } settingsDivider settingsRow(title: "系统版本", value: versionText) settingsDivider Button { copyDownloadLink() } label: { settingsRow(title: "App下载", value: copiedDownloadLink ? "已复制" : "复制链接", valueColor: AppDesign.primary) } .buttonStyle(.plain) settingsDivider settingsButton(title: "用户协议") { router.navigate(to: .profile(.agreement(.userAgreement))) } settingsDivider settingsButton(title: "隐私政策") { router.navigate(to: .profile(.agreement(.privacyPolicy))) } } footer .padding(.top, 40) } .padding(.horizontal, 16) .padding(.top, 20) .padding(.bottom, 32) } .background(Color(hex: 0xF5F5F5).ignoresSafeArea()) .navigationTitle("设置") .navigationBarTitleDisplayMode(.inline) } /// 复制 App 下载链接到系统剪贴板。 private func copyDownloadLink() { UIPasteboard.general.string = downloadLink copiedDownloadLink = true Task { try? await Task.sleep(nanoseconds: 1_500_000_000) copiedDownloadLink = false } } /// 构造设置分组容器。 private func settingsGroup(@ViewBuilder content: () -> Content) -> some View { VStack(spacing: 0) { content() } .padding(.horizontal, 20) .padding(.vertical, 10) .background(.white, in: RoundedRectangle(cornerRadius: 12)) } /// 构造可点击的设置行。 private func settingsButton(title: String, action: @escaping () -> Void) -> some View { Button(action: action) { settingsRow(title: title, showsChevron: true) } .buttonStyle(.plain) } /// 构造设置行。 private func settingsRow( title: String, value: String? = nil, valueColor: Color = Color(hex: 0x333333), showsChevron: Bool = false ) -> some View { HStack(spacing: 12) { Text(title) .font(.system(size: 18)) .foregroundStyle(Color(hex: 0x4B5563)) Spacer() if let value, !value.isEmpty { Text(value) .font(.system(size: 18)) .foregroundStyle(valueColor) .lineLimit(1) } if showsChevron { Image(systemName: "chevron.right") .font(.system(size: 20, weight: .semibold)) .foregroundStyle(Color(hex: 0x999999)) } } .frame(height: 58) .contentShape(Rectangle()) } /// 设置行分割线。 private var settingsDivider: some View { Rectangle() .fill(Color(hex: 0xEEEEEE)) .frame(height: 0.5) } /// 设置页版权页脚。 private var footer: some View { VStack(spacing: 12) { Text("Copyright © 2025 All Rights Reserved") Text("苏ICP备2025157647号") } .font(.system(size: 16)) .foregroundStyle(Color(hex: 0xB6BECA)) .multilineTextAlignment(.center) } } /// 协议 H5 页面,使用 WKWebView 加载线上内容。 struct AgreementView: View { let page: AgreementPage @State private var isLoading = true var body: some View { ZStack { AgreementWebView(url: page.url, isLoading: $isLoading) if isLoading { ProgressView() .controlSize(.large) .frame(maxWidth: .infinity, maxHeight: .infinity) .background(Color.white.opacity(0.4)) } } .background(Color(hex: 0xF5F5F5).ignoresSafeArea()) .navigationTitle(page.title) .navigationBarTitleDisplayMode(.inline) } } /// WKWebView 的 SwiftUI 包装,用于加载协议和说明页面。 private struct AgreementWebView: UIViewRepresentable { let url: URL @Binding var isLoading: Bool /// 创建 WebView 协调器。 func makeCoordinator() -> Coordinator { Coordinator(isLoading: $isLoading) } /// 创建底层 WKWebView。 func makeUIView(context: Context) -> WKWebView { let configuration = WKWebViewConfiguration() configuration.defaultWebpagePreferences.allowsContentJavaScript = true let webView = WKWebView(frame: .zero, configuration: configuration) webView.navigationDelegate = context.coordinator return webView } /// 根据 URL 更新 WebView 请求。 func updateUIView(_ webView: WKWebView, context: Context) { guard webView.url != url else { return } isLoading = true webView.load(URLRequest(url: url)) } /// WebView 代理协调器,负责同步加载状态。 final class Coordinator: NSObject, WKNavigationDelegate { @Binding var isLoading: Bool /// 初始化协调器并绑定加载状态。 init(isLoading: Binding) { _isLoading = isLoading } /// 页面加载成功后关闭 loading。 func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { isLoading = false } /// 页面加载失败后关闭 loading。 func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { isLoading = false } } }