完善任务流程、键盘适配与页面交互

This commit is contained in:
2026-07-10 15:56:15 +08:00
parent f88a85a807
commit ab5220e460
189 changed files with 16779 additions and 1038 deletions

View File

@ -3,6 +3,7 @@
// suixinkan
//
import AVFoundation
import Kingfisher
import SnapKit
import UIKit
@ -139,6 +140,9 @@ final class TaskAddMediaSectionView: UIView {
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
snapshot.appendSections([.main])
snapshot.appendItems(files.map { Item.file($0.id) } + [.add])
let existingItems = Set(dataSource.snapshot().itemIdentifiers)
let itemsToReconfigure = snapshot.itemIdentifiers.filter(existingItems.contains)
snapshot.reconfigureItems(itemsToReconfigure)
dataSource.apply(snapshot, animatingDifferences: false)
updateCollectionHeight(itemCount: files.count + 1)
}
@ -320,7 +324,10 @@ final class TaskAddMediaCell: UICollectionViewCell {
func apply(file: TaskFileItem) {
let urlString = file.previewURLString
if let url = URL(string: urlString), !urlString.isEmpty {
if let localPreviewData = file.localPreviewData,
let localImage = UIImage(data: localPreviewData) {
imageView.image = localImage
} else if let url = URL(string: urlString), !urlString.isEmpty {
imageView.kf.setImage(with: url)
} else {
imageView.image = UIImage(systemName: "photo")
@ -391,6 +398,7 @@ final class TaskPrioritySectionView: UIView {
var onUrgentHourChange: ((Int) -> Void)?
var onCustomHourTextChange: ((String) -> Void)?
var onShowMessage: ((String) -> Void)?
private let titleLabel = UILabel()
private let noneButton = UIButton(type: .system)
@ -401,6 +409,8 @@ final class TaskPrioritySectionView: UIView {
private let optionRow = UIStackView()
private let customRow = UIStackView()
private let stackView = UIStackView()
private var customHourText = ""
private var lastUrgentHour = 0
override init(frame: CGRect) {
super.init(frame: frame)
@ -426,6 +436,7 @@ final class TaskPrioritySectionView: UIView {
customField.layer.cornerRadius = AppRadius.sm
customField.layer.borderWidth = 1
customField.layer.borderColor = AppColor.border.cgColor
customField.delegate = self
customField.leftView = UIView(frame: CGRect(x: 0, y: 0, width: AppSpacing.sm, height: 1))
customField.leftViewMode = .always
customField.addTarget(self, action: #selector(customFieldChanged), for: .editingChanged)
@ -480,15 +491,21 @@ final class TaskPrioritySectionView: UIView {
func apply(urgentHour: Int) {
let isCustom = urgentHour > 0 && urgentHour != 2
let wasCustom = lastUrgentHour > 0 && lastUrgentHour != 2
styleOption(noneButton, selected: urgentHour == 0)
styleOption(twoHourButton, selected: urgentHour == 2)
styleOption(customButton, selected: isCustom)
customRow.isHidden = !isCustom
if isCustom {
customField.text = "\(urgentHour)"
if !wasCustom || urgentHour != lastUrgentHour {
customHourText = "\(urgentHour)"
}
customField.text = customHourText
} else {
customHourText = ""
customField.text = ""
}
lastUrgentHour = urgentHour
}
private func configureOptionButton(_ button: UIButton, title: String) {
@ -506,14 +523,31 @@ final class TaskPrioritySectionView: UIView {
@objc private func noneTapped() { onUrgentHourChange?(0) }
@objc private func twoHourTapped() { onUrgentHourChange?(2) }
@objc private func customTapped() {
guard lastUrgentHour <= 0 || lastUrgentHour == 2 else { return }
onUrgentHourChange?(1)
onCustomHourTextChange?("1")
}
@objc private func customFieldChanged() {
onCustomHourTextChange?(customField.text ?? "")
customHourText = customField.text ?? ""
onCustomHourTextChange?(customHourText)
}
@objc private func customConfirmTapped() {
onCustomHourTextChange?(customField.text ?? "")
guard let hour = Int(customHourText), hour > 0 else {
onShowMessage?("请输入有效的正整数")
return
}
onCustomHourTextChange?(customHourText)
onShowMessage?("加急\(hour)小时")
}
}
extension TaskPrioritySectionView: UITextFieldDelegate {
func textField(
_ textField: UITextField,
shouldChangeCharactersIn range: NSRange,
replacementString string: String
) -> Bool {
string.isEmpty || string.allSatisfy(\.isNumber)
}
}
@ -573,7 +607,9 @@ final class TaskFileRemarkDialogView: KeyboardAvoidingDialogView {
private let containerView = UIView()
private let titleLabel = UILabel()
private let previewContainerView = UIView()
private let previewView = UIImageView()
private let videoView = TaskRemarkVideoView()
private let remarkTitleLabel = UILabel()
private let textView = UITextView()
private let placeholderLabel = UILabel()
@ -599,6 +635,10 @@ final class TaskFileRemarkDialogView: KeyboardAvoidingDialogView {
previewView.layer.cornerRadius = AppRadius.lg
previewView.backgroundColor = AppColor.inputBackground
previewContainerView.backgroundColor = AppColor.inputBackground
previewContainerView.layer.cornerRadius = AppRadius.lg
previewContainerView.clipsToBounds = true
remarkTitleLabel.text = "备注信息"
remarkTitleLabel.font = .app(.caption)
remarkTitleLabel.textColor = AppColor.textPrimary
@ -640,7 +680,9 @@ final class TaskFileRemarkDialogView: KeyboardAvoidingDialogView {
addSubview(containerView)
containerView.addSubview(titleLabel)
containerView.addSubview(previewView)
containerView.addSubview(previewContainerView)
previewContainerView.addSubview(previewView)
previewContainerView.addSubview(videoView)
containerView.addSubview(remarkTitleLabel)
containerView.addSubview(countLabel)
containerView.addSubview(textView)
@ -655,13 +697,15 @@ final class TaskFileRemarkDialogView: KeyboardAvoidingDialogView {
titleLabel.snp.makeConstraints { make in
make.top.leading.equalToSuperview().offset(AppSpacing.md)
}
previewView.snp.makeConstraints { make in
previewContainerView.snp.makeConstraints { make in
make.top.equalTo(titleLabel.snp.bottom).offset(AppSpacing.sm)
make.leading.trailing.equalToSuperview().inset(AppSpacing.md)
make.height.equalTo(previewView.snp.width)
make.height.equalTo(previewContainerView.snp.width)
}
previewView.snp.makeConstraints { make in make.edges.equalToSuperview() }
videoView.snp.makeConstraints { make in make.edges.equalToSuperview() }
remarkTitleLabel.snp.makeConstraints { make in
make.top.equalTo(previewView.snp.bottom).offset(AppSpacing.md)
make.top.equalTo(previewContainerView.snp.bottom).offset(AppSpacing.md)
make.leading.equalToSuperview().offset(AppSpacing.md)
}
countLabel.snp.makeConstraints { make in
@ -701,8 +745,23 @@ final class TaskFileRemarkDialogView: KeyboardAvoidingDialogView {
textView.text = text
countLabel.text = "\(text.count)/50"
placeholderLabel.isHidden = !text.isEmpty
if file.fileType == 1,
let url = URL(string: file.uploadFileUrl ?? file.cloudFile?.fileUrl ?? ""),
!url.absoluteString.isEmpty {
previewView.isHidden = true
videoView.isHidden = false
videoView.configure(url: url)
return
}
videoView.stop()
videoView.isHidden = true
previewView.isHidden = false
let urlString = file.previewURLString
if let url = URL(string: urlString), !urlString.isEmpty {
if let localPreviewData = file.localPreviewData,
let localImage = UIImage(data: localPreviewData) {
previewView.image = localImage
} else if let url = URL(string: urlString), !urlString.isEmpty {
previewView.kf.setImage(with: url)
} else {
previewView.image = UIImage(systemName: "photo")
@ -710,13 +769,82 @@ final class TaskFileRemarkDialogView: KeyboardAvoidingDialogView {
}
}
@objc private func cancelTapped() { onCancel?() }
@objc private func confirmTapped() { onConfirm?() }
@objc private func cancelTapped() {
videoView.stop()
onCancel?()
}
@objc private func confirmTapped() {
videoView.stop()
onConfirm?()
}
///
var inputText: String { textView.text ?? "" }
}
///
final class TaskRemarkVideoView: UIView {
private let playerLayer = AVPlayerLayer()
private let playButton = UIButton(type: .system)
private var player: AVPlayer?
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .black
layer.addSublayer(playerLayer)
playButton.setImage(UIImage(systemName: "play.circle.fill"), for: .normal)
playButton.tintColor = .white
playButton.addTarget(self, action: #selector(togglePlayback), for: .touchUpInside)
addSubview(playButton)
playButton.snp.makeConstraints { make in
make.center.equalToSuperview()
make.width.height.equalTo(52)
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
playerLayer.frame = bounds
}
///
func configure(url: URL) {
stop()
let player = AVPlayer(url: url)
self.player = player
playerLayer.player = player
playerLayer.videoGravity = .resizeAspect
playButton.isHidden = false
}
///
func stop() {
player?.pause()
player = nil
playerLayer.player = nil
playButton.isHidden = false
}
@objc private func togglePlayback() {
guard let player else { return }
if player.timeControlStatus == .playing {
player.pause()
playButton.isHidden = false
} else {
player.play()
playButton.isHidden = true
}
}
}
extension TaskFileRemarkDialogView: UITextViewDelegate {
func textViewDidChange(_ textView: UITextView) {
if textView.text.count > 50 {