punchnet-macos/punchnet/Views/ResetPassword/ResetPasswordView.swift
2026-03-20 00:56:16 +08:00

278 lines
9.0 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: - 1.
struct ResetPasswordRootView: View {
@State private var resetPasswordModel = ResetPasswordModel()
var body: some View {
ZStack {
// (macOS )
VisualEffectView(material: .underWindowBackground, blendingMode: .behindWindow)
.ignoresSafeArea()
Group {
switch resetPasswordModel.stage {
case .requestVerifyCode(let username):
GetVerifyCodeView(username: username ?? "")
case .submitVerifyCode(let username):
SubmitVerifyCodeView(username: username)
case .resetPassword(let username):
ResetPasswordView(username: username)
}
}
.transition(.asymmetric(
insertion: .move(edge: resetPasswordModel.transitionEdge).combined(with: .opacity),
removal: .move(edge: resetPasswordModel.transitionEdge == .trailing ? .leading : .trailing).combined(with: .opacity)
))
}
.environment(resetPasswordModel)
}
}
// MARK: - 2.
struct GetVerifyCodeView: View {
@Environment(ResetPasswordModel.self) var resetPasswordModel
@State var username: String = ""
@State private var isProcessing = false
@State private var showAlert = false
@State private var errorMessage = ""
var body: some View {
VStack(spacing: 24) {
headerSection(title: "重置密码", subtitle: "请输入关联的手机号或邮箱来验证身份")
PunchTextField(icon: "person.crop.circle", placeholder: "手机号 / 邮箱", text: $username)
.frame(width: 280)
Button {
self.sendVerifyCode()
} label: {
Text("获取验证码")
.fontWeight(.medium)
.frame(maxWidth: .infinity)
}
.buttonStyle(.borderedProminent)
.controlSize(.large)
.frame(width: 280)
.disabled(!SDLUtil.isValidIdentifyContact(username) || isProcessing)
Spacer()
}
.padding(40)
.alert(isPresented: $showAlert) {
Alert(title: Text("提示"), message: Text(errorMessage))
}
}
//
private func sendVerifyCode() {
self.isProcessing = true
Task { @MainActor in
do {
// _ = try await resetPasswordModel.requestVerifyCode(username: username)
withAnimation(.spring(duration: 0.6, bounce: 0.2)) {
resetPasswordModel.stage = .submitVerifyCode(username: username)
resetPasswordModel.transitionEdge = .trailing
}
} catch {
self.errorMessage = error.localizedDescription
self.showAlert = true
}
self.isProcessing = false
}
}
}
// MARK: - 3.
struct SubmitVerifyCodeView: View {
@Environment(ResetPasswordModel.self) var resetPasswordModel
let username: String
@State private var code: String = ""
@State private var isProcessing = false
//
@State private var remainingSeconds = 60
@State private var isResendEnabled = false
//
@State private var showAlert = false
@State private var errorMessage = ""
var validInputCode: Bool {
return !self.code.isEmpty && self.code.count == 4 && self.code.allSatisfy {$0.isNumber}
}
var body: some View {
VStack(spacing: 24) {
headerSection(title: "身份验证", subtitle: "验证码已发送至 \(username)")
VStack(alignment: .trailing, spacing: 16) {
PunchTextField(icon: "envelope.badge", placeholder: "输入 4 位验证码", text: $code)
Button(isResendEnabled ? "重新获取" : "重新获取 (\(remainingSeconds)s)") {
self.resendAction()
}
.buttonStyle(.link)
.font(.caption)
.disabled(!isResendEnabled)
}
.frame(width: 280)
VStack(spacing: 12) {
Button {
self.submitAction()
} label: {
Text("验证并继续")
.fontWeight(.medium)
.frame(maxWidth: .infinity)
}
.buttonStyle(.borderedProminent)
.controlSize(.large)
.disabled(!validInputCode)
Button("返回上一步") {
withAnimation(.spring(duration: 0.6, bounce: 0.2)) {
self.resetPasswordModel.stage = .requestVerifyCode(username: self.username)
self.resetPasswordModel.transitionEdge = .leading
}
}
.buttonStyle(.plain)
.foregroundColor(.secondary)
}
.frame(width: 280)
Spacer()
}
.padding(40)
.task {
await startCountdown()
}
.alert(isPresented: $showAlert) {
Alert(title: Text("提示"), message: Text(self.errorMessage))
}
}
private func resendAction() {
Task {
_ = try? await resetPasswordModel.requestVerifyCode(username: username)
await startCountdown()
}
}
private func startCountdown() async {
self.isResendEnabled = false
self.remainingSeconds = 60
while remainingSeconds > 0 {
try? await Task.sleep(nanoseconds: 1_000_000_000)
self.remainingSeconds -= 1
}
self.isResendEnabled = true
}
private func submitAction() {
self.isProcessing = true
Task { @MainActor in
do {
// _ = try await resetPasswordModel.submitVerifyCode(username: username, verifyCode: code)
withAnimation(.spring(duration: 0.6, bounce: 0.2)) {
self.resetPasswordModel.stage = .resetPassword(username: username)
self.resetPasswordModel.transitionEdge = .trailing
}
} catch {
self.errorMessage = error.localizedDescription
self.showAlert = true
}
self.isProcessing = false
}
}
}
// MARK: - 4.
struct ResetPasswordView: View {
@Environment(ResetPasswordModel.self) var resetPasswordModel
let username: String
@State private var password = ""
@State private var confirm = ""
@State private var isProcessing = false
//
var isInputValid: Bool {
!password.isEmpty && password == confirm && password.count >= 8
}
//
var passwordError: String? {
if password.isEmpty || confirm.isEmpty {
return nil
}
if password != confirm {
return "两次输入的密码不一致"
}
if password.count < 8 {
return "密码至少需要 8 位"
}
return nil
}
var body: some View {
VStack(spacing: 24) {
headerSection(title: "设置新密码", subtitle: "请为账号 \(username) 设置一个强密码")
VStack(spacing: 12) {
PunchTextField(icon: "lock.shield", placeholder: "新密码 (至少8位)", text: $password, isSecure: true)
PunchTextField(icon: "lock.shield", placeholder: "确认新密码", text: $confirm, isSecure: true)
if let passwordError = self.passwordError {
Text(passwordError)
.font(.caption)
.foregroundColor(.red)
.frame(width: 280, alignment: .leading)
}
}
.frame(width: 280)
Button {
self.handleReset()
} label: {
Text("重置密码并登录")
.fontWeight(.medium)
.frame(maxWidth: .infinity)
}
.buttonStyle(.borderedProminent)
.controlSize(.large)
.frame(width: 280)
.disabled(!isInputValid || isProcessing)
Spacer()
}
.padding(40)
}
private func handleReset() {
self.isProcessing = true
Task { @MainActor in
do {
//_ = try await resetPasswordModel.resetPassword(username: username, password: password)
print("密码重置成功")
//
} catch {
print("重置失败: \(error)")
}
self.isProcessing = false
}
}
}