LNBaseInfoSetupViewController.swift 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  1. //
  2. // LNBaseInfoSetupViewController.swift
  3. // Lanu
  4. //
  5. // Created by OneeChan on 2025/12/2.
  6. //
  7. import Foundation
  8. import UIKit
  9. import SnapKit
  10. import Combine
  11. extension UIView {
  12. func pushToBaseInfoSetup(_ config: LNProfileUpdateConfig) {
  13. let vc = LNBaseInfoSetupViewController(config: config)
  14. navigationController?.pushViewController(vc, animated: true)
  15. }
  16. }
  17. enum LNAvatarModifyType: CaseIterable {
  18. case camera
  19. case photo
  20. case random
  21. var title: String {
  22. switch self {
  23. case .camera: .init(key: "A00011")
  24. case .photo: .init(key: "A00012")
  25. case .random: .init(key: "A00100")
  26. }
  27. }
  28. }
  29. class LNBaseInfoSetupViewController: LNViewController {
  30. private let updateConfig: LNProfileUpdateConfig
  31. private let container = UIView()
  32. private let avatar = LNImageUploadView()
  33. private let nameInputField = UITextField()
  34. private let randomView = UIView()
  35. // private let birthDayLabel = UILabel()
  36. // private var curDate: TimeInterval = Date().yearAgo(-18).timeIntervalSince1970 {
  37. // didSet {
  38. // birthDayLabel.text = curDate.formattedFullDate("-")
  39. // }
  40. // }
  41. private let nextButton = UIButton()
  42. private var randomProfile: LNProfileRandomInfoVO?
  43. init(config: LNProfileUpdateConfig) {
  44. updateConfig = config
  45. super.init(nibName: nil, bundle: nil)
  46. }
  47. required init?(coder: NSCoder) {
  48. fatalError("init(coder:) has not been implemented")
  49. }
  50. override func viewDidLoad() {
  51. super.viewDidLoad()
  52. setupViews()
  53. loadRandomProfile()
  54. LNEventDeliver.addObserver(self)
  55. }
  56. }
  57. extension LNBaseInfoSetupViewController {
  58. private func changeAvatar() {
  59. let handler: (UIImage?, URL?) -> Void = { [weak self] image, _ in
  60. guard let self, let image = image?.compress(type: .avatar) else { return }
  61. avatar.uploadImage(image: image)
  62. checkNextButtonEnable()
  63. }
  64. let panel = LNBottomSheetMenu()
  65. var titles: [String] = [
  66. LNAvatarModifyType.camera.title,
  67. LNAvatarModifyType.photo.title,
  68. ]
  69. if randomProfile?.avatars.isEmpty == false {
  70. titles.append(LNAvatarModifyType.random.title)
  71. }
  72. panel.update(titles) { [weak self] index, text in
  73. guard let self else { return }
  74. if index == 0 {
  75. LNMediaPicker.shared.pick(from: view, type: .camera, options: .init(allowEdit: true), handler: handler)
  76. } else if index == 1 {
  77. LNMediaPicker.shared.pick(from: view, type: .photo, options: .init(allowEdit: true), handler: handler)
  78. } else if index == 2 {
  79. guard let item = randomProfile?.avatars.randomElement() else { return }
  80. avatar.loadImage(url: item)
  81. checkNextButtonEnable()
  82. }
  83. }
  84. panel.popup()
  85. }
  86. private func loadRandomProfile() {
  87. LNProfileManager.shared.getRandomProfile { [weak self] male, female in
  88. guard let self else { return }
  89. if updateConfig.gender == .male, let male {
  90. randomProfile = male
  91. randomView.isHidden = male.nicknames.isEmpty
  92. } else if updateConfig.gender == .female, let female {
  93. randomProfile = female
  94. randomView.isHidden = female.nicknames.isEmpty
  95. }
  96. if let url = randomProfile?.avatars.randomElement() {
  97. avatar.loadImage(url: url)
  98. }
  99. nameInputField.text = randomProfile?.nicknames.randomElement()
  100. checkNextButtonEnable()
  101. }
  102. }
  103. }
  104. extension LNBaseInfoSetupViewController: LNImageUploadViewDelegate {
  105. func onImageUploadViewStartUpload(view: LNImageUploadView) {
  106. checkNextButtonEnable()
  107. }
  108. func onImageUploadView(view: LNImageUploadView, didUploadImage url: String) {
  109. checkNextButtonEnable()
  110. }
  111. }
  112. extension LNBaseInfoSetupViewController: LNKeyboardNotify {
  113. func onKeyboardWillShow(curInput: UIView?, keyboardHeight: CGFloat) {
  114. guard let curInput, curInput.isDescendant(of: view) else { return }
  115. let frame = curInput.convert(curInput.frame, to: view)
  116. let offset = keyboardHeight - (view.bounds.height - CGRectGetMaxY(frame) - 20)
  117. if offset > 0 {
  118. container.snp.updateConstraints { make in
  119. make.top.equalToSuperview().offset(-offset)
  120. }
  121. }
  122. }
  123. func onKeyboardShow(curInput: UIView?, keyboardHeight: CGFloat) {
  124. guard curInput == nameInputField else { return }
  125. view.layoutIfNeeded()
  126. }
  127. func onKeyboardWillHide(curInput: UIView?) {
  128. guard curInput == nameInputField else { return }
  129. container.snp.updateConstraints { make in
  130. make.top.equalToSuperview().offset(0)
  131. }
  132. }
  133. func onKeyboardHide(curInput: UIView?) {
  134. guard curInput == nameInputField else { return }
  135. view.layoutIfNeeded()
  136. }
  137. }
  138. extension LNBaseInfoSetupViewController {
  139. private func checkNextButtonEnable() {
  140. let name = nameInputField.text ?? ""
  141. nextButton.isEnabled = !name.isEmpty && avatar.imageUrl?.isEmpty == false
  142. }
  143. private func setupViews() {
  144. navigationBarColor = .clear
  145. view.backgroundColor = .primary_1
  146. view.addSubview(container)
  147. container.onTap { [weak self] in
  148. guard let self else { return }
  149. self.view.endEditing(true)
  150. }
  151. container.snp.makeConstraints { make in
  152. make.top.equalToSuperview().offset(0)
  153. make.horizontalEdges.equalToSuperview()
  154. make.height.equalToSuperview()
  155. }
  156. let bg = UIImageView()
  157. bg.image = .icLoginProfileBg
  158. container.addSubview(bg)
  159. bg.snp.makeConstraints { make in
  160. make.horizontalEdges.equalToSuperview()
  161. make.top.equalTo(fakeNaviBgView)
  162. }
  163. let titleView = buildTitles()
  164. container.addSubview(titleView)
  165. titleView.snp.makeConstraints { make in
  166. make.horizontalEdges.equalToSuperview().inset(22)
  167. make.top.equalToSuperview().offset(22)
  168. }
  169. let baseInfo = buildBaseInfoView()
  170. container.addSubview(baseInfo)
  171. baseInfo.snp.makeConstraints { make in
  172. make.centerY.equalToSuperview().multipliedBy(0.8)
  173. make.horizontalEdges.equalToSuperview().inset(22)
  174. }
  175. nextButton.setBackgroundImage(.primary_8, for: .normal)
  176. nextButton.setTitle(.init(key: "A00101"), for: .normal)
  177. nextButton.setTitleColor(.text_1, for: .normal)
  178. nextButton.titleLabel?.font = .heading_h3
  179. nextButton.layer.cornerRadius = 23.5
  180. nextButton.clipsToBounds = true
  181. nextButton.isEnabled = false
  182. nextButton.addAction(UIAction(handler: { [weak self] _ in
  183. guard let self else { return }
  184. updateConfig.nickName = nameInputField.text ?? ""
  185. // updateConfig.birthday = curDate.formattedFullDate("-", normal: true)
  186. updateConfig.avatar = avatar.imageUrl ?? ""
  187. view.pushToInterestSetup(updateConfig)
  188. }), for: .touchUpInside)
  189. container.addSubview(nextButton)
  190. nextButton.snp.makeConstraints { make in
  191. make.horizontalEdges.equalToSuperview().inset(16)
  192. make.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottom).offset(-4)
  193. make.height.equalTo(47)
  194. }
  195. }
  196. private func buildTitles() -> UIView {
  197. let stackView = UIStackView()
  198. stackView.axis = .vertical
  199. stackView.spacing = 6
  200. let mainLabel = UILabel()
  201. mainLabel.text = .init(key: "A00102")
  202. mainLabel.font = .init(name: UIFont.boldFontName, size: 40)
  203. mainLabel.textColor = .text_5
  204. mainLabel.numberOfLines = 0
  205. stackView.addArrangedSubview(mainLabel)
  206. let subTitleLabel = UILabel()
  207. subTitleLabel.text = .init(key: "A00103")
  208. subTitleLabel.font = .heading_h1
  209. subTitleLabel.numberOfLines = 0
  210. stackView.addArrangedSubview(subTitleLabel)
  211. return stackView
  212. }
  213. private func buildBaseInfoView() -> UIView {
  214. let container = UIView()
  215. let avatar = buildAvatar()
  216. container.addSubview(avatar)
  217. avatar.snp.makeConstraints { make in
  218. make.centerX.equalToSuperview()
  219. make.top.equalToSuperview()
  220. }
  221. let name = buildNickName()
  222. container.addSubview(name)
  223. name.snp.makeConstraints { make in
  224. make.horizontalEdges.equalToSuperview()
  225. make.top.equalTo(avatar.snp.bottom).offset(16)
  226. make.bottom.equalToSuperview()
  227. }
  228. // let birthDay = buildBirthDay()
  229. // container.addSubview(birthDay)
  230. // birthDay.snp.makeConstraints { make in
  231. // make.horizontalEdges.equalToSuperview()
  232. // make.top.equalTo(name.snp.bottom).offset(22)
  233. // make.bottom.equalToSuperview()
  234. // }
  235. return container
  236. }
  237. private func buildAvatar() -> UIView {
  238. let container = UIView()
  239. avatar.uploadType = .avatar
  240. avatar.image = .icProfileLoginAvatar
  241. avatar.layer.borderColor = UIColor.fill.cgColor
  242. avatar.layer.borderWidth = 2
  243. avatar.layer.cornerRadius = 60
  244. avatar.clipsToBounds = true
  245. avatar.delegate = self
  246. avatar.onTap { [weak self] in
  247. guard let self else { return }
  248. changeAvatar()
  249. }
  250. container.addSubview(avatar)
  251. avatar.snp.makeConstraints { make in
  252. make.width.height.equalTo(120)
  253. make.edges.equalToSuperview()
  254. }
  255. let editAvatar = UIButton()
  256. editAvatar.setImage(.icLogonAvatarEdit, for: .normal)
  257. editAvatar.addAction(UIAction(handler: { [weak self] _ in
  258. guard let self else { return }
  259. changeAvatar()
  260. }), for: .touchUpInside)
  261. container.addSubview(editAvatar)
  262. editAvatar.snp.makeConstraints { make in
  263. make.trailing.bottom.equalToSuperview()
  264. }
  265. return container
  266. }
  267. private func buildNickName() -> UIView {
  268. let container = UIView()
  269. container.backgroundColor = .fill
  270. container.layer.cornerRadius = 26
  271. container.snp.makeConstraints { make in
  272. make.height.equalTo(52)
  273. }
  274. randomView.isHidden = true
  275. container.addSubview(randomView)
  276. randomView.snp.makeConstraints { make in
  277. make.verticalEdges.equalToSuperview()
  278. make.trailing.equalToSuperview().offset(-16)
  279. }
  280. let config = UIImage.SymbolConfiguration(pointSize: 16)
  281. let refresh = UIButton()
  282. refresh.setImage(.init(systemName: "arrow.clockwise", withConfiguration: config), for: .normal)
  283. refresh.tintColor = .text_3
  284. refresh.addAction(UIAction(handler: { [weak self] _ in
  285. guard let self else { return }
  286. guard let randomProfile else { return }
  287. nameInputField.text = randomProfile.nicknames.randomElement()
  288. checkNextButtonEnable()
  289. }), for: .touchUpInside)
  290. randomView.addSubview(refresh)
  291. refresh.snp.makeConstraints { make in
  292. make.centerY.equalToSuperview()
  293. make.trailing.equalToSuperview()
  294. make.width.equalTo(17)
  295. }
  296. let random = UILabel()
  297. random.text = .init(key: "A00104")
  298. random.font = .body_m
  299. random.textColor = .text_3
  300. random.setContentHuggingPriority(.defaultHigh, for: .horizontal)
  301. random.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
  302. randomView.addSubview(random)
  303. random.snp.makeConstraints { make in
  304. make.centerY.equalToSuperview()
  305. make.trailing.equalTo(refresh.snp.leading).offset(-6)
  306. make.leading.equalToSuperview()
  307. }
  308. nameInputField.textColor = .text_5
  309. nameInputField.font = .heading_h3
  310. nameInputField.attributedPlaceholder = .init(
  311. string: .init(key: "A00105"),
  312. attributes: [
  313. .font: UIFont.body_l,
  314. .foregroundColor: UIColor.text_2
  315. ]
  316. )
  317. nameInputField.addAction(UIAction(handler: { [weak self] _ in
  318. guard let self else { return }
  319. checkNextButtonEnable()
  320. }), for: .editingChanged)
  321. container.addSubview(nameInputField)
  322. nameInputField.snp.makeConstraints { make in
  323. make.verticalEdges.equalToSuperview()
  324. make.leading.equalToSuperview().offset(16)
  325. make.trailing.equalTo(randomView.snp.leading).offset(-6)
  326. }
  327. return container
  328. }
  329. // private func buildBirthDay() -> UIView {
  330. // let container = UIView()
  331. // container.backgroundColor = .fill
  332. // container.layer.cornerRadius = 26
  333. // container.snp.makeConstraints { make in
  334. // make.height.equalTo(52)
  335. // }
  336. // container.onTap { [weak self] in
  337. // guard let self else { return }
  338. // let panel = LNBirthdayDatePickerPanel()
  339. // panel.setDefault(curDate)
  340. // panel.handler = { [weak self] date in
  341. // guard let self else { return }
  342. // curDate = date
  343. // }
  344. // panel.showIn(view)
  345. // }
  346. //
  347. // let config = UIImage.SymbolConfiguration(pointSize: 6)
  348. // let arrow = UIImageView()
  349. // arrow.image = .init(systemName: "arrowtriangle.down.fill", withConfiguration: config)
  350. // arrow.tintColor = .text_5
  351. // container.addSubview(arrow)
  352. // arrow.snp.makeConstraints { make in
  353. // make.centerY.equalToSuperview()
  354. // make.trailing.equalToSuperview().offset(-16)
  355. // }
  356. //
  357. // birthDayLabel.text = curDate.formattedFullDate("-")
  358. // birthDayLabel.font = .body_l
  359. // birthDayLabel.textColor = .text_5
  360. // container.addSubview(birthDayLabel)
  361. // birthDayLabel.snp.makeConstraints { make in
  362. // make.centerY.equalToSuperview()
  363. // make.leading.equalToSuperview().offset(16)
  364. // }
  365. //
  366. // return container
  367. // }
  368. }
  369. #if DEBUG
  370. import SwiftUI
  371. struct LNBaseInfoSetupViewControllerPreview: UIViewControllerRepresentable {
  372. func makeUIViewController(context: Context) -> some UIViewController {
  373. LNBaseInfoSetupViewController(config: LNProfileUpdateConfig())
  374. }
  375. func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
  376. }
  377. }
  378. #Preview(body: {
  379. LNBaseInfoSetupViewControllerPreview()
  380. })
  381. #endif