Add submit-task flow aligned with Android and extract profile edit page.
Implement task creation with cloud/local media, order linking, and OSS upload; wire home entry and move profile nickname/avatar editing to ProfileEditViewController. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
627
suixinkan/UI/Task/Views/TaskAddViews.swift
Normal file
627
suixinkan/UI/Task/Views/TaskAddViews.swift
Normal file
@ -0,0 +1,627 @@
|
||||
//
|
||||
// TaskAddViews.swift
|
||||
// suixinkan
|
||||
//
|
||||
|
||||
import Kingfisher
|
||||
import SnapKit
|
||||
import UIKit
|
||||
|
||||
/// 添加任务素材网格区。
|
||||
final class TaskAddMediaSectionView: UIView {
|
||||
|
||||
var onCloudImport: (() -> Void)?
|
||||
var onLocalImport: (() -> Void)?
|
||||
var onFileTap: ((TaskFileItem) -> Void)?
|
||||
var onDeleteFile: ((TaskFileItem) -> Void)?
|
||||
|
||||
private var files: [TaskFileItem] = []
|
||||
private var collectionHeightConstraint: Constraint?
|
||||
|
||||
private enum Section { case main }
|
||||
private enum Item: Hashable { case add; case file(String) }
|
||||
|
||||
private lazy var collectionView: UICollectionView = {
|
||||
let layout = UICollectionViewCompositionalLayout { _, environment in
|
||||
let columns = 3
|
||||
let spacing: CGFloat = 8
|
||||
let inset = AppSpacing.md * 2
|
||||
let availableWidth = environment.container.effectiveContentSize.width - inset
|
||||
let totalSpacing = spacing * CGFloat(columns - 1)
|
||||
let itemWidth = max(0, (availableWidth - totalSpacing) / CGFloat(columns))
|
||||
let itemHeight = itemWidth / 1.77
|
||||
let itemSize = NSCollectionLayoutSize(
|
||||
widthDimension: .absolute(itemWidth),
|
||||
heightDimension: .absolute(itemHeight)
|
||||
)
|
||||
let item = NSCollectionLayoutItem(layoutSize: itemSize)
|
||||
let groupSize = NSCollectionLayoutSize(
|
||||
widthDimension: .fractionalWidth(1),
|
||||
heightDimension: .absolute(itemHeight)
|
||||
)
|
||||
let group = NSCollectionLayoutGroup.horizontal(
|
||||
layoutSize: groupSize,
|
||||
repeatingSubitem: item,
|
||||
count: columns
|
||||
)
|
||||
group.interItemSpacing = .fixed(spacing)
|
||||
let section = NSCollectionLayoutSection(group: group)
|
||||
section.interGroupSpacing = spacing
|
||||
return section
|
||||
}
|
||||
let view = UICollectionView(frame: .zero, collectionViewLayout: layout)
|
||||
view.backgroundColor = .clear
|
||||
view.isScrollEnabled = false
|
||||
return view
|
||||
}()
|
||||
|
||||
private lazy var dataSource = UICollectionViewDiffableDataSource<Section, Item>(collectionView: collectionView) {
|
||||
[weak self] collectionView, indexPath, item in
|
||||
switch item {
|
||||
case .add:
|
||||
let cell = collectionView.dequeueReusableCell(
|
||||
withReuseIdentifier: TaskAddAddMediaCell.reuseIdentifier,
|
||||
for: indexPath
|
||||
) as! TaskAddAddMediaCell
|
||||
return cell
|
||||
case .file(let id):
|
||||
let cell = collectionView.dequeueReusableCell(
|
||||
withReuseIdentifier: TaskAddMediaCell.reuseIdentifier,
|
||||
for: indexPath
|
||||
) as! TaskAddMediaCell
|
||||
if let file = self?.files.first(where: { $0.id == id }) {
|
||||
cell.apply(file: file)
|
||||
cell.onDelete = { self?.onDeleteFile?(file) }
|
||||
}
|
||||
return cell
|
||||
}
|
||||
}
|
||||
|
||||
private let titleLabel = UILabel()
|
||||
private let tipsLabel = UILabel()
|
||||
private let cloudButton = UIButton(type: .system)
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
backgroundColor = .white
|
||||
layer.cornerRadius = AppRadius.md
|
||||
|
||||
titleLabel.text = "添加图片"
|
||||
titleLabel.font = .app(.bodyMedium)
|
||||
titleLabel.textColor = AppColor.textPrimary
|
||||
|
||||
tipsLabel.text = "(点击图片或视频可添加备注)"
|
||||
tipsLabel.font = .app(.caption)
|
||||
tipsLabel.textColor = AppColor.textPrimary
|
||||
|
||||
cloudButton.setTitle("云盘导入", for: .normal)
|
||||
cloudButton.setTitleColor(AppColor.primary, for: .normal)
|
||||
cloudButton.titleLabel?.font = .app(.caption)
|
||||
cloudButton.addTarget(self, action: #selector(cloudTapped), for: .touchUpInside)
|
||||
|
||||
collectionView.register(TaskAddMediaCell.self, forCellWithReuseIdentifier: TaskAddMediaCell.reuseIdentifier)
|
||||
collectionView.register(TaskAddAddMediaCell.self, forCellWithReuseIdentifier: TaskAddAddMediaCell.reuseIdentifier)
|
||||
collectionView.delegate = self
|
||||
|
||||
addSubview(titleLabel)
|
||||
addSubview(tipsLabel)
|
||||
addSubview(cloudButton)
|
||||
addSubview(collectionView)
|
||||
|
||||
titleLabel.snp.makeConstraints { make in
|
||||
make.top.leading.equalToSuperview().offset(AppSpacing.md)
|
||||
}
|
||||
tipsLabel.snp.makeConstraints { make in
|
||||
make.leading.equalTo(titleLabel.snp.trailing).offset(AppSpacing.xxs)
|
||||
make.centerY.equalTo(titleLabel)
|
||||
}
|
||||
cloudButton.snp.makeConstraints { make in
|
||||
make.trailing.equalToSuperview().inset(AppSpacing.md)
|
||||
make.centerY.equalTo(titleLabel)
|
||||
}
|
||||
collectionView.snp.makeConstraints { make in
|
||||
make.top.equalTo(titleLabel.snp.bottom).offset(AppSpacing.sm)
|
||||
make.leading.trailing.equalToSuperview().inset(AppSpacing.md)
|
||||
make.bottom.equalToSuperview().inset(AppSpacing.md)
|
||||
collectionHeightConstraint = make.height.equalTo(88).constraint
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func apply(files: [TaskFileItem]) {
|
||||
self.files = files
|
||||
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
|
||||
snapshot.appendSections([.main])
|
||||
snapshot.appendItems(files.map { Item.file($0.id) } + [.add])
|
||||
dataSource.apply(snapshot, animatingDifferences: false)
|
||||
collectionView.layoutIfNeeded()
|
||||
let height = max(88, collectionView.collectionViewLayout.collectionViewContentSize.height)
|
||||
collectionHeightConstraint?.update(offset: height)
|
||||
}
|
||||
|
||||
@objc private func cloudTapped() {
|
||||
onCloudImport?()
|
||||
}
|
||||
}
|
||||
|
||||
extension TaskAddMediaSectionView: UICollectionViewDelegate {
|
||||
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||
guard let item = dataSource.itemIdentifier(for: indexPath) else { return }
|
||||
switch item {
|
||||
case .add:
|
||||
onLocalImport?()
|
||||
case .file(let id):
|
||||
guard let file = files.first(where: { $0.id == id }) else { return }
|
||||
onFileTap?(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 素材缩略图 cell。
|
||||
final class TaskAddMediaCell: UICollectionViewCell {
|
||||
|
||||
static let reuseIdentifier = "TaskAddMediaCell"
|
||||
|
||||
var onDelete: (() -> Void)?
|
||||
|
||||
private let imageView = UIImageView()
|
||||
private let overlayView = UIView()
|
||||
private let progressLabel = UILabel()
|
||||
private let deleteButton = UIButton(type: .system)
|
||||
private let playIconView = UIImageView()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
contentView.backgroundColor = AppColor.inputBackground
|
||||
contentView.layer.cornerRadius = AppRadius.sm
|
||||
contentView.clipsToBounds = true
|
||||
|
||||
imageView.contentMode = .scaleAspectFill
|
||||
imageView.clipsToBounds = true
|
||||
|
||||
overlayView.backgroundColor = UIColor.black.withAlphaComponent(0.35)
|
||||
overlayView.isHidden = true
|
||||
|
||||
progressLabel.font = .app(.captionMedium)
|
||||
progressLabel.textColor = .white
|
||||
progressLabel.textAlignment = .center
|
||||
|
||||
deleteButton.setImage(UIImage(systemName: "xmark.circle.fill"), for: .normal)
|
||||
deleteButton.tintColor = .white
|
||||
deleteButton.addTarget(self, action: #selector(deleteTapped), for: .touchUpInside)
|
||||
|
||||
playIconView.image = UIImage(systemName: "play.circle.fill")
|
||||
playIconView.tintColor = .white
|
||||
playIconView.isHidden = true
|
||||
|
||||
contentView.addSubview(imageView)
|
||||
contentView.addSubview(overlayView)
|
||||
contentView.addSubview(playIconView)
|
||||
contentView.addSubview(progressLabel)
|
||||
contentView.addSubview(deleteButton)
|
||||
|
||||
imageView.snp.makeConstraints { make in make.edges.equalToSuperview() }
|
||||
overlayView.snp.makeConstraints { make in make.edges.equalToSuperview() }
|
||||
playIconView.snp.makeConstraints { make in
|
||||
make.center.equalToSuperview()
|
||||
make.width.height.equalTo(28)
|
||||
}
|
||||
progressLabel.snp.makeConstraints { make in make.center.equalToSuperview() }
|
||||
deleteButton.snp.makeConstraints { make in
|
||||
make.top.trailing.equalToSuperview().inset(AppSpacing.xxs)
|
||||
make.width.height.equalTo(22)
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
onDelete = nil
|
||||
imageView.kf.cancelDownloadTask()
|
||||
imageView.image = nil
|
||||
}
|
||||
|
||||
func apply(file: TaskFileItem) {
|
||||
let urlString = file.previewURLString
|
||||
if let url = URL(string: urlString), !urlString.isEmpty {
|
||||
imageView.kf.setImage(with: url)
|
||||
} else {
|
||||
imageView.image = UIImage(systemName: "photo")
|
||||
imageView.tintColor = AppColor.textTertiary
|
||||
}
|
||||
playIconView.isHidden = file.fileType != 1
|
||||
if file.isUploading {
|
||||
overlayView.isHidden = false
|
||||
progressLabel.text = "\(file.uploadProgress)%"
|
||||
} else {
|
||||
overlayView.isHidden = true
|
||||
progressLabel.text = nil
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func deleteTapped() {
|
||||
onDelete?()
|
||||
}
|
||||
}
|
||||
|
||||
/// 本地导入占位 cell。
|
||||
final class TaskAddAddMediaCell: UICollectionViewCell {
|
||||
|
||||
static let reuseIdentifier = "TaskAddAddMediaCell"
|
||||
|
||||
private let iconView = UIImageView()
|
||||
private let titleLabel = UILabel()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
contentView.backgroundColor = AppColor.inputBackground
|
||||
contentView.layer.cornerRadius = AppRadius.sm
|
||||
|
||||
iconView.image = UIImage(systemName: "plus")
|
||||
iconView.tintColor = AppColor.textTabInactive
|
||||
|
||||
titleLabel.text = "本地导入"
|
||||
titleLabel.font = .app(.caption)
|
||||
titleLabel.textColor = AppColor.textTabInactive
|
||||
|
||||
contentView.addSubview(iconView)
|
||||
contentView.addSubview(titleLabel)
|
||||
iconView.snp.makeConstraints { make in
|
||||
make.centerX.equalToSuperview()
|
||||
make.centerY.equalToSuperview().offset(-8)
|
||||
make.width.height.equalTo(24)
|
||||
}
|
||||
titleLabel.snp.makeConstraints { make in
|
||||
make.top.equalTo(iconView.snp.bottom).offset(2)
|
||||
make.centerX.equalToSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
|
||||
/// 任务优先级选择区。
|
||||
final class TaskPrioritySectionView: UIView {
|
||||
|
||||
var onUrgentHourChange: ((Int) -> Void)?
|
||||
var onCustomHourTextChange: ((String) -> Void)?
|
||||
|
||||
private let titleLabel = UILabel()
|
||||
private let noneButton = UIButton(type: .system)
|
||||
private let twoHourButton = UIButton(type: .system)
|
||||
private let customButton = UIButton(type: .system)
|
||||
private let customField = UITextField()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
backgroundColor = .white
|
||||
layer.cornerRadius = AppRadius.md
|
||||
|
||||
titleLabel.text = "优先级"
|
||||
titleLabel.font = .app(.bodyMedium)
|
||||
titleLabel.textColor = AppColor.textPrimary
|
||||
|
||||
configureOptionButton(noneButton, title: "不加急")
|
||||
configureOptionButton(twoHourButton, title: "加急(2小时)")
|
||||
configureOptionButton(customButton, title: "自定义")
|
||||
|
||||
noneButton.addTarget(self, action: #selector(noneTapped), for: .touchUpInside)
|
||||
twoHourButton.addTarget(self, action: #selector(twoHourTapped), for: .touchUpInside)
|
||||
customButton.addTarget(self, action: #selector(customTapped), for: .touchUpInside)
|
||||
|
||||
customField.placeholder = "请输入小时数"
|
||||
customField.font = .app(.body)
|
||||
customField.textColor = AppColor.textPrimary
|
||||
customField.keyboardType = .numberPad
|
||||
customField.borderStyle = .roundedRect
|
||||
customField.isHidden = true
|
||||
customField.addTarget(self, action: #selector(customFieldChanged), for: .editingChanged)
|
||||
|
||||
let row = UIStackView(arrangedSubviews: [noneButton, twoHourButton, customButton])
|
||||
row.axis = .horizontal
|
||||
row.spacing = AppSpacing.xs
|
||||
row.distribution = .fillEqually
|
||||
|
||||
addSubview(titleLabel)
|
||||
addSubview(row)
|
||||
addSubview(customField)
|
||||
|
||||
titleLabel.snp.makeConstraints { make in
|
||||
make.top.leading.equalToSuperview().offset(AppSpacing.md)
|
||||
}
|
||||
row.snp.makeConstraints { make in
|
||||
make.top.equalTo(titleLabel.snp.bottom).offset(AppSpacing.sm)
|
||||
make.leading.trailing.equalToSuperview().inset(AppSpacing.md)
|
||||
make.height.equalTo(42)
|
||||
}
|
||||
customField.snp.makeConstraints { make in
|
||||
make.top.equalTo(row.snp.bottom).offset(AppSpacing.sm)
|
||||
make.leading.trailing.equalToSuperview().inset(AppSpacing.md)
|
||||
make.bottom.equalToSuperview().inset(AppSpacing.md)
|
||||
make.height.equalTo(40)
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func apply(urgentHour: Int) {
|
||||
let isCustom = urgentHour > 0 && urgentHour != 2
|
||||
styleOption(noneButton, selected: urgentHour == 0)
|
||||
styleOption(twoHourButton, selected: urgentHour == 2)
|
||||
styleOption(customButton, selected: isCustom)
|
||||
customField.isHidden = !isCustom
|
||||
if isCustom {
|
||||
customField.text = "\(urgentHour)"
|
||||
} else {
|
||||
customField.text = ""
|
||||
}
|
||||
}
|
||||
|
||||
private func configureOptionButton(_ button: UIButton, title: String) {
|
||||
button.setTitle(title, for: .normal)
|
||||
button.titleLabel?.font = .app(.caption)
|
||||
button.layer.cornerRadius = AppRadius.xs
|
||||
button.clipsToBounds = true
|
||||
}
|
||||
|
||||
private func styleOption(_ button: UIButton, selected: Bool) {
|
||||
button.backgroundColor = selected ? AppColor.primary : AppColor.inputBackground
|
||||
button.setTitleColor(selected ? .white : AppColor.textSecondary, for: .normal)
|
||||
}
|
||||
|
||||
@objc private func noneTapped() { onUrgentHourChange?(0) }
|
||||
@objc private func twoHourTapped() { onUrgentHourChange?(2) }
|
||||
@objc private func customTapped() {
|
||||
onUrgentHourChange?(1)
|
||||
onCustomHourTextChange?("1")
|
||||
}
|
||||
@objc private func customFieldChanged() {
|
||||
onCustomHourTextChange?(customField.text ?? "")
|
||||
}
|
||||
}
|
||||
|
||||
/// 关联订单选择按钮。
|
||||
final class TaskOrderSelectButton: UIControl {
|
||||
|
||||
private let titleLabel = UILabel()
|
||||
private let arrowView = UIImageView(image: UIImage(systemName: "chevron.down"))
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
backgroundColor = .white
|
||||
layer.cornerRadius = AppRadius.sm
|
||||
layer.borderWidth = 1
|
||||
layer.borderColor = AppColor.border.cgColor
|
||||
|
||||
titleLabel.font = .app(.bodyMedium)
|
||||
titleLabel.textColor = AppColor.textTabInactive
|
||||
titleLabel.text = "关联订单(可选)"
|
||||
|
||||
arrowView.tintColor = AppColor.textTabInactive
|
||||
|
||||
addSubview(titleLabel)
|
||||
addSubview(arrowView)
|
||||
titleLabel.snp.makeConstraints { make in
|
||||
make.leading.equalToSuperview().offset(AppSpacing.sm)
|
||||
make.centerY.equalToSuperview()
|
||||
}
|
||||
arrowView.snp.makeConstraints { make in
|
||||
make.trailing.equalToSuperview().inset(AppSpacing.sm)
|
||||
make.centerY.equalToSuperview()
|
||||
}
|
||||
snp.makeConstraints { make in make.height.equalTo(44) }
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func apply(orderNumber: String?) {
|
||||
if let orderNumber, !orderNumber.isEmpty {
|
||||
titleLabel.text = orderNumber
|
||||
titleLabel.textColor = AppColor.textPrimary
|
||||
} else {
|
||||
titleLabel.text = "关联订单(可选)"
|
||||
titleLabel.textColor = AppColor.textTabInactive
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 任务素材备注弹窗。
|
||||
final class TaskFileRemarkDialogView: UIView {
|
||||
|
||||
var onCancel: (() -> Void)?
|
||||
var onConfirm: (() -> Void)?
|
||||
|
||||
private let containerView = UIView()
|
||||
private let titleLabel = UILabel()
|
||||
private let previewView = UIImageView()
|
||||
private let textView = UITextView()
|
||||
private let countLabel = UILabel()
|
||||
private let cancelButton = UIButton(type: .system)
|
||||
private let confirmButton = UIButton(type: .system)
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
backgroundColor = AppColor.overlayScrim
|
||||
|
||||
containerView.backgroundColor = .white
|
||||
containerView.layer.cornerRadius = AppRadius.md
|
||||
|
||||
titleLabel.text = "备注信息"
|
||||
titleLabel.font = .app(.title)
|
||||
|
||||
previewView.contentMode = .scaleAspectFill
|
||||
previewView.clipsToBounds = true
|
||||
previewView.layer.cornerRadius = AppRadius.sm
|
||||
previewView.backgroundColor = AppColor.inputBackground
|
||||
|
||||
textView.font = .app(.body)
|
||||
textView.layer.borderWidth = 1
|
||||
textView.layer.borderColor = AppColor.border.cgColor
|
||||
textView.layer.cornerRadius = AppRadius.sm
|
||||
textView.delegate = self
|
||||
|
||||
countLabel.font = .app(.caption)
|
||||
countLabel.textColor = AppColor.textTertiary
|
||||
countLabel.text = "0/50"
|
||||
|
||||
cancelButton.setTitle("取消", for: .normal)
|
||||
cancelButton.setTitleColor(AppColor.textSecondary, for: .normal)
|
||||
cancelButton.backgroundColor = AppColor.inputBackground
|
||||
cancelButton.layer.cornerRadius = AppRadius.xs
|
||||
cancelButton.addTarget(self, action: #selector(cancelTapped), for: .touchUpInside)
|
||||
|
||||
confirmButton.setTitle("确定", for: .normal)
|
||||
confirmButton.setTitleColor(.white, for: .normal)
|
||||
confirmButton.backgroundColor = AppColor.primary
|
||||
confirmButton.layer.cornerRadius = AppRadius.xs
|
||||
confirmButton.addTarget(self, action: #selector(confirmTapped), for: .touchUpInside)
|
||||
|
||||
addSubview(containerView)
|
||||
containerView.addSubview(titleLabel)
|
||||
containerView.addSubview(previewView)
|
||||
containerView.addSubview(textView)
|
||||
containerView.addSubview(countLabel)
|
||||
containerView.addSubview(cancelButton)
|
||||
containerView.addSubview(confirmButton)
|
||||
|
||||
containerView.snp.makeConstraints { make in
|
||||
make.center.equalToSuperview()
|
||||
make.leading.trailing.equalToSuperview().inset(AppSpacing.lg)
|
||||
}
|
||||
titleLabel.snp.makeConstraints { make in
|
||||
make.top.leading.equalToSuperview().offset(AppSpacing.md)
|
||||
}
|
||||
previewView.snp.makeConstraints { make in
|
||||
make.top.equalTo(titleLabel.snp.bottom).offset(AppSpacing.sm)
|
||||
make.leading.trailing.equalToSuperview().inset(AppSpacing.md)
|
||||
make.height.equalTo(160)
|
||||
}
|
||||
textView.snp.makeConstraints { make in
|
||||
make.top.equalTo(previewView.snp.bottom).offset(AppSpacing.sm)
|
||||
make.leading.trailing.equalToSuperview().inset(AppSpacing.md)
|
||||
make.height.equalTo(80)
|
||||
}
|
||||
countLabel.snp.makeConstraints { make in
|
||||
make.top.equalTo(textView.snp.bottom).offset(AppSpacing.xxs)
|
||||
make.trailing.equalTo(textView)
|
||||
}
|
||||
cancelButton.snp.makeConstraints { make in
|
||||
make.top.equalTo(countLabel.snp.bottom).offset(AppSpacing.md)
|
||||
make.leading.bottom.equalToSuperview().inset(AppSpacing.md)
|
||||
make.height.equalTo(36)
|
||||
make.width.equalTo(72)
|
||||
}
|
||||
confirmButton.snp.makeConstraints { make in
|
||||
make.centerY.equalTo(cancelButton)
|
||||
make.trailing.equalToSuperview().inset(AppSpacing.md)
|
||||
make.height.equalTo(36)
|
||||
make.width.equalTo(72)
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func apply(file: TaskFileItem, text: String) {
|
||||
textView.text = text
|
||||
countLabel.text = "\(text.count)/50"
|
||||
let urlString = file.previewURLString
|
||||
if let url = URL(string: urlString), !urlString.isEmpty {
|
||||
previewView.kf.setImage(with: url)
|
||||
} else {
|
||||
previewView.image = UIImage(systemName: "photo")
|
||||
previewView.tintColor = AppColor.textTertiary
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func cancelTapped() { onCancel?() }
|
||||
@objc private func confirmTapped() { onConfirm?() }
|
||||
|
||||
/// 当前输入的备注文本。
|
||||
var inputText: String { textView.text ?? "" }
|
||||
}
|
||||
|
||||
extension TaskFileRemarkDialogView: UITextViewDelegate {
|
||||
func textViewDidChange(_ textView: UITextView) {
|
||||
if textView.text.count > 50 {
|
||||
textView.text = String(textView.text.prefix(50))
|
||||
}
|
||||
countLabel.text = "\(textView.text.count)/50"
|
||||
}
|
||||
}
|
||||
|
||||
/// 带边框的单行输入框。
|
||||
final class TaskBorderTextField: UIView {
|
||||
|
||||
let textField = UITextField()
|
||||
|
||||
init(placeholder: String) {
|
||||
super.init(frame: .zero)
|
||||
backgroundColor = .white
|
||||
layer.cornerRadius = AppRadius.sm
|
||||
layer.borderWidth = 1
|
||||
layer.borderColor = AppColor.border.cgColor
|
||||
|
||||
textField.placeholder = placeholder
|
||||
textField.font = .app(.body)
|
||||
textField.textColor = AppColor.textPrimary
|
||||
addSubview(textField)
|
||||
textField.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview().inset(UIEdgeInsets(top: 10, left: 12, bottom: 10, right: 12))
|
||||
}
|
||||
snp.makeConstraints { make in make.height.equalTo(44) }
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
|
||||
/// 带边框的多行输入框。
|
||||
final class TaskBorderTextView: UIView {
|
||||
|
||||
let textView = UITextView()
|
||||
|
||||
init(placeholder: String) {
|
||||
super.init(frame: .zero)
|
||||
backgroundColor = .white
|
||||
layer.cornerRadius = AppRadius.sm
|
||||
layer.borderWidth = 1
|
||||
layer.borderColor = AppColor.border.cgColor
|
||||
|
||||
textView.font = .app(.body)
|
||||
textView.textColor = AppColor.textPrimary
|
||||
textView.text = placeholder
|
||||
textView.textColor = AppColor.textTertiary
|
||||
addSubview(textView)
|
||||
textView.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview().inset(AppSpacing.xs)
|
||||
}
|
||||
snp.makeConstraints { make in make.height.equalTo(80) }
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user