Files
suixinkan_ios_new/suixinkan/Features/Profile/Views/SettingsFlowViews.swift
汉秋 26f4d0e671 Migrate from iOS 17 Observation to iOS 16-compatible Combine architecture.
Lower deployment target to iOS 16, replace @Observable with ObservableObject, add navigation and UI compatibility shims, and include login smoke UI tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-26 10:16:35 +08:00

247 lines
7.8 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// 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 {
@EnvironmentObject private var router: RouterPath
@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
}
}
}