punchnet-macos/punchnet/Views/Register/RegisterView.swift
2026-03-19 20:08:03 +08:00

336 lines
11 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.

//
// ResetPasswordView.swift
// punchnet
//
// Created by on 2026/3/9.
//
import SwiftUI
import Observation
// MARK: -
struct RegisterRootView: View {
@State private var registerModel = RegisterModel()
var body: some View {
ZStack {
//
VisualEffectView(material: .underWindowBackground, blendingMode: .behindWindow)
.ignoresSafeArea()
Group {
switch registerModel.stage {
case .requestVerifyCode:
RegisterRequestVerifyCodeView()
case .submitVerifyCode(let username):
RegisterSubmitVerifyCodeView(username: username)
case .setPassword(let username):
RegisterSetPasswordView(username: username)
}
}
.transition(.asymmetric(insertion: .move(edge: .trailing).combined(with: .opacity),
removal: .move(edge: .leading).combined(with: .opacity)))
}
.environment(registerModel)
.frame(width: 400, height: 450)
}
}
// MARK: -
struct PunchTextField: View {
let icon: String
let placeholder: String
@Binding var text: String
var isSecure: Bool = false
var isDisabled: Bool = false
var body: some View {
HStack(spacing: 12) {
Image(systemName: icon)
.foregroundColor(.secondary)
.frame(width: 20)
if isSecure {
SecureField(placeholder, text: $text)
.textFieldStyle(.plain)
} else {
TextField(placeholder, text: $text)
.textFieldStyle(.plain)
.disabled(isDisabled)
}
}
.padding(10)
.background(Color.primary.opacity(isDisabled ? 0.02 : 0.05))
.cornerRadius(8)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(Color.primary.opacity(0.1), lineWidth: 1)
)
}
}
// MARK: -
struct RegisterRequestVerifyCodeView: View {
@Environment(RegisterModel.self) var registerModel
@State private var username: String = ""
@State private var isProcessing = false
//
@State private var showAlert: Bool = false
@State private var errorMessage: String = ""
var body: some View {
VStack(spacing: 24) {
headerSection(title: "创建个人网络", subtitle: "输入手机号或邮箱开始注册")
VStack(spacing: 16) {
PunchTextField(icon: "person.crop.circle", placeholder: "手机号 / 邮箱", text: $username)
}
.frame(width: 280)
Button(action: {
self.requestVerifyCode()
}) {
if isProcessing {
ProgressView()
.controlSize(.small)
} else {
Text("获取验证码")
.fontWeight(.medium)
.frame(maxWidth: .infinity)
}
}
.buttonStyle(.borderedProminent)
.controlSize(.large)
.frame(width: 280)
.disabled(username.isEmpty || isProcessing)
Spacer()
}
.padding(40)
.alert(isPresented: $showAlert) {
Alert(title: Text("提示"), message: Text(self.errorMessage))
}
}
private func requestVerifyCode() {
self.isProcessing = true
Task { @MainActor in
if username.isEmpty {
self.showAlert = true
self.errorMessage = "手机号/邮箱为空"
self.isProcessing = false
return
}
switch SDLUtil.identifyContact(username) {
case .email, .phone:
do {
let result = try await self.registerModel.requestVerifyCode(username: username)
print("send verify code result: \(result)")
withAnimation(.spring()) {
self.registerModel.stage = .submitVerifyCode(username: username)
}
} catch {
self.showAlert = true
self.errorMessage = error.localizedDescription
}
case .invalid:
self.showAlert = true
self.errorMessage = "手机号/邮箱格式错误"
}
self.isProcessing = false
}
}
}
// MARK: -
struct RegisterSubmitVerifyCodeView: View {
@Environment(RegisterModel.self) var registerModel
let username: String
@State private var code: String = ""
@State private var isProcessing = false
//
@State private var showAlert: Bool = false
@State private var errorMessage: String = ""
var body: some View {
VStack(spacing: 24) {
headerSection(title: "身份验证", subtitle: "验证码已发送至 \(username)")
VStack(spacing: 16) {
PunchTextField(icon: "envelope.badge", placeholder: "输入 4 位验证码", text: $code)
Button("没有收到?重新获取") {
// Resend Logic
}
.buttonStyle(.link)
.font(.caption)
}
.frame(width: 280)
VStack(spacing: 12) {
Button(action: {
self.submitVerifyCode()
}) {
if isProcessing {
ProgressView()
.controlSize(.small)
} else {
Text("验证并设置密码")
.fontWeight(.medium)
.frame(maxWidth: .infinity)
}
}
.buttonStyle(.borderedProminent)
.controlSize(.large)
Button("返回上一步") {
withAnimation {
registerModel.stage = .requestVerifyCode
}
}
.buttonStyle(.plain)
.foregroundColor(.secondary)
}
.frame(width: 280)
Spacer()
}
.padding(40)
.alert(isPresented: $showAlert) {
Alert(title: Text("提示"), message: Text(errorMessage))
}
}
private func submitVerifyCode() {
self.isProcessing = true
Task { @MainActor in
if self.code.isEmpty {
self.showAlert = true
self.errorMessage = "请输入验证码"
self.isProcessing = false
return
}
if self.code.count != 4 {
self.showAlert = true
self.errorMessage = "验证码错误"
self.isProcessing = false
return
}
do {
let result = try await self.registerModel.submitVerifyCode(username: username, verifyCode: self.code)
print("submit verify code result: \(result)")
withAnimation(.spring()) {
registerModel.stage = .setPassword(username: username)
}
} catch {
self.showAlert = true
self.errorMessage = error.localizedDescription
}
self.isProcessing = false
}
}
}
// MARK: -
struct RegisterSetPasswordView: View {
@Environment(RegisterModel.self) var registerModel
let username: String
@State private var password = ""
@State private var confirm = ""
@State private var isProcessing = false
//
@State private var showAlert: Bool = false
@State private var errorMessage: String = ""
var body: some View {
VStack(spacing: 24) {
headerSection(title: "设置安全密码", subtitle: "最后一步,请确保密码足够强大")
VStack(spacing: 12) {
PunchTextField(icon: "lock.shield", placeholder: "新密码", text: $password, isSecure: true)
PunchTextField(icon: "lock.shield", placeholder: "确认密码", text: $confirm, isSecure: true)
}
.frame(width: 280)
Button(action: {
self.handleRegister()
}) {
if isProcessing {
ProgressView()
.controlSize(.small)
} else {
Text("完成注册")
.fontWeight(.medium)
.frame(maxWidth: .infinity)
}
}
.buttonStyle(.borderedProminent)
.controlSize(.large)
.frame(width: 280)
.disabled(password.isEmpty || password != confirm)
Spacer()
}
.padding(40)
.alert(isPresented: $showAlert) {
Alert(title: Text("提示"), message: Text(errorMessage))
}
}
private func handleRegister() {
self.isProcessing = true
Task { @MainActor in
if password.isEmpty {
self.showAlert = true
self.errorMessage = "请输入新密码"
self.isProcessing = false
return
}
if confirm.isEmpty || confirm != password {
self.showAlert = true
self.errorMessage = "两次输入的密码不一致"
self.isProcessing = false
return
}
do {
let result = try await self.registerModel.register(username: username, password: self.password)
print("send verify code result: \(result)")
} catch {
self.showAlert = true
self.errorMessage = error.localizedDescription
}
self.isProcessing = false
}
}
}
// MARK: -
extension View {
func headerSection(title: String, subtitle: String) -> some View {
VStack(spacing: 8) {
Image(systemName: "shield.lefthalf.filled")
.font(.system(size: 42))
.foregroundStyle(.blue.gradient)
Text(title)
.font(.title2.bold())
Text(subtitle)
.font(.subheadline)
.foregroundColor(.secondary)
.multilineTextAlignment(.center)
}
}
}