完善任务流程、键盘适配与页面交互
This commit is contained in:
@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user