LNPostShareViewController.swift 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. //
  2. // LNPostShareViewController.swift
  3. // Lanu
  4. //
  5. // Created by OneeChan on 2025/11/27.
  6. //
  7. import Foundation
  8. import UIKit
  9. import SnapKit
  10. import Combine
  11. extension UIView {
  12. func pushToPostShare() {
  13. let vc = LNPostShareViewController()
  14. navigationController?.pushViewController(vc, animated: true)
  15. }
  16. }
  17. class LNPostShareViewController: LNViewController {
  18. private let postView = UIImageView()
  19. private let skillStackView = UIStackView()
  20. private var selectedSkills: [LNGameMateSkillVO] = []
  21. private var lastDesc: String?
  22. override func viewDidLoad() {
  23. super.viewDidLoad()
  24. setupViews()
  25. prefetchSkillIcon()
  26. loadLastOrder()
  27. selectedSkills = Array(myUserInfo.skills.prefix(3))
  28. updateSkillsView()
  29. }
  30. }
  31. extension LNPostShareViewController {
  32. private func prefetchSkillIcon() {
  33. myUserInfo.skills.forEach {
  34. SDWebImageManager.shared.loadImage(with: URL(string: $0.icon),
  35. progress: nil)
  36. { _, _, _, _, _, _ in }
  37. }
  38. }
  39. private func loadLastOrder() {
  40. LNOrderManager.shared.getLastOrder { [weak self] res in
  41. guard let self else { return }
  42. guard let res else { return }
  43. lastDesc = .init(key: "A00220", Double(res.finishTime).formattedDate("."), res.nickname, res.bizCateGoryName)
  44. }
  45. }
  46. }
  47. extension LNPostShareViewController {
  48. private func updateSkillsView() {
  49. skillStackView.arrangedSubviews.forEach {
  50. skillStackView.removeArrangedSubview($0)
  51. $0.removeFromSuperview()
  52. }
  53. selectedSkills.prefix(3).forEach {
  54. let itemView = LNPostShareSkillItemView()
  55. itemView.icon.sd_setImage(with: URL(string: $0.icon))
  56. itemView.nameLabel.text = $0.name
  57. skillStackView.addArrangedSubview(itemView)
  58. }
  59. }
  60. private func showSkillSelectPanel() {
  61. guard !myUserInfo.skills.isEmpty else { return }
  62. let panel = LNPostSkillSelectPanel()
  63. panel.setSkills(skills: myUserInfo.skills, selecteds: selectedSkills)
  64. panel.handler = { [weak self] selections in
  65. guard let self else { return }
  66. selectedSkills = selections
  67. updateSkillsView()
  68. }
  69. panel.popup(view)
  70. }
  71. private func setupViews() {
  72. title = .init(key: "A00221")
  73. view.backgroundColor = .primary_1
  74. let topCover = UIImageView()
  75. topCover.image = .icHomeTopBg
  76. view.addSubview(topCover)
  77. topCover.snp.makeConstraints { make in
  78. make.top.leading.trailing.equalToSuperview()
  79. }
  80. let scrollView = UIScrollView()
  81. scrollView.backgroundColor = .clear
  82. scrollView.showsHorizontalScrollIndicator = false
  83. scrollView.showsVerticalScrollIndicator = false
  84. view.addSubview(scrollView)
  85. scrollView.snp.makeConstraints { make in
  86. make.edges.equalToSuperview()
  87. }
  88. let fakeView = UIView()
  89. scrollView.addSubview(fakeView)
  90. fakeView.snp.makeConstraints { make in
  91. make.horizontalEdges.equalToSuperview()
  92. make.top.equalToSuperview()
  93. make.width.equalToSuperview()
  94. make.height.equalTo(0)
  95. }
  96. let post = buildPost()
  97. scrollView.addSubview(post)
  98. post.snp.makeConstraints { make in
  99. make.horizontalEdges.equalToSuperview().inset(16)
  100. make.top.equalToSuperview().offset(32)
  101. }
  102. let skill = buildSkillView()
  103. scrollView.addSubview(skill)
  104. skill.snp.makeConstraints { make in
  105. make.top.equalTo(post.snp.bottom)
  106. make.horizontalEdges.equalToSuperview().inset(16)
  107. make.height.equalTo(100)
  108. }
  109. let album = buildAlbum()
  110. scrollView.addSubview(album)
  111. album.snp.makeConstraints { make in
  112. make.leading.equalToSuperview().offset(16)
  113. make.top.equalTo(skill.snp.bottom).offset(20)
  114. make.trailing.equalToSuperview()
  115. make.bottom.equalToSuperview().offset(-view.safeBottomInset - 45)
  116. }
  117. let share = UIButton()
  118. share.setTitle(.init(key: "A00222"), for: .normal)
  119. share.setTitleColor(.text_1, for: .normal)
  120. share.setBackgroundImage(.primary_8, for: .normal)
  121. share.layer.cornerRadius = 23.5
  122. share.clipsToBounds = true
  123. share.addAction(UIAction(handler: { [weak self] _ in
  124. guard let self else { return }
  125. guard let album = postView.image else { return }
  126. let generator = LNPostShareImageGenerator()
  127. generator.setOrderInfo(desc: lastDesc)
  128. generator.setAlbum(image: album)
  129. generator.setSkills(skills: selectedSkills)
  130. generator.setShareQRCode(url: .profileShareUrl + "?id=\(myUid)&share=app")
  131. let shareImage = generator.generate()
  132. shareImage.saveToLibrary { [weak self] success, err in
  133. guard let self else { return }
  134. if let err {
  135. showToast(err.localizedDescription)
  136. } else {
  137. showToast(.init(key: "A00157"))
  138. navigationController?.popViewController(animated: true)
  139. }
  140. }
  141. }), for: .touchUpInside)
  142. view.addSubview(share)
  143. share.snp.makeConstraints { make in
  144. make.horizontalEdges.equalToSuperview().inset(16)
  145. make.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottom).offset(-4)
  146. make.height.equalTo(47)
  147. }
  148. }
  149. private func buildPost() -> UIView {
  150. let container = UIView()
  151. container.backgroundColor = .fill
  152. container.layer.cornerRadius = 20
  153. let info = buildUserInfo()
  154. container.addSubview(info)
  155. info.snp.makeConstraints { make in
  156. make.horizontalEdges.equalToSuperview().inset(16)
  157. make.top.equalToSuperview().offset(12)
  158. }
  159. postView.contentMode = .scaleAspectFill
  160. postView.layer.cornerRadius = 20
  161. postView.clipsToBounds = true
  162. container.addSubview(postView)
  163. postView.snp.makeConstraints { make in
  164. make.horizontalEdges.equalToSuperview().inset(16)
  165. make.top.equalToSuperview().offset(44)
  166. make.height.equalTo(postView.snp.width).multipliedBy(317.0/311.0)
  167. }
  168. let bio = UILabel()
  169. bio.font = .body_m
  170. bio.textColor = .text_4
  171. bio.numberOfLines = 0
  172. bio.text = myUserInfo.intro
  173. container.addSubview(bio)
  174. bio.snp.makeConstraints { make in
  175. make.horizontalEdges.equalToSuperview().inset(22)
  176. make.top.equalTo(postView.snp.bottom).offset(14)
  177. make.bottom.equalToSuperview().offset(-20)
  178. }
  179. let line = buildDashLine()
  180. container.layer.addSublayer(line)
  181. container.publisher(for: \.bounds).removeDuplicates().sink
  182. { [weak line] newValue in
  183. guard let line else { return }
  184. line.frame = .init(
  185. x: 14,
  186. y: newValue.height - 1,
  187. width: newValue.width - 28,
  188. height: 1)
  189. let path = UIBezierPath()
  190. path.move(to: CGPoint(x: 0, y: 0))
  191. path.addLine(to: CGPoint(x: newValue.width - 28, y: 0))
  192. line.path = path.cgPath
  193. }.store(in: &cancellables)
  194. return container
  195. }
  196. private func buildUserInfo() -> UIView {
  197. let container = UIView()
  198. container.snp.makeConstraints { make in
  199. make.height.equalTo(22)
  200. }
  201. let userNameLabel = UILabel()
  202. userNameLabel.text = myUserInfo.nickname
  203. userNameLabel.font = .heading_h2
  204. userNameLabel.textColor = .text_5
  205. container.addSubview(userNameLabel)
  206. userNameLabel.snp.makeConstraints { make in
  207. make.leading.centerY.equalToSuperview()
  208. }
  209. let gender = LNGenderView()
  210. gender.update(myUserInfo.gender, myUserInfo.age)
  211. container.addSubview(gender)
  212. gender.snp.makeConstraints { make in
  213. make.centerY.equalToSuperview()
  214. make.leading.equalTo(userNameLabel.snp.trailing).offset(4)
  215. }
  216. let starLabel = UILabel()
  217. starLabel.text = "\(myUserInfo.star)"
  218. starLabel.font = .heading_h2
  219. starLabel.textColor = .text_5
  220. container.addSubview(starLabel)
  221. starLabel.snp.makeConstraints { make in
  222. make.centerY.equalToSuperview()
  223. make.trailing.equalToSuperview()
  224. }
  225. let star = LNStarScoreView()
  226. star.score = 1.0
  227. star.icSize = 18
  228. container.addSubview(star)
  229. star.snp.makeConstraints { make in
  230. make.centerY.equalToSuperview()
  231. make.trailing.equalTo(starLabel.snp.leading).offset(-4)
  232. }
  233. return container
  234. }
  235. private func buildDashLine() -> CAShapeLayer {
  236. let lineLayer = CAShapeLayer()
  237. lineLayer.frame = .zero
  238. lineLayer.fillColor = UIColor.clear.cgColor
  239. lineLayer.strokeColor = UIColor.fill_3.cgColor
  240. lineLayer.lineWidth = 2.0
  241. lineLayer.lineDashPattern = [NSNumber(value: 3), NSNumber(value: 3)]
  242. return lineLayer
  243. }
  244. private func buildSkillView() -> UIView {
  245. let container = UIView()
  246. container.backgroundColor = .fill
  247. container.layer.cornerRadius = 20
  248. container.onTap { [weak self] in
  249. guard let self else { return }
  250. showSkillSelectPanel()
  251. }
  252. skillStackView.axis = .horizontal
  253. skillStackView.spacing = 10
  254. container.addSubview(skillStackView)
  255. skillStackView.snp.makeConstraints { make in
  256. make.leading.equalToSuperview().offset(113)
  257. make.bottom.equalToSuperview().offset(-18)
  258. }
  259. let ic = UIImageView()
  260. ic.image = .icHomeTabSelected
  261. ic.alpha = 0.5
  262. container.addSubview(ic)
  263. ic.snp.makeConstraints { make in
  264. make.leading.equalToSuperview().offset(20)
  265. make.top.equalToSuperview().offset(31)
  266. }
  267. let tipsLabel = UILabel()
  268. tipsLabel.text = .init(key: "A00219")
  269. tipsLabel.font = .heading_h4
  270. tipsLabel.textColor = .text_5
  271. tipsLabel.numberOfLines = 0
  272. container.addSubview(tipsLabel)
  273. tipsLabel.snp.makeConstraints { make in
  274. make.centerY.equalToSuperview()
  275. make.leading.equalToSuperview().offset(30)
  276. make.width.equalTo(60)
  277. }
  278. let arrow = UIImageView.arrowImageView(size: 16)
  279. container.addSubview(arrow)
  280. arrow.snp.makeConstraints { make in
  281. make.centerY.equalToSuperview()
  282. make.trailing.equalToSuperview().offset(-8)
  283. }
  284. return container
  285. }
  286. private func buildAlbum() -> UIView {
  287. let scrollView = UIScrollView()
  288. scrollView.showsVerticalScrollIndicator = false
  289. scrollView.showsHorizontalScrollIndicator = false
  290. let fakeView = UIView()
  291. scrollView.addSubview(fakeView)
  292. fakeView.snp.makeConstraints { make in
  293. make.verticalEdges.equalToSuperview()
  294. make.height.equalToSuperview()
  295. make.leading.equalToSuperview()
  296. make.width.equalTo(0)
  297. }
  298. let stackView = UIStackView()
  299. stackView.axis = .horizontal
  300. stackView.spacing = 10
  301. scrollView.addSubview(stackView)
  302. stackView.snp.makeConstraints { make in
  303. make.edges.equalToSuperview()
  304. }
  305. var albumItemViews: [LNSharePostAlbumItemView] = []
  306. myUserInfo.photos.forEach { url in
  307. let album = LNSharePostAlbumItemView()
  308. album.imageView.sd_setImage(with: URL(string: url))
  309. album.onTap { [weak self, weak album] in
  310. guard let self, let album else { return }
  311. albumItemViews.forEach {
  312. $0.isSelected = $0 == album
  313. }
  314. postView.sd_setImage(with: URL(string: url))
  315. }
  316. album.isSelected = false
  317. stackView.addArrangedSubview(album)
  318. albumItemViews.append(album)
  319. }
  320. albumItemViews.first?.isSelected = true
  321. postView.sd_setImage(with: URL(string: myUserInfo.photos.first ?? ""))
  322. return scrollView
  323. }
  324. }
  325. private class LNSharePostAlbumItemView: UIView {
  326. let imageView = UIImageView()
  327. var isSelected: Bool = false {
  328. didSet {
  329. if isSelected {
  330. imageView.layer.cornerRadius = 11
  331. imageView.snp.remakeConstraints { make in
  332. make.center.equalToSuperview()
  333. make.width.height.equalToSuperview().inset(2)
  334. }
  335. selectedBorder.isHidden = false
  336. alpha = 1.0
  337. } else {
  338. imageView.layer.cornerRadius = 12
  339. imageView.snp.remakeConstraints { make in
  340. make.center.equalToSuperview()
  341. make.width.height.equalToSuperview()
  342. }
  343. selectedBorder.isHidden = true
  344. alpha = 0.5
  345. }
  346. }
  347. }
  348. private let selectedBorder = UIImageView()
  349. override init(frame: CGRect) {
  350. super.init(frame: frame)
  351. snp.makeConstraints { make in
  352. make.width.height.equalTo(90)
  353. }
  354. selectedBorder.image = .primary_7
  355. selectedBorder.layer.cornerRadius = 12
  356. selectedBorder.clipsToBounds = true
  357. selectedBorder.isHidden = true
  358. addSubview(selectedBorder)
  359. selectedBorder.snp.makeConstraints { make in
  360. make.edges.equalToSuperview()
  361. }
  362. imageView.clipsToBounds = true
  363. imageView.contentMode = .scaleAspectFill
  364. addSubview(imageView)
  365. }
  366. required init?(coder: NSCoder) {
  367. fatalError("init(coder:) has not been implemented")
  368. }
  369. }
  370. #if DEBUG
  371. import SwiftUI
  372. struct LNSharePostViewControllerPreview: UIViewControllerRepresentable {
  373. func makeUIViewController(context: Context) -> some UIViewController {
  374. LNPostShareViewController()
  375. }
  376. func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { }
  377. }
  378. #Preview(body: {
  379. LNSharePostViewControllerPreview()
  380. })
  381. #endif