LNCreateFeedViewController.swift 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. //
  2. // LNCreateFeedViewController.swift
  3. // Gami
  4. //
  5. // Created by OneeChan on 2026/2/27.
  6. //
  7. import Foundation
  8. import UIKit
  9. import SnapKit
  10. extension UIView {
  11. func pushToCreateFeed() {
  12. let vc = LNCreateFeedViewController()
  13. navigationController?.pushViewController(vc, animated: true)
  14. }
  15. }
  16. class LNCreateFeedViewController: LNViewController {
  17. private let saveButton = UIButton()
  18. private let maxPhotosCount = 6
  19. private let photosView = UIStackView()
  20. private var imageUrls: [String] {
  21. photosView.arrangedSubviews.compactMap { ($0 as? LNImageUploadView)?.imageUrl }
  22. }
  23. private let selectPhotoView = UIView()
  24. private let videoUploadView = LNVideoUploadView()
  25. private let textInputView = LNCommonTextView()
  26. override func viewDidLoad() {
  27. super.viewDidLoad()
  28. setupViews()
  29. }
  30. }
  31. extension LNCreateFeedViewController: LNImageUploadViewDelegate, LNVideoUploadViewDelegate {
  32. func onImageUploadViewDidClickDelete(view: LNImageUploadView) {
  33. photosView.removeArrangedSubview(view)
  34. view.removeFromSuperview()
  35. selectPhotoView.isHidden = false
  36. checkSaveButton()
  37. }
  38. func onImageUploadView(view: LNImageUploadView, didUploadImage url: String) {
  39. checkSaveButton()
  40. }
  41. func onVideoUploadViewDidClickDelete(view: LNVideoUploadView) {
  42. photosView.superview?.isHidden = false
  43. videoUploadView.superview?.isHidden = true
  44. checkSaveButton()
  45. }
  46. func onVideoUploadViewStartUpload(view: LNVideoUploadView) {
  47. showLoading()
  48. }
  49. func onVideoUploadViewUploadFailed(view: LNVideoUploadView) {
  50. dismissLoading()
  51. }
  52. func onVideoUploadView(view: LNVideoUploadView, didUploadVideo url: String, imageUrl: String) {
  53. let media = LNFeedMediaVO()
  54. media.type = .video
  55. media.url = url
  56. media.videoCover = imageUrl
  57. let item = LNPostFeedItem(text: textInputView.text, medias: [media])
  58. LNFeedManager.shared.postFeed(item: item) { [weak self] success in
  59. dismissLoading()
  60. guard let self else { return }
  61. if success {
  62. navigationController?.popViewController(animated: true)
  63. }
  64. }
  65. }
  66. }
  67. extension LNCreateFeedViewController: UITextViewDelegate {
  68. func textViewDidChange(_ textView: UITextView) {
  69. checkSaveButton()
  70. }
  71. }
  72. extension LNCreateFeedViewController {
  73. private func checkSaveButton() {
  74. let enable = !textInputView.text.isEmpty
  75. || !imageUrls.isEmpty
  76. || videoUploadView.superview?.isHidden == false
  77. if enable != saveButton.isEnabled {
  78. if enable {
  79. saveButton.setBackgroundImage(.primary_8, for: .normal)
  80. } else {
  81. saveButton.setBackgroundImage(nil, for: .normal)
  82. }
  83. saveButton.isEnabled = enable
  84. }
  85. }
  86. private func setupViews() {
  87. setupNavBar()
  88. view.onTap { [weak self] in
  89. guard let self else { return }
  90. view.endEditing(true)
  91. }
  92. let stackView = UIStackView()
  93. stackView.axis = .vertical
  94. stackView.spacing = 16
  95. stackView.clipsToBounds = false
  96. view.addSubview(stackView)
  97. stackView.snp.makeConstraints { make in
  98. make.horizontalEdges.equalToSuperview().inset(16)
  99. make.top.equalToSuperview().offset(16)
  100. }
  101. let photos = buildPhotos()
  102. stackView.addArrangedSubview(photos)
  103. let video = buildVideo()
  104. stackView.addArrangedSubview(video)
  105. let text = buildText()
  106. stackView.addArrangedSubview(text)
  107. }
  108. private func setupNavBar() {
  109. title = .init(key: "A00295")
  110. saveButton.setTitle(.init(key: "A00185"), for: .normal)
  111. saveButton.titleLabel?.font = .heading_h4
  112. saveButton.setTitleColor(.white, for: .normal)
  113. saveButton.backgroundColor = .fill_4
  114. saveButton.layer.cornerRadius = 16
  115. saveButton.clipsToBounds = true
  116. saveButton.contentEdgeInsets = .init(top: 0, left: 16, bottom: 0, right: 16)
  117. saveButton.addAction(UIAction(handler: { [weak self] _ in
  118. guard let self else { return }
  119. if videoUploadView.superview?.isHidden == false {
  120. videoUploadView.startUpload()
  121. } else {
  122. var medias: [LNFeedMediaVO] = []
  123. for url in self.imageUrls {
  124. let media = LNFeedMediaVO()
  125. media.type = .image
  126. media.url = url
  127. medias.append(media)
  128. }
  129. let item = LNPostFeedItem(text: textInputView.text, medias: medias)
  130. showLoading()
  131. LNFeedManager.shared.postFeed(item: item) { [weak self] success in
  132. dismissLoading()
  133. guard let self else { return }
  134. guard success else {
  135. return
  136. }
  137. showToast(.init(key: "A00306"))
  138. navigationController?.popViewController(animated: true)
  139. }
  140. }
  141. }), for: .touchUpInside)
  142. saveButton.snp.makeConstraints { make in
  143. make.height.equalTo(32)
  144. make.width.greaterThanOrEqualTo(67)
  145. }
  146. setRightButton(saveButton)
  147. saveButton.isEnabled = false
  148. }
  149. private func buildPhotos() -> UIView {
  150. let scrollView = UIScrollView()
  151. scrollView.showsHorizontalScrollIndicator = false
  152. scrollView.clipsToBounds = false
  153. photosView.axis = .horizontal
  154. photosView.spacing = 10
  155. scrollView.addSubview(photosView)
  156. photosView.snp.makeConstraints { make in
  157. make.edges.equalToSuperview()
  158. make.height.equalToSuperview()
  159. make.height.equalTo(90)
  160. }
  161. selectPhotoView.backgroundColor = .fill_1
  162. selectPhotoView.layer.cornerRadius = 8.33
  163. selectPhotoView.onTap { [weak self] in
  164. guard let self else { return }
  165. LNBottomSheetMenu.showImageSelectMenu(view: view, options: .init(allowVideo: photosView.arrangedSubviews.count == 1))
  166. { [weak self] image, videoUrl in
  167. guard let self else { return }
  168. if let image {
  169. let imageView = buildImageView()
  170. imageView.uploadImage(image: image)
  171. imageView.onTap { [weak self, weak imageView] in
  172. guard let self, let imageView else { return }
  173. guard let url = imageView.imageUrl else { return }
  174. view.presentImagePreview(imageUrls, imageUrls.firstIndex(of: url)!)
  175. }
  176. photosView.insertArrangedSubview(imageView, at: photosView.arrangedSubviews.count - 1)
  177. imageView.snp.makeConstraints { make in
  178. make.width.equalTo(imageView.snp.height)
  179. }
  180. selectPhotoView.isHidden = photosView.arrangedSubviews.count == maxPhotosCount + 1
  181. } else if let videoUrl {
  182. photosView.superview?.isHidden = true
  183. videoUploadView.superview?.isHidden = false
  184. let size = videoUploadView.preview(url: videoUrl)
  185. let previewSize: CGSize = size.width >= size.height ? .init(width: 173, height: 130) : .init(width: 130, height: 173)
  186. videoUploadView.snp.updateConstraints { make in
  187. make.size.equalTo(previewSize)
  188. }
  189. checkSaveButton()
  190. }
  191. }
  192. }
  193. selectPhotoView.snp.makeConstraints { make in
  194. make.width.equalTo(selectPhotoView.snp.height)
  195. }
  196. photosView.addArrangedSubview(selectPhotoView)
  197. let selectView = UIView()
  198. selectView.isUserInteractionEnabled = false
  199. selectPhotoView.addSubview(selectView)
  200. selectView.snp.makeConstraints { make in
  201. make.horizontalEdges.equalToSuperview()
  202. make.centerY.equalToSuperview()
  203. }
  204. let cameraIc = UIImageView(image: .icImChatCamera)
  205. selectView.addSubview(cameraIc)
  206. cameraIc.snp.makeConstraints { make in
  207. make.centerX.equalToSuperview()
  208. make.top.equalToSuperview()
  209. }
  210. let tipsLabel = UILabel()
  211. tipsLabel.text = .init(key: "A00296")
  212. tipsLabel.numberOfLines = 2
  213. tipsLabel.font = .body_xs
  214. tipsLabel.textColor = .text_4
  215. tipsLabel.textAlignment = .center
  216. selectView.addSubview(tipsLabel)
  217. tipsLabel.snp.makeConstraints { make in
  218. make.horizontalEdges.equalToSuperview().inset(10)
  219. make.top.equalTo(cameraIc.snp.bottom).offset(2)
  220. make.bottom.equalToSuperview()
  221. }
  222. return scrollView
  223. }
  224. private func buildImageView() -> LNImageUploadView {
  225. let imageView = LNImageUploadView()
  226. imageView.uploadType = .feedback
  227. imageView.layer.cornerRadius = 9
  228. imageView.clipsToBounds = true
  229. imageView.showClearButton = true
  230. imageView.delegate = self
  231. return imageView
  232. }
  233. private func buildVideo() -> UIView {
  234. let container = UIView()
  235. container.isHidden = true
  236. videoUploadView.delegate = self
  237. videoUploadView.onTap { [weak self] in
  238. guard let self else { return }
  239. guard let sourceUrl = videoUploadView.sourceUrl else { return }
  240. view.presentVideoPreview([sourceUrl.absoluteString], 0)
  241. }
  242. videoUploadView.snp.makeConstraints { make in
  243. make.size.equalTo(CGSize.zero)
  244. }
  245. container.addSubview(videoUploadView)
  246. videoUploadView.snp.makeConstraints { make in
  247. make.verticalEdges.equalToSuperview()
  248. make.leading.equalToSuperview()
  249. }
  250. return container
  251. }
  252. private func buildText() -> UIView {
  253. textInputView.maxInput = LNFeedManager.feedMaxIntput
  254. textInputView.placeholderLabel.text = .init(key: "A00297")
  255. textInputView.placeholderLabel.font = .body_m
  256. textInputView.placeholderLabel.textColor = .text_2
  257. textInputView.delegate = self
  258. textInputView.snp.makeConstraints { make in
  259. make.height.equalTo(229)
  260. }
  261. return textInputView
  262. }
  263. }
  264. #if DEBUG
  265. import SwiftUI
  266. struct LNCreateFeedViewControllerPreview: UIViewControllerRepresentable {
  267. func makeUIViewController(context: Context) -> some UIViewController {
  268. LNCreateFeedViewController()
  269. }
  270. func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
  271. }
  272. }
  273. #Preview(body: {
  274. LNCreateFeedViewControllerPreview()
  275. })
  276. #endif