同步已迁移的 iOS 模块

This commit is contained in:
2026-06-22 15:35:12 +08:00
parent 0a0d4fbd79
commit 08465fffde
48 changed files with 4455 additions and 15 deletions

View 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
}
}
}