新增线下收款记录和 ai 修图优化
This commit is contained in:
@@ -0,0 +1,761 @@
|
||||
//
|
||||
// AccountDeletionViewControllers.swift
|
||||
// suixinkan
|
||||
//
|
||||
|
||||
import SnapKit
|
||||
import UIKit
|
||||
|
||||
/// 注销账号资产核验页,中性展示账号资产与注销后果。
|
||||
final class AccountDeletionViewController: BaseViewController {
|
||||
private let viewModel: AccountDeletionViewModel
|
||||
|
||||
private let scrollView = UIScrollView()
|
||||
private let contentView = UIView()
|
||||
private let contentStack = UIStackView()
|
||||
private let warningView = AccountDeletionWarningView()
|
||||
private let assetCardView = AccountDeletionAssetCardView()
|
||||
private let detailButton = UIButton(type: .system)
|
||||
private let consequenceCardView = AccountDeletionConsequenceCardView()
|
||||
private let bottomPanel = UIView()
|
||||
private let acknowledgementButton = UIButton(type: .system)
|
||||
private let continueButton = AppButton(title: "继续注销", style: .primary)
|
||||
private var lastErrorMessage: String?
|
||||
|
||||
/// 创建注销资产核验页。
|
||||
init(viewModel: AccountDeletionViewModel = AccountDeletionViewModel()) {
|
||||
self.viewModel = viewModel
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func setupNavigationBar() {
|
||||
title = "注销账号"
|
||||
}
|
||||
|
||||
override func setupUI() {
|
||||
view.backgroundColor = AppColor.pageBackground
|
||||
scrollView.alwaysBounceVertical = true
|
||||
scrollView.showsVerticalScrollIndicator = false
|
||||
|
||||
contentStack.axis = .vertical
|
||||
contentStack.spacing = 16
|
||||
contentStack.isLayoutMarginsRelativeArrangement = true
|
||||
contentStack.layoutMargins = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16)
|
||||
|
||||
detailButton.setTitle("查看资产明细", for: .normal)
|
||||
detailButton.setTitleColor(AppColor.primary, for: .normal)
|
||||
detailButton.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
|
||||
|
||||
bottomPanel.backgroundColor = AppColor.pageBackground
|
||||
|
||||
acknowledgementButton.contentHorizontalAlignment = .leading
|
||||
acknowledgementButton.tintColor = AppColor.primary
|
||||
acknowledgementButton.setTitleColor(AppColor.textPrimary, for: .normal)
|
||||
acknowledgementButton.titleLabel?.font = .systemFont(ofSize: 14)
|
||||
acknowledgementButton.titleLabel?.numberOfLines = 0
|
||||
acknowledgementButton.setTitle(" 我已知晓并自愿放弃以上资产和权益", for: .normal)
|
||||
acknowledgementButton.accessibilityLabel = "我已知晓并自愿放弃以上资产和权益"
|
||||
|
||||
view.addSubview(scrollView)
|
||||
scrollView.addSubview(contentView)
|
||||
contentView.addSubview(contentStack)
|
||||
view.addSubview(bottomPanel)
|
||||
bottomPanel.addSubview(acknowledgementButton)
|
||||
bottomPanel.addSubview(continueButton)
|
||||
|
||||
[warningView, assetCardView, detailButton, consequenceCardView].forEach(contentStack.addArrangedSubview)
|
||||
|
||||
applyViewModel()
|
||||
}
|
||||
|
||||
override func setupConstraints() {
|
||||
bottomPanel.snp.makeConstraints { make in
|
||||
make.leading.trailing.bottom.equalToSuperview()
|
||||
}
|
||||
|
||||
acknowledgementButton.snp.makeConstraints { make in
|
||||
make.top.equalToSuperview().offset(8)
|
||||
make.leading.trailing.equalToSuperview().inset(16)
|
||||
make.height.greaterThanOrEqualTo(44)
|
||||
}
|
||||
|
||||
continueButton.snp.makeConstraints { make in
|
||||
make.top.equalTo(acknowledgementButton.snp.bottom).offset(8)
|
||||
make.leading.trailing.equalToSuperview().inset(16)
|
||||
make.bottom.equalTo(view.safeAreaLayoutGuide).offset(-12)
|
||||
}
|
||||
|
||||
scrollView.snp.makeConstraints { make in
|
||||
make.top.leading.trailing.equalTo(view.safeAreaLayoutGuide)
|
||||
make.bottom.equalTo(bottomPanel.snp.top)
|
||||
}
|
||||
|
||||
contentView.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview()
|
||||
make.width.equalTo(scrollView.snp.width)
|
||||
}
|
||||
|
||||
contentStack.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview()
|
||||
}
|
||||
|
||||
warningView.snp.makeConstraints { make in
|
||||
make.height.greaterThanOrEqualTo(64)
|
||||
}
|
||||
detailButton.snp.makeConstraints { make in
|
||||
make.height.equalTo(36)
|
||||
}
|
||||
}
|
||||
|
||||
override func bindActions() {
|
||||
viewModel.onStateChange = { [weak self] in
|
||||
Task { @MainActor in self?.applyViewModel() }
|
||||
}
|
||||
acknowledgementButton.addTarget(self, action: #selector(acknowledgementTapped), for: .touchUpInside)
|
||||
continueButton.addTarget(self, action: #selector(continueTapped), for: .touchUpInside)
|
||||
detailButton.addTarget(self, action: #selector(detailTapped), for: .touchUpInside)
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
Task {
|
||||
showLoading()
|
||||
await viewModel.load()
|
||||
hideLoading()
|
||||
}
|
||||
}
|
||||
|
||||
private func applyViewModel() {
|
||||
assetCardView.configure(assets: viewModel.assets)
|
||||
consequenceCardView.configure(items: viewModel.consequences)
|
||||
acknowledgementButton.setImage(
|
||||
UIImage(systemName: viewModel.isAcknowledged ? "checkmark.square.fill" : "square"),
|
||||
for: .normal
|
||||
)
|
||||
acknowledgementButton.accessibilityValue = viewModel.isAcknowledged ? "已勾选" : "未勾选"
|
||||
continueButton.isEnabled = viewModel.isContinueButtonEnabled
|
||||
|
||||
if let errorMessage = viewModel.errorMessage,
|
||||
!errorMessage.isEmpty,
|
||||
errorMessage != lastErrorMessage {
|
||||
lastErrorMessage = errorMessage
|
||||
showToast(errorMessage)
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func acknowledgementTapped() {
|
||||
viewModel.toggleAcknowledgement()
|
||||
}
|
||||
|
||||
@objc private func continueTapped() {
|
||||
do {
|
||||
let verificationViewModel = try viewModel.makeVerificationViewModel()
|
||||
navigationController?.pushViewController(
|
||||
AccountDeletionVerificationViewController(viewModel: verificationViewModel),
|
||||
animated: true
|
||||
)
|
||||
} catch {
|
||||
showToast(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func detailTapped() {
|
||||
let message = viewModel.assets
|
||||
.map { "\($0.title):\($0.valueText)" }
|
||||
.joined(separator: "\n")
|
||||
showAlert(title: "当前账号资产", message: message)
|
||||
}
|
||||
}
|
||||
|
||||
/// 注销手机号验证页,使用短信验证码确认是账号本人操作。
|
||||
final class AccountDeletionVerificationViewController: BaseViewController {
|
||||
private let viewModel: AccountDeletionVerificationViewModel
|
||||
|
||||
private let contentStack = UIStackView()
|
||||
private let iconView = UIImageView()
|
||||
private let titleLabel = UILabel()
|
||||
private let descriptionLabel = UILabel()
|
||||
private let codeContainer = UIView()
|
||||
private let codeField = UITextField()
|
||||
private let resendButton = UIButton(type: .system)
|
||||
private let demoHintLabel = UILabel()
|
||||
private let noticeView = AccountDeletionNoticeView()
|
||||
private let submitButton = AppButton(title: "提交注销申请", style: .primary)
|
||||
private var lastErrorMessage: String?
|
||||
|
||||
/// 创建注销手机号验证页。
|
||||
init(viewModel: AccountDeletionVerificationViewModel) {
|
||||
self.viewModel = viewModel
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func setupNavigationBar() {
|
||||
title = "验证手机号"
|
||||
}
|
||||
|
||||
override func setupUI() {
|
||||
view.backgroundColor = AppColor.pageBackground
|
||||
|
||||
contentStack.axis = .vertical
|
||||
contentStack.alignment = .fill
|
||||
contentStack.spacing = 16
|
||||
|
||||
iconView.image = UIImage(systemName: "message.badge.filled.fill")
|
||||
iconView.tintColor = AppColor.primary
|
||||
iconView.contentMode = .scaleAspectFit
|
||||
|
||||
titleLabel.text = "确认是你本人操作"
|
||||
titleLabel.font = .systemFont(ofSize: 22, weight: .semibold)
|
||||
titleLabel.textColor = AppColor.textPrimary
|
||||
titleLabel.textAlignment = .center
|
||||
|
||||
descriptionLabel.text = "验证码将发送至 \(viewModel.context.maskedPhone)"
|
||||
descriptionLabel.font = .systemFont(ofSize: 15)
|
||||
descriptionLabel.textColor = AppColor.textSecondary
|
||||
descriptionLabel.textAlignment = .center
|
||||
|
||||
codeContainer.backgroundColor = .white
|
||||
codeContainer.layer.cornerRadius = 12
|
||||
codeContainer.layer.borderWidth = 1
|
||||
codeContainer.layer.borderColor = AppColor.border.cgColor
|
||||
|
||||
codeField.placeholder = "请输入6位验证码"
|
||||
codeField.font = .systemFont(ofSize: 18, weight: .medium)
|
||||
codeField.textColor = AppColor.textPrimary
|
||||
codeField.keyboardType = .numberPad
|
||||
codeField.textContentType = .oneTimeCode
|
||||
codeField.clearButtonMode = .whileEditing
|
||||
|
||||
resendButton.setTitle("重新发送", for: .normal)
|
||||
resendButton.setTitleColor(AppColor.primary, for: .normal)
|
||||
resendButton.titleLabel?.font = .systemFont(ofSize: 14, weight: .medium)
|
||||
|
||||
demoHintLabel.text = "演示验证码:123456"
|
||||
demoHintLabel.font = .systemFont(ofSize: 13)
|
||||
demoHintLabel.textColor = AppColor.warning
|
||||
demoHintLabel.textAlignment = .center
|
||||
|
||||
noticeView.configure(
|
||||
iconName: "calendar.badge.exclamationmark",
|
||||
text: "提交成功后账号将退出,7天内再次登录可取消注销。"
|
||||
)
|
||||
|
||||
view.addSubview(contentStack)
|
||||
view.addSubview(submitButton)
|
||||
codeContainer.addSubview(codeField)
|
||||
codeContainer.addSubview(resendButton)
|
||||
|
||||
[iconView, titleLabel, descriptionLabel, codeContainer, demoHintLabel, noticeView]
|
||||
.forEach(contentStack.addArrangedSubview)
|
||||
|
||||
applyViewModel()
|
||||
}
|
||||
|
||||
override func setupConstraints() {
|
||||
contentStack.snp.makeConstraints { make in
|
||||
make.top.equalTo(view.safeAreaLayoutGuide).offset(32)
|
||||
make.leading.trailing.equalToSuperview().inset(24)
|
||||
}
|
||||
iconView.snp.makeConstraints { make in
|
||||
make.height.equalTo(64)
|
||||
}
|
||||
codeContainer.snp.makeConstraints { make in
|
||||
make.height.equalTo(56)
|
||||
}
|
||||
codeField.snp.makeConstraints { make in
|
||||
make.leading.equalToSuperview().offset(16)
|
||||
make.top.bottom.equalToSuperview()
|
||||
make.trailing.equalTo(resendButton.snp.leading).offset(-8)
|
||||
}
|
||||
resendButton.snp.makeConstraints { make in
|
||||
make.trailing.equalToSuperview().offset(-12)
|
||||
make.centerY.equalToSuperview()
|
||||
make.width.equalTo(76)
|
||||
make.height.equalTo(44)
|
||||
}
|
||||
noticeView.snp.makeConstraints { make in
|
||||
make.height.greaterThanOrEqualTo(64)
|
||||
}
|
||||
submitButton.snp.makeConstraints { make in
|
||||
make.leading.trailing.equalToSuperview().inset(16)
|
||||
make.bottom.equalTo(view.safeAreaLayoutGuide).offset(-12)
|
||||
}
|
||||
}
|
||||
|
||||
override func bindActions() {
|
||||
viewModel.onStateChange = { [weak self] in
|
||||
Task { @MainActor in self?.applyViewModel() }
|
||||
}
|
||||
codeField.addTarget(self, action: #selector(codeChanged), for: .editingChanged)
|
||||
resendButton.addTarget(self, action: #selector(resendTapped), for: .touchUpInside)
|
||||
submitButton.addTarget(self, action: #selector(submitTapped), for: .touchUpInside)
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
Task {
|
||||
await viewModel.sendVerificationCode()
|
||||
if viewModel.hasSentCode {
|
||||
showToast("验证码已发送")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func applyViewModel() {
|
||||
if codeField.text != viewModel.verificationCode {
|
||||
codeField.text = viewModel.verificationCode
|
||||
}
|
||||
resendButton.isEnabled = !viewModel.isSendingCode && !viewModel.isSubmitting
|
||||
resendButton.setTitle(viewModel.isSendingCode ? "发送中" : "重新发送", for: .normal)
|
||||
submitButton.isEnabled = viewModel.isSubmitEnabled
|
||||
|
||||
if let errorMessage = viewModel.errorMessage,
|
||||
!errorMessage.isEmpty,
|
||||
errorMessage != lastErrorMessage {
|
||||
lastErrorMessage = errorMessage
|
||||
showToast(errorMessage)
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func codeChanged() {
|
||||
viewModel.updateVerificationCode(codeField.text ?? "")
|
||||
}
|
||||
|
||||
@objc private func resendTapped() {
|
||||
Task {
|
||||
await viewModel.sendVerificationCode()
|
||||
if viewModel.hasSentCode {
|
||||
showToast("验证码已重新发送")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func submitTapped() {
|
||||
let alert = UIAlertController(
|
||||
title: "确认提交注销申请?",
|
||||
message: "提交后账号将退出,7天内再次登录可以取消注销。",
|
||||
preferredStyle: .alert
|
||||
)
|
||||
alert.addAction(UIAlertAction(title: "再想想", style: .cancel))
|
||||
alert.addAction(UIAlertAction(title: "确认提交", style: .destructive) { [weak self] _ in
|
||||
self?.submitDeletion()
|
||||
})
|
||||
present(alert, animated: true)
|
||||
}
|
||||
|
||||
private func submitDeletion() {
|
||||
Task {
|
||||
showLoading()
|
||||
defer { hideLoading() }
|
||||
do {
|
||||
let request = try await viewModel.submit()
|
||||
navigationController?.pushViewController(
|
||||
AccountDeletionSuccessViewController(request: request),
|
||||
animated: true
|
||||
)
|
||||
} catch is CancellationError {
|
||||
return
|
||||
} catch {
|
||||
showToast(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 注销申请提交成功页,明确展示计划删除时间和恢复方式。
|
||||
final class AccountDeletionSuccessViewController: BaseViewController {
|
||||
private let request: AccountDeletionRequest
|
||||
|
||||
private let iconView = UIImageView()
|
||||
private let titleLabel = UILabel()
|
||||
private let descriptionLabel = UILabel()
|
||||
private let dateCardView = UIView()
|
||||
private let dateTitleLabel = UILabel()
|
||||
private let dateValueLabel = UILabel()
|
||||
private let recoveryLabel = UILabel()
|
||||
private let exitButton = AppButton(title: "退出并返回登录页", style: .primary)
|
||||
|
||||
/// 创建注销申请提交成功页。
|
||||
init(request: AccountDeletionRequest) {
|
||||
self.request = request
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func setupNavigationBar() {
|
||||
title = "注销账号"
|
||||
navigationItem.hidesBackButton = true
|
||||
}
|
||||
|
||||
override func setupUI() {
|
||||
view.backgroundColor = AppColor.pageBackground
|
||||
|
||||
iconView.image = UIImage(systemName: "checkmark.circle.fill")
|
||||
iconView.tintColor = AppColor.success
|
||||
iconView.contentMode = .scaleAspectFit
|
||||
|
||||
titleLabel.text = "注销申请已提交"
|
||||
titleLabel.font = .systemFont(ofSize: 24, weight: .semibold)
|
||||
titleLabel.textColor = AppColor.textPrimary
|
||||
titleLabel.textAlignment = .center
|
||||
|
||||
descriptionLabel.text = "账号已进入7天注销冷静期"
|
||||
descriptionLabel.font = .systemFont(ofSize: 15)
|
||||
descriptionLabel.textColor = AppColor.textSecondary
|
||||
descriptionLabel.textAlignment = .center
|
||||
|
||||
dateCardView.backgroundColor = .white
|
||||
dateCardView.layer.cornerRadius = 12
|
||||
|
||||
dateTitleLabel.text = "计划永久注销时间"
|
||||
dateTitleLabel.font = .systemFont(ofSize: 14)
|
||||
dateTitleLabel.textColor = AppColor.textSecondary
|
||||
|
||||
dateValueLabel.text = AccountDeletionDateFormatter.displayText(request.scheduledDeletionAt)
|
||||
dateValueLabel.font = .systemFont(ofSize: 20, weight: .semibold)
|
||||
dateValueLabel.textColor = AppColor.danger
|
||||
dateValueLabel.textAlignment = .right
|
||||
|
||||
recoveryLabel.text = "在上述时间前再次使用当前手机号和密码登录,确认恢复账号后即可取消注销。"
|
||||
recoveryLabel.font = .systemFont(ofSize: 15)
|
||||
recoveryLabel.textColor = AppColor.textSecondary
|
||||
recoveryLabel.numberOfLines = 0
|
||||
recoveryLabel.textAlignment = .center
|
||||
|
||||
[iconView, titleLabel, descriptionLabel, dateCardView, recoveryLabel, exitButton].forEach(view.addSubview)
|
||||
dateCardView.addSubview(dateTitleLabel)
|
||||
dateCardView.addSubview(dateValueLabel)
|
||||
}
|
||||
|
||||
override func setupConstraints() {
|
||||
iconView.snp.makeConstraints { make in
|
||||
make.top.equalTo(view.safeAreaLayoutGuide).offset(56)
|
||||
make.centerX.equalToSuperview()
|
||||
make.width.height.equalTo(72)
|
||||
}
|
||||
titleLabel.snp.makeConstraints { make in
|
||||
make.top.equalTo(iconView.snp.bottom).offset(24)
|
||||
make.leading.trailing.equalToSuperview().inset(24)
|
||||
}
|
||||
descriptionLabel.snp.makeConstraints { make in
|
||||
make.top.equalTo(titleLabel.snp.bottom).offset(10)
|
||||
make.leading.trailing.equalToSuperview().inset(24)
|
||||
}
|
||||
dateCardView.snp.makeConstraints { make in
|
||||
make.top.equalTo(descriptionLabel.snp.bottom).offset(32)
|
||||
make.leading.trailing.equalToSuperview().inset(16)
|
||||
make.height.equalTo(72)
|
||||
}
|
||||
dateTitleLabel.snp.makeConstraints { make in
|
||||
make.leading.equalToSuperview().offset(16)
|
||||
make.centerY.equalToSuperview()
|
||||
}
|
||||
dateValueLabel.snp.makeConstraints { make in
|
||||
make.trailing.equalToSuperview().offset(-16)
|
||||
make.centerY.equalToSuperview()
|
||||
make.leading.greaterThanOrEqualTo(dateTitleLabel.snp.trailing).offset(12)
|
||||
}
|
||||
recoveryLabel.snp.makeConstraints { make in
|
||||
make.top.equalTo(dateCardView.snp.bottom).offset(24)
|
||||
make.leading.trailing.equalToSuperview().inset(32)
|
||||
}
|
||||
exitButton.snp.makeConstraints { make in
|
||||
make.leading.trailing.equalToSuperview().inset(16)
|
||||
make.bottom.equalTo(view.safeAreaLayoutGuide).offset(-12)
|
||||
}
|
||||
}
|
||||
|
||||
override func bindActions() {
|
||||
exitButton.addTarget(self, action: #selector(exitTapped), for: .touchUpInside)
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
navigationController?.interactivePopGestureRecognizer?.isEnabled = false
|
||||
}
|
||||
|
||||
@objc private func exitTapped() {
|
||||
NotificationCenter.default.post(name: NotificationName.userDidLogout, object: nil)
|
||||
}
|
||||
}
|
||||
|
||||
/// 注销页顶部风险提示条。
|
||||
private final class AccountDeletionWarningView: UIView {
|
||||
private let iconView = UIImageView()
|
||||
private let label = UILabel()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
backgroundColor = AppColor.dangerBackground
|
||||
layer.cornerRadius = 12
|
||||
layer.borderWidth = 1
|
||||
layer.borderColor = AppColor.danger.withAlphaComponent(0.25).cgColor
|
||||
|
||||
iconView.image = UIImage(systemName: "exclamationmark.shield.fill")
|
||||
iconView.tintColor = AppColor.danger
|
||||
iconView.contentMode = .scaleAspectFit
|
||||
|
||||
label.text = "注销后,账号及相关数据将永久删除"
|
||||
label.font = .systemFont(ofSize: 15, weight: .medium)
|
||||
label.textColor = AppColor.danger
|
||||
label.numberOfLines = 0
|
||||
|
||||
addSubview(iconView)
|
||||
addSubview(label)
|
||||
iconView.snp.makeConstraints { make in
|
||||
make.leading.equalToSuperview().offset(16)
|
||||
make.centerY.equalToSuperview()
|
||||
make.width.height.equalTo(28)
|
||||
}
|
||||
label.snp.makeConstraints { make in
|
||||
make.leading.equalTo(iconView.snp.trailing).offset(12)
|
||||
make.trailing.equalToSuperview().offset(-16)
|
||||
make.top.bottom.equalToSuperview().inset(14)
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
|
||||
/// 注销资产汇总卡片,以列表形式中性展示名称和数量。
|
||||
private final class AccountDeletionAssetCardView: UIView {
|
||||
private let stackView = UIStackView()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
backgroundColor = .white
|
||||
layer.cornerRadius = 12
|
||||
clipsToBounds = true
|
||||
stackView.axis = .vertical
|
||||
addSubview(stackView)
|
||||
stackView.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview().inset(UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16))
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
/// 使用最新资产快照刷新卡片。
|
||||
func configure(assets: [AccountDeletionAssetSummary]) {
|
||||
stackView.arrangedSubviews.forEach {
|
||||
stackView.removeArrangedSubview($0)
|
||||
$0.removeFromSuperview()
|
||||
}
|
||||
assets.enumerated().forEach { index, asset in
|
||||
stackView.addArrangedSubview(
|
||||
AccountDeletionAssetRowView(asset: asset, showsDivider: index < assets.count - 1)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 注销资产列表行,仅显示客观资产名称和当前值。
|
||||
private final class AccountDeletionAssetRowView: UIView {
|
||||
private let titleLabel = UILabel()
|
||||
private let valueLabel = UILabel()
|
||||
private let divider = UIView()
|
||||
|
||||
init(asset: AccountDeletionAssetSummary, showsDivider: Bool) {
|
||||
super.init(frame: .zero)
|
||||
titleLabel.text = asset.title
|
||||
titleLabel.font = .systemFont(ofSize: 17, weight: .medium)
|
||||
titleLabel.textColor = AppColor.textPrimary
|
||||
|
||||
valueLabel.text = asset.valueText
|
||||
valueLabel.font = .systemFont(ofSize: 18, weight: .semibold)
|
||||
valueLabel.textColor = AppColor.textPrimary
|
||||
valueLabel.textAlignment = .right
|
||||
|
||||
divider.backgroundColor = AppColor.border
|
||||
divider.isHidden = !showsDivider
|
||||
|
||||
[titleLabel, valueLabel, divider].forEach(addSubview)
|
||||
snp.makeConstraints { make in
|
||||
make.height.equalTo(76)
|
||||
}
|
||||
titleLabel.snp.makeConstraints { make in
|
||||
make.leading.centerY.equalToSuperview()
|
||||
}
|
||||
valueLabel.snp.makeConstraints { make in
|
||||
make.trailing.centerY.equalToSuperview()
|
||||
make.leading.greaterThanOrEqualTo(titleLabel.snp.trailing).offset(12)
|
||||
}
|
||||
divider.snp.makeConstraints { make in
|
||||
make.leading.trailing.bottom.equalToSuperview()
|
||||
make.height.equalTo(0.5)
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
|
||||
/// 注销影响说明卡片,展示账号解除、内容删除和7天恢复规则。
|
||||
private final class AccountDeletionConsequenceCardView: UIView {
|
||||
private let titleLabel = UILabel()
|
||||
private let stackView = UIStackView()
|
||||
private let iconNames = [
|
||||
"person.crop.circle.badge.xmark",
|
||||
"folder",
|
||||
"calendar.badge.exclamationmark",
|
||||
]
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
backgroundColor = .white
|
||||
layer.cornerRadius = 12
|
||||
clipsToBounds = true
|
||||
|
||||
titleLabel.text = "注销后将发生"
|
||||
titleLabel.font = .systemFont(ofSize: 18, weight: .semibold)
|
||||
titleLabel.textColor = AppColor.textPrimary
|
||||
|
||||
stackView.axis = .vertical
|
||||
addSubview(titleLabel)
|
||||
addSubview(stackView)
|
||||
titleLabel.snp.makeConstraints { make in
|
||||
make.top.leading.trailing.equalToSuperview().inset(16)
|
||||
}
|
||||
stackView.snp.makeConstraints { make in
|
||||
make.top.equalTo(titleLabel.snp.bottom).offset(8)
|
||||
make.leading.trailing.bottom.equalToSuperview().inset(UIEdgeInsets(top: 0, left: 16, bottom: 8, right: 16))
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
/// 使用最新注销影响文案刷新卡片。
|
||||
func configure(items: [String]) {
|
||||
stackView.arrangedSubviews.forEach {
|
||||
stackView.removeArrangedSubview($0)
|
||||
$0.removeFromSuperview()
|
||||
}
|
||||
items.enumerated().forEach { index, text in
|
||||
stackView.addArrangedSubview(
|
||||
AccountDeletionConsequenceRowView(
|
||||
iconName: iconNames.indices.contains(index) ? iconNames[index] : "info.circle",
|
||||
text: text,
|
||||
showsDivider: index < items.count - 1
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 注销影响说明列表行。
|
||||
private final class AccountDeletionConsequenceRowView: UIView {
|
||||
private let iconBackgroundView = UIView()
|
||||
private let iconView = UIImageView()
|
||||
private let label = UILabel()
|
||||
private let divider = UIView()
|
||||
|
||||
init(iconName: String, text: String, showsDivider: Bool) {
|
||||
super.init(frame: .zero)
|
||||
iconBackgroundView.backgroundColor = AppColor.primaryLight
|
||||
iconBackgroundView.layer.cornerRadius = 18
|
||||
iconView.image = UIImage(systemName: iconName)
|
||||
iconView.tintColor = AppColor.primary
|
||||
iconView.contentMode = .scaleAspectFit
|
||||
label.text = text
|
||||
label.font = .systemFont(ofSize: 14)
|
||||
label.textColor = AppColor.textPrimary
|
||||
label.numberOfLines = 0
|
||||
divider.backgroundColor = AppColor.border
|
||||
divider.isHidden = !showsDivider
|
||||
|
||||
addSubview(iconBackgroundView)
|
||||
iconBackgroundView.addSubview(iconView)
|
||||
addSubview(label)
|
||||
addSubview(divider)
|
||||
snp.makeConstraints { make in
|
||||
make.height.greaterThanOrEqualTo(58)
|
||||
}
|
||||
iconBackgroundView.snp.makeConstraints { make in
|
||||
make.leading.centerY.equalToSuperview()
|
||||
make.width.height.equalTo(36)
|
||||
}
|
||||
iconView.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview().inset(8)
|
||||
}
|
||||
label.snp.makeConstraints { make in
|
||||
make.leading.equalTo(iconBackgroundView.snp.trailing).offset(12)
|
||||
make.trailing.equalToSuperview()
|
||||
make.centerY.equalToSuperview()
|
||||
}
|
||||
divider.snp.makeConstraints { make in
|
||||
make.leading.equalTo(label)
|
||||
make.trailing.bottom.equalToSuperview()
|
||||
make.height.equalTo(0.5)
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
|
||||
/// 注销流程通用说明条。
|
||||
private final class AccountDeletionNoticeView: UIView {
|
||||
private let iconView = UIImageView()
|
||||
private let label = UILabel()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
backgroundColor = AppColor.infoBackground
|
||||
layer.cornerRadius = 12
|
||||
addSubview(iconView)
|
||||
addSubview(label)
|
||||
iconView.tintColor = AppColor.primary
|
||||
iconView.contentMode = .scaleAspectFit
|
||||
label.font = .systemFont(ofSize: 14)
|
||||
label.textColor = AppColor.textSecondary
|
||||
label.numberOfLines = 0
|
||||
iconView.snp.makeConstraints { make in
|
||||
make.leading.equalToSuperview().offset(16)
|
||||
make.centerY.equalToSuperview()
|
||||
make.width.height.equalTo(24)
|
||||
}
|
||||
label.snp.makeConstraints { make in
|
||||
make.leading.equalTo(iconView.snp.trailing).offset(12)
|
||||
make.trailing.equalToSuperview().offset(-16)
|
||||
make.top.bottom.equalToSuperview().inset(14)
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
/// 配置说明条图标与文案。
|
||||
func configure(iconName: String, text: String) {
|
||||
iconView.image = UIImage(systemName: iconName)
|
||||
label.text = text
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user