同步已迁移的 iOS 模块
This commit is contained in:
246
suixinkan/Features/Profile/Views/SettingsFlowViews.swift
Normal file
246
suixinkan/Features/Profile/Views/SettingsFlowViews.swift
Normal file
@ -0,0 +1,246 @@
|
||||
//
|
||||
// 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<Content: View>(@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<Bool>) {
|
||||
_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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user