Files

185 lines
5.6 KiB
Swift

//
// AccountDeletionViewModels.swift
// suixinkan
//
import Foundation
/// 注销资产核验页 ViewModel,管理资产快照、确认状态与验证页上下文。
final class AccountDeletionViewModel {
private let service: any AccountDeletionServing
let username: String
private(set) var assets: [AccountDeletionAssetSummary] = []
private(set) var consequences: [String] = []
private(set) var isAcknowledged = false
private(set) var isLoading = false
private(set) var errorMessage: String?
var onStateChange: (() -> Void)?
/// 创建注销核验 ViewModel,默认读取当前登录账号并使用共享 Mock 服务。
init(
username: String? = AccountDeletionIdentity.currentUsername(),
service: any AccountDeletionServing = AccountDeletionMockService.shared
) {
self.username = AccountDeletionIdentity.normalizedUsername(username ?? "")
self.service = service
}
/// 勾选后才允许进入短信验证。
var isContinueEnabled: Bool {
!assets.isEmpty && isAcknowledged && !isLoading
}
/// 资产加载完成后主按钮保持可点击,未勾选时由业务校验给出明确提示。
var isContinueButtonEnabled: Bool {
!assets.isEmpty && !isLoading
}
/// 拉取注销前置核验结果。
func load() async {
guard !isLoading else { return }
isLoading = true
errorMessage = nil
notifyStateChange()
defer {
isLoading = false
notifyStateChange()
}
do {
let precheck = try await service.loadPrecheck(username: username)
assets = precheck.assets
consequences = precheck.consequences
} catch {
errorMessage = error.localizedDescription
}
}
/// 切换用户对注销影响的确认状态。
func toggleAcknowledgement() {
isAcknowledged.toggle()
notifyStateChange()
}
/// 生成短信验证页上下文。
func makeVerificationContext() throws -> AccountDeletionVerificationContext {
guard isContinueEnabled else {
throw AccountDeletionError.incompleteAcknowledgement
}
guard !username.isEmpty else {
throw AccountDeletionError.missingIdentity
}
return AccountDeletionVerificationContext(
username: username,
maskedPhone: AccountDeletionIdentity.maskedPhone(username),
assets: assets
)
}
/// 使用当前服务创建短信验证 ViewModel,保持同一套 Mock 状态。
func makeVerificationViewModel() throws -> AccountDeletionVerificationViewModel {
try AccountDeletionVerificationViewModel(
context: makeVerificationContext(),
service: service
)
}
private func notifyStateChange() {
onStateChange?()
}
}
/// 注销短信验证页 ViewModel,负责验证码发送、校验和提交申请。
final class AccountDeletionVerificationViewModel {
private let service: any AccountDeletionServing
private let clientRequestID = UUID()
let context: AccountDeletionVerificationContext
private(set) var verificationCode = ""
private(set) var isSendingCode = false
private(set) var isSubmitting = false
private(set) var hasSentCode = false
private(set) var errorMessage: String?
var onStateChange: (() -> Void)?
/// 创建短信验证 ViewModel。
init(
context: AccountDeletionVerificationContext,
service: any AccountDeletionServing = AccountDeletionMockService.shared
) throws {
guard !context.username.isEmpty else {
throw AccountDeletionError.missingIdentity
}
self.context = context
self.service = service
}
/// 验证码满足6位数字且当前未提交时允许继续。
var isSubmitEnabled: Bool {
verificationCode.count == 6
&& verificationCode.allSatisfy(\.isNumber)
&& !isSubmitting
}
/// 更新用户输入的验证码,只保留前6位数字。
func updateVerificationCode(_ value: String) {
verificationCode = String(value.filter(\.isNumber).prefix(6))
errorMessage = nil
notifyStateChange()
}
/// 发送 Mock 短信验证码。
func sendVerificationCode() async {
guard !isSendingCode else { return }
isSendingCode = true
errorMessage = nil
notifyStateChange()
defer {
isSendingCode = false
notifyStateChange()
}
do {
try await service.sendVerificationCode(to: context.username)
hasSentCode = true
} catch {
errorMessage = error.localizedDescription
}
}
/// 提交注销申请并返回计划删除时间。
func submit() async throws -> AccountDeletionRequest {
guard isSubmitEnabled else {
throw AccountDeletionError.invalidVerificationCode
}
guard !isSubmitting else { throw CancellationError() }
isSubmitting = true
errorMessage = nil
notifyStateChange()
defer {
isSubmitting = false
notifyStateChange()
}
do {
return try await service.submitDeletion(
username: context.username,
verificationCode: verificationCode,
acknowledgedAssetKinds: Set(context.assets.map(\.kind)),
clientRequestID: clientRequestID
)
} catch {
errorMessage = error.localizedDescription
throw error
}
}
private func notifyStateChange() {
onStateChange?()
}
}