LNImageUploadView.swift 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. //
  2. // LNImageUploadView.swift
  3. // Lanu
  4. //
  5. // Created by OneeChan on 2025/12/25.
  6. //
  7. import Foundation
  8. import UIKit
  9. import SnapKit
  10. protocol LNImageUploadViewDelegate: NSObject {
  11. func onImageUploadView(view: LNImageUploadView, didUploadImage url: String)
  12. func onImageUploadViewStartUpload(view: LNImageUploadView)
  13. func onImageUploadViewDidClickDelete(view: LNImageUploadView)
  14. }
  15. extension LNImageUploadViewDelegate {
  16. func onImageUploadView(view: LNImageUploadView, didUploadImage url: String) {}
  17. func onImageUploadViewStartUpload(view: LNImageUploadView) {}
  18. func onImageUploadViewDidClickDelete(view: LNImageUploadView) {}
  19. }
  20. class LNImageUploadView: UIImageView {
  21. private let defaultView = UIImageView()
  22. var showDefault: Bool = false {
  23. didSet {
  24. defaultView.isHidden = image != nil || !showDefault
  25. }
  26. }
  27. private let progressCover = UIView()
  28. let progressView = LNCircleProgressView()
  29. private let failedIc = UIImageView()
  30. private var curTask: String?
  31. private(set) var imageUrl: String?
  32. private let deleteButton = UIButton()
  33. var showClearButton: Bool = false {
  34. didSet {
  35. deleteButton.isHidden = !showClearButton || image == nil
  36. }
  37. }
  38. var uploadType: LNUploadFileType = .feedback
  39. weak var delegate: LNImageUploadViewDelegate?
  40. override var image: UIImage? {
  41. didSet {
  42. defaultView.isHidden = image != nil || !showDefault
  43. }
  44. }
  45. override init(image: UIImage? = nil) {
  46. super.init(image: image)
  47. setupViews()
  48. }
  49. func loadImage(url: String) {
  50. reset()
  51. imageUrl = url
  52. sd_setImage(with: URL(string: url))
  53. progressCover.isHidden = true
  54. if showClearButton {
  55. deleteButton.isHidden = false
  56. }
  57. }
  58. func uploadImage(image: UIImage) {
  59. reset()
  60. self.image = image
  61. progressCover.isHidden = false
  62. failedIc.isHidden = true
  63. progressView.isHidden = false
  64. if showClearButton {
  65. deleteButton.isHidden = false
  66. }
  67. let suffix = "\(Int(image.size.width))x\(Int(image.size.height)).jpeg"
  68. delegate?.onImageUploadViewStartUpload(view: self)
  69. curTask = LNFileUploader.shared.startUpload(
  70. type: uploadType, fileData: image.jpegData(compressionQuality: 1.0)!,
  71. suffix: suffix)
  72. { [weak self] progress in
  73. guard let self else { return }
  74. progressView.setProgress(CGFloat(progress), animated: true)
  75. } completionHandler: { [weak self] url, err in
  76. guard let self else { return }
  77. curTask = nil
  78. if let url, err == nil {
  79. imageUrl = url
  80. progressCover.isHidden = true
  81. let key = SDWebImageManager.shared.cacheKey(for: URL(string: url))
  82. SDWebImageManager.shared.imageCache.store?(image, imageData: nil, forKey: key, context: nil, cacheType: .all)
  83. delegate?.onImageUploadView(view: self, didUploadImage: url)
  84. } else if url == nil, let err {
  85. showToast(err)
  86. failedIc.isHidden = false
  87. progressView.isHidden = true
  88. }
  89. }
  90. }
  91. func cancelUpload() {
  92. if let curTask {
  93. LNFileUploader.shared.cancelUpload(taskID: curTask)
  94. }
  95. }
  96. func reset() {
  97. cancelUpload()
  98. image = nil
  99. imageUrl = nil
  100. curTask = nil
  101. deleteButton.isHidden = true
  102. }
  103. required init?(coder: NSCoder) {
  104. fatalError("init(coder:) has not been implemented")
  105. }
  106. }
  107. extension LNImageUploadView {
  108. private func setupViews() {
  109. isUserInteractionEnabled = true
  110. contentMode = .scaleAspectFill
  111. let noneView = buildDefault()
  112. addSubview(noneView)
  113. noneView.snp.makeConstraints { make in
  114. make.center.equalToSuperview()
  115. }
  116. let progress = buildProgress()
  117. addSubview(progress)
  118. progress.snp.makeConstraints { make in
  119. make.edges.equalToSuperview()
  120. }
  121. let clear = buildClearButton()
  122. addSubview(clear)
  123. clear.snp.makeConstraints { make in
  124. make.top.equalToSuperview()
  125. make.trailing.equalToSuperview()
  126. }
  127. }
  128. private func buildDefault() -> UIView {
  129. let config = UIImage.SymbolConfiguration(pointSize: 17)
  130. defaultView.image = .init(systemName: "plus", withConfiguration: config)
  131. defaultView.tintColor = .text_3
  132. defaultView.isHidden = true
  133. return defaultView
  134. }
  135. private func buildProgress() -> UIView {
  136. progressCover.isHidden = true
  137. progressCover.backgroundColor = .black.withAlphaComponent(0.3)
  138. progressCover.addSubview(progressView)
  139. progressView.snp.makeConstraints { make in
  140. make.center.equalToSuperview()
  141. make.width.height.equalTo(30)
  142. }
  143. failedIc.isHidden = true
  144. failedIc.image = .init(named: "ic_im_chat_send_failed")
  145. failedIc.onTap { [weak self] in
  146. guard let self else { return }
  147. guard let image = self.image else { return }
  148. uploadImage(image: image)
  149. }
  150. progressCover.addSubview(failedIc)
  151. failedIc.snp.makeConstraints { make in
  152. make.center.equalToSuperview()
  153. make.width.height.equalTo(24)
  154. }
  155. return progressCover
  156. }
  157. private func buildClearButton() -> UIView {
  158. deleteButton.isHidden = true
  159. deleteButton.addAction(UIAction(handler: { [weak self] _ in
  160. guard let self else { return }
  161. reset()
  162. delegate?.onImageUploadViewDidClickDelete(view: self)
  163. }), for: .touchUpInside)
  164. deleteButton.snp.makeConstraints { make in
  165. make.width.height.equalTo(28)
  166. }
  167. let config = UIImage.SymbolConfiguration(pointSize: 7)
  168. let icon = UIImageView()
  169. icon.image = .init(systemName: "xmark", withConfiguration: config)
  170. icon.contentMode = .center
  171. icon.tintColor = .white
  172. icon.backgroundColor = .black.withAlphaComponent(0.5)
  173. icon.layer.cornerRadius = 8
  174. deleteButton.addSubview(icon)
  175. icon.snp.makeConstraints { make in
  176. make.center.equalToSuperview()
  177. make.width.height.equalTo(16)
  178. }
  179. return deleteButton
  180. }
  181. }