Files
suixinkan_uikit/suixinkan/UI/TravelAlbum/CreateTravelAlbumSheetViewController.swift

297 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.

//
// CreateTravelAlbumSheetViewController.swift
// suixinkan
//
import SnapKit
import UIKit
/// Sheet Android `CreateTravelAlbumModal`
final class CreateTravelAlbumSheetViewController: BaseViewController {
var onDismissed: (() -> Void)?
private let viewModel: TravelAlbumEntryViewModel
private let api: any TravelAlbumServing
private let mode: TravelAlbumEntryViewModel.CreateMode = .preShoot
private let scrollView = UIScrollView()
private let contentView = UIView()
private let titleLabel = UILabel()
private let preShootCard = TravelAlbumModeOptionView()
private let fieldsStack = UIStackView()
private let freeCountField = UITextField()
private let singlePriceField = UITextField()
private let packagePriceField = UITextField()
private let cancelButton = UIButton(type: .system)
private let confirmButton = UIButton(type: .system)
init(viewModel: TravelAlbumEntryViewModel, api: any TravelAlbumServing) {
self.viewModel = viewModel
self.api = api
super.init(nibName: nil, bundle: nil)
modalPresentationStyle = .pageSheet
if let sheetPresentationController {
sheetPresentationController.detents = [.medium(), .large()]
sheetPresentationController.prefersGrabberVisible = false
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func setupNavigationBar() {}
override func setupUI() {
view.backgroundColor = .white
titleLabel.text = "新建相册任务"
titleLabel.font = .systemFont(ofSize: 18, weight: .semibold)
titleLabel.textColor = AppColor.textPrimary
titleLabel.textAlignment = .center
preShootCard.apply(
title: "先拍再买",
desc: "拍完后分享给用户,用户在小程序上选择性购买",
selected: true
)
fieldsStack.axis = .vertical
fieldsStack.spacing = 14
configureTextField(freeCountField, placeholder: "请输入免费张数", keyboardType: .numberPad)
configureTextField(singlePriceField, placeholder: "请输入单张照片价格", keyboardType: .decimalPad)
configureTextField(packagePriceField, placeholder: "请输入打包价格", keyboardType: .decimalPad)
configureActionButton(cancelButton, title: "取消", backgroundColor: UIColor(hex: 0xF4F4F4), titleColor: AppColor.textSecondary)
configureActionButton(confirmButton, title: "确定", backgroundColor: AppColor.primary, titleColor: .white)
view.addSubview(scrollView)
scrollView.addSubview(contentView)
[titleLabel, preShootCard, fieldsStack, cancelButton, confirmButton].forEach(contentView.addSubview)
rebuildFields()
}
override func setupConstraints() {
scrollView.snp.makeConstraints { make in
make.edges.equalTo(view.safeAreaLayoutGuide)
}
contentView.snp.makeConstraints { make in
make.edges.equalToSuperview()
make.width.equalToSuperview()
}
titleLabel.snp.makeConstraints { make in
make.top.equalToSuperview().offset(18)
make.leading.trailing.equalToSuperview().inset(20)
}
preShootCard.snp.makeConstraints { make in
make.top.equalTo(titleLabel.snp.bottom).offset(18)
make.leading.trailing.equalToSuperview().inset(16)
}
fieldsStack.snp.makeConstraints { make in
make.top.equalTo(preShootCard.snp.bottom).offset(18)
make.leading.trailing.equalTo(preShootCard)
}
cancelButton.snp.makeConstraints { make in
make.top.equalTo(fieldsStack.snp.bottom).offset(22)
make.leading.equalToSuperview().offset(16)
make.height.equalTo(48)
make.width.equalTo(confirmButton)
make.bottom.equalToSuperview().offset(-16)
}
confirmButton.snp.makeConstraints { make in
make.top.height.width.equalTo(cancelButton)
make.leading.equalTo(cancelButton.snp.trailing).offset(12)
make.trailing.equalToSuperview().offset(-16)
}
}
override func bindActions() {
cancelButton.addTarget(self, action: #selector(cancelTapped), for: .touchUpInside)
confirmButton.addTarget(self, action: #selector(confirmTapped), for: .touchUpInside)
[freeCountField, singlePriceField, packagePriceField].forEach {
$0.addTarget(self, action: #selector(textFieldEditingChanged(_:)), for: .editingChanged)
}
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
if isBeingDismissed {
onDismissed?()
}
}
private func configureTextField(_ field: UITextField, placeholder: String, keyboardType: UIKeyboardType) {
field.placeholder = placeholder
field.keyboardType = keyboardType
field.font = .systemFont(ofSize: 14)
field.layer.cornerRadius = 8
field.layer.borderWidth = 1
field.layer.borderColor = AppColor.border.cgColor
field.leftView = UIView(frame: CGRect(x: 0, y: 0, width: 12, height: 1))
field.leftViewMode = .always
field.snp.makeConstraints { make in
make.height.equalTo(44)
}
}
private func configureActionButton(_ button: UIButton, title: String, backgroundColor: UIColor, titleColor: UIColor) {
button.setTitle(title, for: .normal)
button.setTitleColor(titleColor, for: .normal)
button.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
button.backgroundColor = backgroundColor
button.layer.cornerRadius = 10
}
private func rebuildFields() {
fieldsStack.arrangedSubviews.forEach { $0.removeFromSuperview() }
fieldsStack.addArrangedSubview(makeFieldGroup(title: "免费张数", required: false, field: freeCountField))
fieldsStack.addArrangedSubview(makeFieldGroup(title: "单张照片价格(元)", required: true, field: singlePriceField))
fieldsStack.addArrangedSubview(makeFieldGroup(title: "打包价格(元)", required: false, field: packagePriceField))
}
private func makeFieldGroup(title: String, required: Bool, field: UITextField) -> UIView {
let container = UIView()
let label = UILabel()
label.attributedText = fieldTitle(title, required: required)
container.addSubview(label)
container.addSubview(field)
label.snp.makeConstraints { make in
make.top.leading.trailing.equalToSuperview()
}
field.snp.makeConstraints { make in
make.top.equalTo(label.snp.bottom).offset(8)
make.leading.trailing.bottom.equalToSuperview()
}
return container
}
private func fieldTitle(_ text: String, required: Bool) -> NSAttributedString {
let result = NSMutableAttributedString(
string: text,
attributes: [.font: UIFont.systemFont(ofSize: 14, weight: .medium), .foregroundColor: AppColor.textPrimary]
)
if required {
result.append(NSAttributedString(string: " *", attributes: [.foregroundColor: UIColor.red]))
}
return result
}
@objc private func cancelTapped() {
dismiss(animated: true)
}
@objc private func confirmTapped() {
Task {
await viewModel.confirmCreate(
mode: mode,
freeCount: freeCountField.text ?? "",
singlePrice: singlePriceField.text ?? "",
packagePrice: packagePriceField.text ?? "",
order: nil,
api: api
)
await MainActor.run {
if !viewModel.isCreateSheetVisible {
dismiss(animated: true)
}
}
}
}
@objc private func textFieldEditingChanged(_ field: UITextField) {
let text = field.text ?? ""
if field === freeCountField {
field.text = text.filter(\.isNumber)
} else {
field.text = sanitizeMoneyInput(text)
}
}
private func sanitizeMoneyInput(_ value: String) -> String {
var result = ""
var hasDot = false
for char in value {
if char.isNumber {
result.append(char)
} else if char == ".", !hasDot {
result.append(char)
hasDot = true
}
}
guard !result.isEmpty else { return "" }
if result.hasPrefix(".") {
result = "0" + result
}
guard let dotIndex = result.firstIndex(of: ".") else {
let trimmed = result.drop { $0 == "0" }
return trimmed.isEmpty ? "0" : String(trimmed)
}
let integerPart = result[..<dotIndex].drop { $0 == "0" }
let decimalStart = result.index(after: dotIndex)
let decimal = result[decimalStart...].prefix(2)
return "\(integerPart.isEmpty ? "0" : String(integerPart)).\(decimal)"
}
}
///
private final class TravelAlbumModeOptionView: UIControl {
private let dotView = UIView()
private let innerDotView = UIView()
private let titleLabel = UILabel()
private let descLabel = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
layer.cornerRadius = 10
layer.borderWidth = 1
dotView.layer.cornerRadius = 10
innerDotView.backgroundColor = .white
innerDotView.layer.cornerRadius = 3.5
titleLabel.font = .systemFont(ofSize: 15, weight: .semibold)
descLabel.font = .systemFont(ofSize: 12)
descLabel.textColor = AppColor.textSecondary
descLabel.numberOfLines = 0
addSubview(dotView)
dotView.addSubview(innerDotView)
addSubview(titleLabel)
addSubview(descLabel)
dotView.snp.makeConstraints { make in
make.leading.top.equalToSuperview().offset(14)
make.size.equalTo(20)
}
innerDotView.snp.makeConstraints { make in
make.center.equalToSuperview()
make.size.equalTo(7)
}
titleLabel.snp.makeConstraints { make in
make.top.equalToSuperview().offset(14)
make.leading.equalTo(dotView.snp.trailing).offset(10)
make.trailing.equalToSuperview().offset(-14)
}
descLabel.snp.makeConstraints { make in
make.top.equalTo(titleLabel.snp.bottom).offset(4)
make.leading.trailing.equalTo(titleLabel)
make.bottom.equalToSuperview().offset(-14)
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func apply(title: String, desc: String, selected: Bool) {
titleLabel.text = title
descLabel.text = desc
backgroundColor = selected ? AppColor.primary.withAlphaComponent(0.06) : .white
layer.borderColor = (selected ? AppColor.primary : AppColor.border).cgColor
titleLabel.textColor = selected ? AppColor.primary : AppColor.textPrimary
dotView.backgroundColor = selected ? AppColor.primary : .clear
dotView.layer.borderWidth = selected ? 0 : 2
dotView.layer.borderColor = AppColor.border.cgColor
innerDotView.isHidden = !selected
}
}