// // LNImageUploadView.swift // Lanu // // Created by OneeChan on 2025/12/25. // import Foundation import UIKit import SnapKit protocol LNImageUploadViewDelegate: AnyObject { func onImageUploadView(view: LNImageUploadView, didUploadImage url: String) func onImageUploadViewStartUpload(view: LNImageUploadView) func onImageUploadViewDidClickDelete(view: LNImageUploadView) } extension LNImageUploadViewDelegate { func onImageUploadView(view: LNImageUploadView, didUploadImage url: String) {} func onImageUploadViewStartUpload(view: LNImageUploadView) {} func onImageUploadViewDidClickDelete(view: LNImageUploadView) {} } class LNImageUploadView: UIImageView { private let defaultView = UIImageView() var showDefault: Bool = false { didSet { defaultView.isHidden = image != nil || !showDefault } } private let progressCover = UIView() let progressView = LNCircleProgressView() private let failedIc = UIImageView() private var curTask: String? private(set) var imageUrl: String? private let deleteButton = UIButton() var showClearButton: Bool = false { didSet { deleteButton.isHidden = !showClearButton || image == nil } } var uploadType: LNUploadFileType = .feedback weak var delegate: LNImageUploadViewDelegate? override var image: UIImage? { didSet { defaultView.isHidden = image != nil || !showDefault } } override init(image: UIImage? = nil) { super.init(image: image) setupViews() } func loadImage(url: String) { reset() imageUrl = url sd_setImage(with: URL(string: url)) progressCover.isHidden = true if showClearButton { deleteButton.isHidden = false } } func uploadImage(image: UIImage) { reset() self.image = image progressCover.isHidden = false failedIc.isHidden = true progressView.isHidden = false if showClearButton { deleteButton.isHidden = false } let suffix = "\(Int(image.size.width))x\(Int(image.size.height)).jpeg" delegate?.onImageUploadViewStartUpload(view: self) curTask = LNFileUploader.shared.startUpload( type: uploadType, fileData: image.jpegData(compressionQuality: 1.0)!, suffix: suffix) { [weak self] progress in guard let self else { return } progressView.setProgress(CGFloat(progress), animated: true) } completionHandler: { [weak self] url, err in guard let self else { return } curTask = nil if let url, err == nil { imageUrl = url progressCover.isHidden = true let key = SDWebImageManager.shared.cacheKey(for: URL(string: url)) SDWebImageManager.shared.imageCache.store?(image, imageData: nil, forKey: key, context: nil, cacheType: .all) delegate?.onImageUploadView(view: self, didUploadImage: url) } else if url == nil, let err { showToast(err) failedIc.isHidden = false progressView.isHidden = true } } } func cancelUpload() { if let curTask { LNFileUploader.shared.cancelUpload(taskID: curTask) } } func reset() { cancelUpload() image = nil imageUrl = nil curTask = nil deleteButton.isHidden = true } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } extension LNImageUploadView { private func setupViews() { isUserInteractionEnabled = true contentMode = .scaleAspectFill let noneView = buildDefault() addSubview(noneView) noneView.snp.makeConstraints { make in make.center.equalToSuperview() } let progress = buildProgress() addSubview(progress) progress.snp.makeConstraints { make in make.edges.equalToSuperview() } let clear = buildClearButton() addSubview(clear) clear.snp.makeConstraints { make in make.top.equalToSuperview() make.trailing.equalToSuperview() } } private func buildDefault() -> UIView { let config = UIImage.SymbolConfiguration(pointSize: 17) defaultView.image = .init(systemName: "plus", withConfiguration: config) defaultView.tintColor = .text_3 defaultView.isHidden = true return defaultView } private func buildProgress() -> UIView { progressCover.isHidden = true progressCover.backgroundColor = .black.withAlphaComponent(0.3) progressCover.addSubview(progressView) progressView.snp.makeConstraints { make in make.center.equalToSuperview() make.width.height.equalTo(30) } failedIc.isHidden = true failedIc.image = .init(named: "ic_im_chat_send_failed") failedIc.onTap { [weak self] in guard let self else { return } guard let image = self.image else { return } uploadImage(image: image) } progressCover.addSubview(failedIc) failedIc.snp.makeConstraints { make in make.center.equalToSuperview() make.width.height.equalTo(24) } return progressCover } private func buildClearButton() -> UIView { deleteButton.isHidden = true deleteButton.addAction(UIAction(handler: { [weak self] _ in guard let self else { return } reset() delegate?.onImageUploadViewDidClickDelete(view: self) }), for: .touchUpInside) deleteButton.snp.makeConstraints { make in make.width.height.equalTo(28) } let config = UIImage.SymbolConfiguration(pointSize: 7) let icon = UIImageView() icon.image = .init(systemName: "xmark", withConfiguration: config) icon.contentMode = .center icon.tintColor = .white icon.backgroundColor = .black.withAlphaComponent(0.5) icon.layer.cornerRadius = 8 deleteButton.addSubview(icon) icon.snp.makeConstraints { make in make.center.equalToSuperview() make.width.height.equalTo(16) } return deleteButton } }