// // CommonUploadImageButton.swift // Crush // // Created by Leon on 2025/7/21. // import UIKit import SnapKit enum CommentUploadState: Int { case loading case success case failed case checkdFailed } class CommonUploadImageButton: UIButton { private var indicatorBGView: UIView! private var indicatorView: UIActivityIndicatorView! private var retryButton: UIButton! private var errorButton: UIButton! private var contentImageView: UIImageView! private var closeButton: EPIconTertiaryDarkButton! @Published private(set) var uploadState: CommentUploadState = .loading { didSet { reloadViews() } } private(set) var imageUrlNowIfHave: String? var uploadModel: CommentImageModel? var optionErrorMsg: String? var closeAction: ((CommonUploadImageButton) -> Void)? var tapRetryAction: ((CommonUploadImageButton) -> Void)? var uploadSuceess: ((CommonUploadImageButton) -> Void)? var uploadOrCheckFailed: ((CommonUploadImageButton) -> Void)? override init(frame: CGRect) { super.init(frame: frame) initializeViews() } required init?(coder: NSCoder) { super.init(coder: coder) initializeViews() } private func initializeViews() { layer.cornerRadius = 8.0 layer.masksToBounds = true contentImageView = { let imageView = UIImageView() addSubview(imageView) imageView.contentMode = .scaleAspectFill imageView.snp.makeConstraints { make in make.edges.equalToSuperview() } return imageView }() indicatorBGView = { let view = UIView() addSubview(view) view.isHidden = true view.layer.borderColor = UIColor.c.civn.cgColor//EPSystemToken.color(.important_variant_normal).cgColor view.layer.cornerRadius = layer.cornerRadius view.backgroundColor = UIColor.c.csedn//EPSystemToken.color(.surface_element_dark_normal) view.snp.makeConstraints { make in make.edges.equalToSuperview() } return view }() indicatorView = { let indicator = UIActivityIndicatorView() addSubview(indicator) indicator.color = .c.ctpsn//EPSystemToken.color(.txt_primary_specialmap_normal) indicator.isHidden = true indicator.snp.makeConstraints { make in make.center.equalToSuperview() make.size.equalTo(CGSize(width: 24, height: 24)) } return indicator }() closeButton = { let button = EPIconTertiaryDarkButton(radius: .round, iconSize: .xxs, iconCode: .delete) addSubview(button) button.isHidden = true button.touchAreaInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) button.addTarget(self, action: #selector(closeButtonAction), for: .touchUpInside) button.snp.makeConstraints { make in make.trailing.equalToSuperview().offset(-8) make.top.equalToSuperview().offset(8) make.size.equalTo(CGSize(width: 16, height: 16)) } return button }() retryButton = { let button = UIButton(type: .custom) addSubview(button) button.isHidden = true let image = MWIconFont.image(fromIcon: .iconReload, size: .init(width: 16, height: 16), color: .c.ctpsn) button.setImage(image, for: .normal) button.contentMode = .center button.touchAreaInsets = UIEdgeInsets(top: 2, left: 2, bottom: 2, right: 2) button.addTarget(self, action: #selector(retryButtonAction), for: .touchUpInside) button.snp.makeConstraints { make in make.center.equalToSuperview() make.size.equalTo(CGSize(width: 32, height: 32)) } return button }() errorButton = { let button = UIButton(type: .custom) addSubview(button) button.isHidden = true button.setImage(UIImage(named: "album_reupload"), for: .normal) button.touchAreaInsets = UIEdgeInsets(top: 2, left: 2, bottom: 2, right: 2) button.addTarget(self, action: #selector(errorButtonAction), for: .touchUpInside) button.snp.makeConstraints { make in make.center.equalToSuperview() make.size.equalTo(CGSize(width: 32, height: 32)) } return button }() setupImageDefaultState() } private func reloadViews() { indicatorBGView.isHidden = true indicatorBGView.layer.borderWidth = 0 if indicatorView.isAnimating { indicatorView.stopAnimating() } retryButton.isHidden = true closeButton.isHidden = true errorButton.isHidden = true switch uploadState { case .loading: indicatorBGView.isHidden = false indicatorView.startAnimating() case .failed: indicatorBGView.isHidden = false retryButton.isHidden = false closeButton.isHidden = false indicatorBGView.layer.borderWidth = CLSystemToken.border(token: .bs)//EPSystemToken.border(.s) case .success: closeButton.isHidden = false case .checkdFailed: indicatorBGView.isHidden = false closeButton.isHidden = false errorButton.isHidden = false indicatorBGView.layer.borderWidth = CLSystemToken.border(token: .bs)//EPSystemToken.border(.s) } if imageView?.image != nil { setImage(nil, for: .normal) } } // MARK: - Public Methods func setupImageDefaultState() { let image = MWIconFont.image(fromIcon: .iconUploadimg, size: .init(width: 24, height: 24), color: .c.cttn) setImage(image, for: .normal) backgroundColor = .c.csen//EPSystemToken.color(.surface_element_normal) contentImageView.image = nil imageUrlNowIfHave = nil closeButton.isHidden = true indicatorBGView.isHidden = true errorButton.isHidden = true } @discardableResult func bindViewImage(_ image: UIImage, report: Bool = false, checkImage: Bool = false) -> UploadPhotoM { contentImageView.image = image uploadState = .loading DispatchQueue.main.async {[weak self] in // UploadPhotoM is returned finally self?.uploadState = .loading } let model = CommentImageModel() model.isReport = report uploadModel = model let photo = model.startUpload(with: image, checkImage: checkImage) uploadModel?.imageUploadComplete = { [weak self] result, photo in guard let self = self else { return } if result { self.uploadState = .success self.uploadSuceess?(self) } else if photo.imageChecked && photo.imageCheckIsViolation { self.uploadState = .checkdFailed self.uploadOrCheckFailed?(self) } else { self.uploadState = .failed self.uploadOrCheckFailed?(self) } self.reloadViews() } reloadViews() return photo } func bindViewImageNoUpload(_ image: UIImage) { contentImageView.image = image uploadState = .loading reloadViews() } func bindViewImage(_ image: UIImage?) { guard let img = image else{ return } contentImageView.image = img } func bindSuccessImage(_ image: UIImage?) { guard let img = image else{ return } contentImageView.image = img uploadState = .success } func bindViewImageUrl(_ imageUrl: String) { imageUrlNowIfHave = imageUrl //contentImageView.sd_setImage(with: URL(string: imageUrl), placeholderImage: UIImage.imgPlaceHolder) contentImageView.loadImage(imageUrl) let model = CommentImageModel() model.uploadUrl = imageUrl uploadModel = model uploadState = .success reloadViews() } func givtStateOutsize(_ state: CommentUploadState) { uploadState = state } func setupCornerRadius(_ radius: CGFloat) { layer.cornerRadius = radius indicatorBGView.layer.cornerRadius = radius } // MARK: - Actions @objc private func retryButtonAction() { uploadState = .loading reloadViews() if let tapRetryAction = tapRetryAction { tapRetryAction(self) } else { if let img = uploadModel?.image{ uploadModel?.startUpload(with: img) }else{ fatalError() } } } @objc private func errorButtonAction() { if let optionErrorMsg = optionErrorMsg, !optionErrorMsg.isEmpty { //Hud.showTip(optionErrorMsg) Hud.toast(str: optionErrorMsg) } else { Hud.toast(str: "NSFW materials have been detected, please upload other photos") } } @objc private func closeButtonAction() { closeAction?(self) } }