LNInterestSetupViewController.swift 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. //
  2. // LNInterestSetupViewController.swift
  3. // Lanu
  4. //
  5. // Created by OneeChan on 2025/12/3.
  6. //
  7. import Foundation
  8. import UIKit
  9. import SnapKit
  10. extension UIView {
  11. func pushToInterestSetup(_ config: LNProfileUpdateConfig) {
  12. let vc = LNInterestSetupViewController(config: config)
  13. navigationController?.pushViewController(vc, animated: true)
  14. }
  15. }
  16. class LNInterestSetupViewController: LNViewController {
  17. private let updateConfig: LNProfileUpdateConfig
  18. private let fakeNavBar = UIView()
  19. private let horizentalSpace = 22.0
  20. private let lineSpace = 8.0
  21. private let itemSpacing = 13.0
  22. private let minWidth = 115.0
  23. private let itemHeight = 85.0
  24. static let headerHeight = 44.0
  25. private let collectionViewLayout = UICollectionViewFlowLayout()
  26. private let collectionView: UICollectionView
  27. private let nextButton = UIButton()
  28. private let bottomGradient = CAGradientLayer()
  29. private let maxCount = 5
  30. private var selectedIndexs: [IndexPath] = []
  31. private var games: [LNGameTypeItemVO] = LNGameMateManager.shared.curGameTypes
  32. init(config: LNProfileUpdateConfig) {
  33. updateConfig = config
  34. collectionView = UICollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
  35. super.init(nibName: nil, bundle: nil)
  36. }
  37. required init?(coder: NSCoder) {
  38. fatalError("init(coder:) has not been implemented")
  39. }
  40. override func viewDidLoad() {
  41. super.viewDidLoad()
  42. showNavigationBar = false
  43. setupViews()
  44. }
  45. override func viewDidLayoutSubviews() {
  46. super.viewDidLayoutSubviews()
  47. if let superlayer = bottomGradient.superlayer {
  48. bottomGradient.frame = superlayer.bounds
  49. }
  50. }
  51. }
  52. extension LNInterestSetupViewController: UICollectionViewDataSource, UICollectionViewDelegate {
  53. func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
  54. games[section].children.count
  55. }
  56. func numberOfSections(in collectionView: UICollectionView) -> Int {
  57. games.count
  58. }
  59. func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
  60. let cell = collectionView.dequeueReusableCell(withReuseIdentifier: LNInterestItemCell.className, for: indexPath) as! LNInterestItemCell
  61. let item = games[indexPath.section].children[indexPath.row]
  62. cell.update(item)
  63. cell.inSelected = selectedIndexs.contains(indexPath)
  64. cell.isEnable = selectedIndexs.count < maxCount || selectedIndexs.contains(indexPath)
  65. return cell
  66. }
  67. func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
  68. guard kind == UICollectionView.elementKindSectionHeader else {
  69. return UICollectionReusableView()
  70. }
  71. let view = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: LNInterestGroupHeader.className, for: indexPath) as! LNInterestGroupHeader
  72. let section = games[indexPath.section]
  73. view.update(section.name)
  74. return view
  75. }
  76. func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
  77. let needReload: Bool
  78. if selectedIndexs.contains(indexPath) {
  79. needReload = selectedIndexs.count == maxCount
  80. selectedIndexs.removeAll { $0 == indexPath }
  81. } else if selectedIndexs.count < maxCount {
  82. selectedIndexs.append(indexPath)
  83. needReload = selectedIndexs.count == maxCount
  84. } else {
  85. needReload = false
  86. }
  87. if needReload {
  88. collectionView.reloadData()
  89. } else {
  90. collectionView.reloadItems(at: [indexPath])
  91. }
  92. checkNextButtonEnable()
  93. }
  94. }
  95. extension LNInterestSetupViewController {
  96. private func checkNextButtonEnable() {
  97. nextButton.isEnabled = selectedIndexs.count > 0
  98. }
  99. private func setupViews() {
  100. view.backgroundColor = .primary_1
  101. let bg = UIImageView()
  102. bg.image = .init(named: "ic_login_interest_bg")
  103. view.addSubview(bg)
  104. bg.snp.makeConstraints { make in
  105. make.horizontalEdges.equalToSuperview()
  106. make.top.equalToSuperview()
  107. }
  108. let navBar = buildFakeNavBar()
  109. view.addSubview(navBar)
  110. navBar.snp.makeConstraints { make in
  111. make.directionalHorizontalEdges.equalToSuperview()
  112. make.top.equalToSuperview()
  113. }
  114. let titleView = buildTitles()
  115. view.addSubview(titleView)
  116. titleView.snp.makeConstraints { make in
  117. make.leading.equalToSuperview().offset(22)
  118. make.top.equalTo(fakeNavBar.snp.bottom).offset(22)
  119. }
  120. let listView = buildListView()
  121. view.addSubview(listView)
  122. listView.snp.makeConstraints { make in
  123. make.directionalHorizontalEdges.equalToSuperview().inset(horizentalSpace)
  124. make.top.equalTo(titleView.snp.bottom).offset(6)
  125. }
  126. let bottom = buildBottomView()
  127. view.addSubview(bottom)
  128. bottom.snp.makeConstraints { make in
  129. make.horizontalEdges.equalToSuperview()
  130. make.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottom)
  131. make.top.equalTo(listView.snp.bottom).offset(-16)
  132. }
  133. }
  134. private func buildFakeNavBar() -> UIView {
  135. fakeNavBar.snp.makeConstraints { make in
  136. make.height.equalTo(44 + UIView.statusBarHeight)
  137. }
  138. let container = UIView()
  139. fakeNavBar.addSubview(container)
  140. container.snp.makeConstraints { make in
  141. make.horizontalEdges.equalToSuperview()
  142. make.bottom.equalToSuperview()
  143. make.height.equalTo(44)
  144. }
  145. let backButton = UIButton()
  146. backButton.addAction(UIAction(handler: { [weak self] _ in
  147. guard let self else { return }
  148. navigationController?.popViewController(animated: true)
  149. }), for: .touchUpInside)
  150. let buttonImage: UIImage? = .init(systemName: "chevron.backward")?.withRenderingMode(.alwaysTemplate)
  151. backButton.setImage(buttonImage, for: .normal)
  152. backButton.tintColor = .text_4
  153. container.addSubview(backButton)
  154. backButton.snp.makeConstraints { make in
  155. make.centerY.equalToSuperview()
  156. make.leading.equalToSuperview().offset(16)
  157. make.width.height.equalTo(24)
  158. }
  159. return fakeNavBar
  160. }
  161. private func buildTitles() -> UIView {
  162. let stackView = UIStackView()
  163. stackView.axis = .vertical
  164. stackView.spacing = 6
  165. let mainLabel = UILabel()
  166. mainLabel.text = .init(key: "A00111")
  167. mainLabel.font = .heading_h1
  168. mainLabel.textColor = .text_5
  169. stackView.addArrangedSubview(mainLabel)
  170. let descLabel = UILabel()
  171. descLabel.font = .body_m
  172. descLabel.textColor = .text_3
  173. descLabel.text = .init(key: "A00112")
  174. descLabel.numberOfLines = 0
  175. stackView.addArrangedSubview(descLabel)
  176. return stackView
  177. }
  178. private func buildListView() -> UIView {
  179. let count = floor(CGFloat((view.bounds.width - horizentalSpace * 2) + itemSpacing) / minWidth)
  180. let itemWidth = (view.bounds.width - horizentalSpace * 2 + itemSpacing) / count - itemSpacing
  181. collectionViewLayout.itemSize = .init(width: itemWidth, height: itemHeight)
  182. collectionViewLayout.minimumLineSpacing = lineSpace
  183. collectionViewLayout.minimumInteritemSpacing = itemSpacing
  184. collectionViewLayout.headerReferenceSize = .init(width: view.bounds.width - horizentalSpace * 2, height: Self.headerHeight)
  185. // collectionViewLayout.sectionHeadersPinToVisibleBounds = true
  186. collectionView.backgroundColor = .clear
  187. collectionView.dataSource = self
  188. collectionView.delegate = self
  189. collectionView.showsHorizontalScrollIndicator = false
  190. collectionView.showsVerticalScrollIndicator = false
  191. collectionView.register(LNInterestItemCell.self, forCellWithReuseIdentifier: LNInterestItemCell.className)
  192. collectionView.register(LNInterestGroupHeader.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: LNInterestGroupHeader.className)
  193. collectionView.contentInset = .init(top: 0, left: 0, bottom: 20, right: 0)
  194. return collectionView
  195. }
  196. private func buildBottomView() -> UIView {
  197. let container = UIView()
  198. bottomGradient.colors = [
  199. UIColor.white.withAlphaComponent(0).cgColor,
  200. UIColor.primary_1.cgColor,
  201. UIColor.primary_1.cgColor
  202. ]
  203. bottomGradient.locations = [0, 0.3, 1]
  204. bottomGradient.startPoint = .init(x: 0, y: 0)
  205. bottomGradient.endPoint = .init(x: 0, y: 1)
  206. container.layer.addSublayer(bottomGradient)
  207. nextButton.setBackgroundImage(.primary_8, for: .normal)
  208. nextButton.setTitle(.init(key: "A00113"), for: .normal)
  209. nextButton.setTitleColor(.text_1, for: .normal)
  210. nextButton.layer.cornerRadius = 23.5
  211. nextButton.clipsToBounds = true
  212. nextButton.isEnabled = false
  213. nextButton.addAction(UIAction(handler: { [weak self] _ in
  214. guard let self else { return }
  215. let interests = selectedIndexs.compactMap {
  216. self.games[$0.section].children[$0.row].code
  217. }
  218. updateConfig.interest = interests
  219. LNProfileManager.shared.modifyMyProfile(config: updateConfig) { [weak self] success in
  220. guard let self else { return }
  221. guard success else { return }
  222. navigationController?.popToRootViewController(animated: true)
  223. LNGameMateManager.shared.getGameTypeList { _ in }
  224. }
  225. }), for: .touchUpInside)
  226. container.addSubview(nextButton)
  227. nextButton.snp.makeConstraints { make in
  228. make.horizontalEdges.equalToSuperview().inset(16)
  229. make.top.equalToSuperview().offset(16)
  230. make.bottom.equalToSuperview().offset(-4)
  231. make.height.equalTo(47)
  232. }
  233. return container
  234. }
  235. }
  236. private class LNInterestItemCell: UICollectionViewCell {
  237. private let ic = UIImageView()
  238. private let nameLabel = UILabel()
  239. private let bg = UIImageView()
  240. var inSelected: Bool = false {
  241. didSet {
  242. bg.image = .init(named: inSelected ? "ic_login_interest_item_selected" : "ic_login_interest_item")
  243. }
  244. }
  245. var isEnable: Bool = true {
  246. didSet {
  247. contentView.alpha = isEnable ? 1.0 : 0.5
  248. isUserInteractionEnabled = isEnable
  249. }
  250. }
  251. override init(frame: CGRect) {
  252. super.init(frame: frame)
  253. contentView.addSubview(bg)
  254. bg.snp.makeConstraints { make in
  255. make.center.equalToSuperview()
  256. make.width.equalTo(102)
  257. make.height.equalTo(85)
  258. }
  259. ic.layer.cornerRadius = 25
  260. ic.clipsToBounds = true
  261. bg.addSubview(ic)
  262. ic.snp.makeConstraints { make in
  263. make.centerX.equalToSuperview()
  264. make.top.equalToSuperview().offset(7)
  265. make.width.height.equalTo(50)
  266. }
  267. nameLabel.font = .body_s
  268. nameLabel.textColor = .text_5
  269. nameLabel.textAlignment = .center
  270. bg.addSubview(nameLabel)
  271. nameLabel.snp.makeConstraints { make in
  272. make.directionalHorizontalEdges.equalToSuperview().inset(6)
  273. make.bottom.equalToSuperview().offset(-6)
  274. }
  275. }
  276. func update(_ item: LNGameCategoryItemVO) {
  277. ic.sd_setImage(with: URL(string: item.icon))
  278. nameLabel.text = item.name
  279. let curState = isSelected
  280. isSelected = curState
  281. }
  282. required init?(coder: NSCoder) {
  283. fatalError("init(coder:) has not been implemented")
  284. }
  285. }
  286. private class LNInterestGroupHeader: UICollectionReusableView {
  287. private let titleLabel = UILabel()
  288. override init(frame: CGRect) {
  289. super.init(frame: frame)
  290. snp.makeConstraints { make in
  291. make.height.equalTo(LNInterestSetupViewController.headerHeight)
  292. }
  293. titleLabel.font = .heading_h2
  294. titleLabel.textColor = .text_5
  295. addSubview(titleLabel)
  296. titleLabel.snp.makeConstraints { make in
  297. make.bottom.equalToSuperview().offset(-8)
  298. make.leading.equalToSuperview()
  299. }
  300. }
  301. func update(_ title: String) {
  302. titleLabel.text = title
  303. }
  304. required init?(coder: NSCoder) {
  305. fatalError("init(coder:) has not been implemented")
  306. }
  307. }