TSPTPInputVC.swift 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558
  1. //
  2. // TSPTPInputVC.swift
  3. // AIEmoji
  4. //
  5. // Created by 100Years on 2025/4/7.
  6. //
  7. import PhotosUI
  8. class TSPTPInputVC: TSBaseVC {
  9. lazy var viewModel: TSPTPInputVM = {
  10. let viewModel = TSPTPInputVM()
  11. viewModel.isCanGennerateBlock = { [weak self] _ in
  12. guard let self = self else { return }
  13. setCreatBtnEnabled()
  14. }
  15. return viewModel
  16. }()
  17. lazy var photoPickerManager: TSPhotoPickerManager = {
  18. let photoPickerManager = TSPhotoPickerManager(viewController: self)
  19. return photoPickerManager
  20. }()
  21. private var keyboardHeight: CGFloat = 0
  22. var hintBaseVC = TSAIListHintBaseVC(config: .defaultConfig)
  23. // ###################################### 导航栏 view ######################################
  24. lazy var vipBtn: UIButton = {
  25. let vipBtn = UIButton.createButton(image: UIImage(named: "nav_vip")) { [weak self] in
  26. guard let self = self else { return }
  27. TSPurchaseVC.show(target: self) {}
  28. }
  29. return vipBtn
  30. }()
  31. lazy var navBarView: TSBaseNavContentBarView = {
  32. let navBarView = TSBaseNavContentBarView()
  33. let titleImageView = UIImageView.createImageView(imageName: "nav_title_pic", contentMode: .scaleToFill)
  34. navBarView.barView.addSubview(titleImageView)
  35. titleImageView.snp.makeConstraints { make in
  36. make.centerY.equalToSuperview()
  37. make.left.equalTo(16)
  38. }
  39. navBarView.barView.addSubview(vipBtn)
  40. vipBtn.snp.makeConstraints { make in
  41. make.centerY.equalToSuperview()
  42. make.trailing.equalTo(-16) // (-60)
  43. make.width.height.equalTo(24)
  44. }
  45. return navBarView
  46. }()
  47. // ###################################### cusStackView ######################################
  48. lazy var cusStackView: TSCustomStackView = {
  49. let cusStackView = TSCustomStackView(axis: .vertical, spacing: 0)
  50. cusStackView.scrollView.isScrollEnabled = true
  51. return cusStackView
  52. }()
  53. // ###################################### 上传图片 ######################################
  54. lazy var uploadView: TSPTPUploadView = {
  55. let uploadView = TSPTPUploadView()
  56. uploadView.clickHandel = { [weak self] index in
  57. guard let self = self else { return }
  58. if index == 0 { // 删除
  59. viewModel.upLoadImage = nil
  60. uploadView.upLoadImage = nil
  61. } else { // 添加
  62. if TSAIListHintBaseVC.isShowUploadImageHint {
  63. TSAIListHintBaseVC.isShowUploadImageHint = false
  64. presentModalHintVC()
  65. } else {
  66. pickSinglePhoto()
  67. }
  68. }
  69. }
  70. return uploadView
  71. }()
  72. func pickSinglePhoto() {
  73. photoPickerManager.pickCustomSinglePhoto { [weak self] image, errorString in
  74. guard let self = self else { return }
  75. if let errorString = errorString {
  76. TSToastShared.showToast(text: errorString)
  77. } else {
  78. viewModel.upLoadImage = image
  79. uploadView.upLoadImage = image
  80. }
  81. kDelayMainShort {
  82. self.photoPickerManager.dismissPageVC()
  83. }
  84. }
  85. }
  86. // ###################################### 选择风格 ######################################
  87. lazy var selectStyleView: TSPTPSelectStyleView = {
  88. let selectStyleView = TSPTPSelectStyleView()
  89. selectStyleView.currentIndexPath = IndexPath(item: viewModel.selectedStyleIndex, section: 0)
  90. selectStyleView.dataArray = viewModel.ptpStyleModels
  91. selectStyleView.clickHandle = { [weak self] indexPath, model in
  92. guard let self = self else { return }
  93. viewModel.selectedPTPStyleModel = model
  94. viewModel.selectedStyleIndex = indexPath.item
  95. updateVipView()
  96. updateTextFiledView()
  97. }
  98. return selectStyleView
  99. }()
  100. // ###################################### 输入框 ######################################
  101. lazy var customTextView: TSPlaceholderTextView = {
  102. let customTextView = TSPlaceholderTextView(
  103. placeholder: "Describe how you want to transform".localized,
  104. text: "",
  105. font: .font(size: 14),
  106. textColor: .white,
  107. backgroundColor: .clear
  108. )
  109. customTextView.delegate = self
  110. customTextView.returnKeyType = .done
  111. return customTextView
  112. }()
  113. lazy var clearBtn: TSUIExpandedTouchButton = {
  114. let clearBtn = TSUIExpandedTouchButton()
  115. clearBtn.setUpButton(
  116. image: UIImage(named: "clear_text")
  117. ) { [weak self] in
  118. guard let self = self else { return }
  119. customTextView.text = ""
  120. textViewDidChange(customTextView)
  121. }
  122. clearBtn.isHidden = true
  123. return clearBtn
  124. }()
  125. var promptTextViewH: CGFloat = 96.0
  126. lazy var promptTextView: UIView = {
  127. let promptTextView = UIView()
  128. promptTextView.isHidden = true
  129. promptTextView.clipsToBounds = true
  130. let bgView = UIView()
  131. bgView.backgroundColor = "#333333".uiColor
  132. bgView.cornerRadius = 16.0
  133. promptTextView.addSubview(bgView)
  134. bgView.snp.makeConstraints { make in
  135. make.edges.equalTo(UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16))
  136. }
  137. bgView.addSubview(customTextView)
  138. bgView.addSubview(clearBtn)
  139. customTextView.snp.makeConstraints { make in
  140. make.centerY.equalToSuperview()
  141. make.leading.equalTo(12.0)
  142. make.top.equalTo(12.0)
  143. make.bottom.equalTo(-12.0)
  144. make.trailing.equalTo(-20)
  145. }
  146. clearBtn.snp.makeConstraints { make in
  147. make.width.height.equalTo(16.0)
  148. make.trailing.equalTo(-12)
  149. make.bottom.equalTo(-12.0)
  150. }
  151. return promptTextView
  152. }()
  153. // ###################################### 集合视图 ######################################
  154. private var collectionViewObserver: CollectionViewObserver!
  155. let collectionViewBtootm: CGFloat = 80
  156. lazy var collectionComponent: TSCollectionViewComponent = {
  157. let layout = UICollectionViewFlowLayout()
  158. let cp = TSCollectionViewComponent(frame: CGRect.zero, layout: layout, attributes: [:])
  159. cp.collectionView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: collectionViewBtootm, right: 0)
  160. cp.collectionView.isScrollEnabled = false
  161. // 禁用自动 contentInset 调整
  162. if #available(iOS 11.0, *) {
  163. cp.collectionView.contentInsetAdjustmentBehavior = .never
  164. } else {
  165. automaticallyAdjustsScrollViewInsets = false
  166. }
  167. cp.sectionActionHandler = { [weak self] cellCp, _ in
  168. guard let self = self else { return }
  169. if let cmd = cellCp as? String {
  170. if cmd == "delete" {
  171. showCustomAlert(message: "Are you sure to delete".localized, deleteHandler: {
  172. self.viewModel.removeAllHistoryList()
  173. self.updataCollectionView()
  174. })
  175. } else if cmd == "more" {
  176. let historyVC = TSPTPHistoryVC(style: .ptp)
  177. kPushVC(target: self, modelVC: historyVC)
  178. }
  179. }
  180. }
  181. cp.itemActionHandler = { [weak self] object, indexPath in
  182. guard let self = self else { return }
  183. // 删除过期的任务
  184. if let cmd = object as? String, cmd == "delete_task_expired" {
  185. if let sections = viewModel.colDataArray.safeObj(At: indexPath.section) as? TSGenmojiCoLSectionModel,
  186. let currentActionInfoModel = sections.items.safeObj(At: indexPath.item) {
  187. TSRMShared.ptpDBHistory.deleteListModel(id: currentActionInfoModel.dataModel.id)
  188. updataCollectionView()
  189. }
  190. }
  191. }
  192. cp.itemDidSelectedHandler = { [weak self] _, indexPath in
  193. guard let self = self else { return }
  194. if let sections = viewModel.colDataArray.safeObj(At: indexPath.section) as? TSGenmojiCoLSectionModel,
  195. let dataModel = sections.items.safeObj(At: indexPath.item)?.dataModel {
  196. var dataModelArray: [TSActionInfoModel] = []
  197. for itemModel in sections.items {
  198. if itemModel.dataModel.status == "success" || itemModel.dataModel.modelType == .example {
  199. dataModelArray.append(itemModel.dataModel)
  200. }
  201. }
  202. let browseVC = TSAIPhotoBrowseVC()
  203. browseVC.dataModelArray = dataModelArray
  204. browseVC.currentIndex = dataModelArray.firstIndex(of: dataModel) ?? 0
  205. browseVC.deleteComplete = { [weak self] deleteModel in
  206. guard let self = self else { return }
  207. TSRMShared.ptpDBHistory.deleteListModel(id: deleteModel.id)
  208. updataCollectionView()
  209. }
  210. kPresentModalVC(target: self, modelVC: browseVC,transitionStyle: .crossDissolve)
  211. }
  212. }
  213. cp.collectionView.keyboardDismissMode = .interactive
  214. return cp
  215. }()
  216. // ###################################### Button ######################################
  217. lazy var creatBtnView: TSAppBtnView = {
  218. let creatBtnView = TSAppBtnView()
  219. creatBtnView.setUpButton(style: .generate, vipFreeNumType: .picToPic) { [weak self] in
  220. guard let self = self else { return }
  221. generateImage()
  222. }
  223. creatBtnView.setBtnEnabled(isEnabled: false)
  224. creatBtnView.isIconVipBlock = { [weak self] in
  225. guard let self = self else { return false }
  226. var showVip = kPurchaseDefault.generateVipShow(type: .picToPic)
  227. if showVip == false {
  228. showVip = self.viewModel.selectedPTPStyleModel.isVip
  229. }
  230. return showVip
  231. }
  232. creatBtnView.isClickVipBlock = { [weak self] in
  233. guard let self = self else { return false }
  234. var isVip = kPurchaseDefault.freeNumAvailable(type: .picToPic) == false
  235. if viewModel.selectedPTPStyleModel.isVip == true {
  236. isVip = true
  237. }
  238. return isVip
  239. }
  240. return creatBtnView
  241. }()
  242. override func createView() {
  243. let tapGesture = UITapGestureRecognizer(target: self, action: #selector(clickView))
  244. tapGesture.cancelsTouchesInView = false
  245. view.addGestureRecognizer(tapGesture)
  246. navBarContentView.addSubview(navBarView)
  247. navBarView.snp.makeConstraints { make in
  248. make.edges.equalToSuperview()
  249. }
  250. contentView.addSubview(cusStackView)
  251. cusStackView.snp.makeConstraints { make in
  252. make.edges.equalToSuperview()
  253. }
  254. contentView.addSubview(creatBtnView)
  255. creatBtnView.snp.makeConstraints { make in
  256. make.bottom.equalTo(-16)
  257. make.leading.equalTo(16)
  258. make.trailing.equalTo(-16)
  259. make.height.equalTo(48)
  260. }
  261. setUpCusStackView()
  262. }
  263. override func dealThings() {
  264. updataCollectionView()
  265. // 监听 vip 变化
  266. NotificationCenter.default.addObserver(self, selector: #selector(vipInfoChanged), name: .kPurchaseDidChanged, object: nil)
  267. updateVipView()
  268. TSAIListHintBaseVC.userDefaultsKey = "isFirstUploadImagePTP"
  269. // 监听键盘事件
  270. NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
  271. NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil)
  272. // 监听collectionView 的 contentSize
  273. collectionViewObserver = CollectionViewObserver(collectionView: collectionComponent.collectionView)
  274. collectionViewObserver.onContentSizeChange = { [weak self] size in
  275. guard let self = self else { return }
  276. print("collectionViewObserver 内容大小变化: \(size)")
  277. self.collectionComponent.collectionView.snp.updateConstraints { make in
  278. make.height.equalTo(size.height)
  279. }
  280. }
  281. // 监听按钮任务生成中
  282. NotificationCenter.default.addObserver(forName: .kBaseOperationQueueCountChanged, object: nil, queue: nil) { [weak self] _ in
  283. guard let self = self else { return }
  284. setCreatBtnEnabled()
  285. }
  286. // 后台生成 UI 任务,刷新 UI界面
  287. NotificationCenter.default.addObserver(forName: .kGeneratePTPOperationChanged, object: nil, queue: nil) { [weak self] notification in
  288. guard let self = self else { return }
  289. if let userInfo = notification.userInfo as? [String: Any], let state = userInfo["state"] as? TSProgressState {
  290. dePrint("TSBaseOperation stateDatauPblished 收到 = \(state)")
  291. // if state.isResult {//有结果,一定要刷新
  292. // updataCollectionView()
  293. // }else if self.isViewVisible == false {
  294. //// dePrint("TSBaseOperation 视图不可见")
  295. // return
  296. // }else
  297. // if state.reloadNewData {//主要是给pending用,让他再视图中有个位置占着
  298. updataCollectionView()
  299. // }
  300. }
  301. }
  302. // 同时 VC主动刷新UI界面
  303. NotificationCenter.default.addObserver(forName: .kPTPDataChanged, object: nil, queue: nil) { [weak self] _ in
  304. guard let self = self else { return }
  305. DispatchQueue.main.async {
  306. debugPrint("kPTPDataChanged listModels.first=\(self.viewModel.listModels.first?.toJSONString())")
  307. self.updataCollectionView()
  308. }
  309. }
  310. NotificationCenter.default.addObserver(self, selector: #selector(autoSelectImageToImageStyle(notify:)), name: .schemeImageToImageStylePick, object: nil)
  311. }
  312. func updataCollectionView() {
  313. viewModel.updateRecentData()
  314. collectionComponent.clear()
  315. collectionComponent.reloadView(with: viewModel.colDataArray)
  316. }
  317. }
  318. extension TSPTPInputVC {
  319. func presentModalHintVC() {
  320. hintBaseVC = TSAIListHintBaseVC(config: .defaultConfig) { [weak self] image in
  321. guard let self = self else { return }
  322. viewModel.upLoadImage = image
  323. uploadView.upLoadImage = image
  324. hintBaseVC.dismissPageVC()
  325. }
  326. kPresentModalVC(target: self, modelVC: hintBaseVC, transitionStyle: .crossDissolve)
  327. }
  328. func setUpCusStackView() {
  329. let uploadPhotoTitleView = TSTitleView.creatTitleView(title: "Upload Photo".localized, subTitle: "")
  330. cusStackView.addSubviewToStack(uploadPhotoTitleView)
  331. uploadPhotoTitleView.snp.makeConstraints { make in
  332. make.height.equalTo(uploadPhotoTitleView.viewH)
  333. make.width.equalTo(k_ScreenWidth)
  334. }
  335. let hintBtn = TSUIExpandedTouchButton()
  336. hintBtn.setUpButton(image: UIImage(named: "ptp_hint")) { [weak self] in
  337. guard let self = self else { return }
  338. presentModalHintVC()
  339. }
  340. uploadPhotoTitleView.contentView.addSubview(hintBtn)
  341. hintBtn.snp.makeConstraints { make in
  342. make.centerY.equalToSuperview().offset(kSectionTitleViewCenterYOffset)
  343. make.trailing.equalTo(-16)
  344. make.width.height.equalTo(16)
  345. }
  346. cusStackView.addSubviewToStack(uploadView)
  347. uploadView.snp.makeConstraints { make in
  348. make.height.equalTo(uploadView.viewH)
  349. make.width.equalTo(k_ScreenWidth)
  350. }
  351. let selectStyleTitleView = TSTitleMoreView()
  352. selectStyleTitleView.setTitle(title: "Select Style".localized) { [weak self] in
  353. guard let self = self else { return }
  354. let selectStyleVC = TSGennertatorSelectStyleVC()
  355. selectStyleVC.currentIndexPath = IndexPath(item: viewModel.selectedStyleIndex, section: 0)
  356. selectStyleVC.dataArray = viewModel.ptpStyleModels
  357. selectStyleVC.selectedValueBlock = { [weak self] indexPath, _ in
  358. guard let self = self else { return }
  359. selectStyleView.collectionView(selectStyleView.styleCollectionView, didSelectItemAt: indexPath)
  360. }
  361. kPresentModalVC(target: self, modelVC: selectStyleVC, transitionStyle: .coverVertical)
  362. }
  363. cusStackView.addSubviewToStack(selectStyleTitleView)
  364. selectStyleTitleView.snp.makeConstraints { make in
  365. make.height.equalTo(selectStyleTitleView.viewH)
  366. make.width.equalTo(k_ScreenWidth)
  367. }
  368. cusStackView.addSubviewToStack(selectStyleView)
  369. selectStyleView.snp.makeConstraints { make in
  370. make.height.equalTo(selectStyleView.viewH)
  371. make.width.equalTo(k_ScreenWidth)
  372. }
  373. cusStackView.addSubviewToStack(promptTextView, length: promptTextViewH)
  374. promptTextView.isHidden = !viewModel.selectedPTPStyleModel.input
  375. cusStackView.addSubviewToStack(collectionComponent.collectionView)
  376. collectionComponent.collectionView.snp.makeConstraints { make in
  377. make.height.equalTo(0)
  378. make.width.equalTo(k_ScreenWidth)
  379. }
  380. cusStackView.addSpacing(length: collectionViewBtootm)
  381. }
  382. }
  383. extension TSPTPInputVC: UITextViewDelegate {
  384. // 实现 UITextViewDelegate 协议方法,控制 return 键行为
  385. func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
  386. if text == "\n" {
  387. // 当输入为换行符(即按下 return 键)时,执行相应操作
  388. // sendBolck?(textView.text)
  389. clickView()
  390. return false
  391. }
  392. return true
  393. }
  394. func textViewDidChange(_ textView: UITextView) {
  395. clearBtn.isHidden = textView.text.count <= 0
  396. viewModel.selectedPTPStyleModel.inputText = textView.text
  397. viewModel.gennerateChange()
  398. }
  399. }
  400. extension TSPTPInputVC {
  401. @objc func vipInfoChanged() {
  402. kExecuteOnMainThread {
  403. self.updateVipView()
  404. }
  405. }
  406. func updateVipView() {
  407. kExecuteOnMainThread {
  408. self.vipBtn.isHidden = PurchaseManager.default.isVip
  409. self.creatBtnView.updateVipView()
  410. }
  411. }
  412. func getVipText() -> String {
  413. return "Generate".localized
  414. }
  415. }
  416. extension TSPTPInputVC {
  417. @objc func clickView() {
  418. view.endEditing(true)
  419. }
  420. func generateImage() {
  421. viewModel.selectedPTPStyleModel.upLoadImage = viewModel.upLoadImage
  422. viewModel.selectedPTPStyleModel.upLoadImageUrl = nil
  423. let gennerateVC = TSPTPGeneratorVC(generateStyleModel: viewModel.selectedPTPStyleModel) { [weak self] _ in
  424. guard let self = self else { return }
  425. updateVipView()
  426. }
  427. gennerateVC.reloadViewBlock = { [weak self] in
  428. guard let self = self else { return }
  429. updataCollectionView()
  430. }
  431. gennerateVC.closePageComplete = {
  432. [weak self] in
  433. guard let self = self else { return }
  434. updataCollectionView()
  435. }
  436. kPresentModalVC(target: self, modelVC: gennerateVC, transitionStyle: .crossDissolve)
  437. }
  438. func setCreatBtnEnabled() {
  439. let isAvailability = TSGeneratePTPOperationQueue.shared.isAvailability
  440. if viewModel.isCanGennerate, isAvailability {
  441. creatBtnView.setBtnEnabled(isEnabled: true)
  442. creatBtnView.loading = false
  443. dePrint("TSTextGeneralPicVC setCreatBtnEnabled false")
  444. } else {
  445. dePrint("TSTextGeneralPicVC setCreatBtnEnabled = \(isAvailability)")
  446. creatBtnView.setBtnEnabled(isEnabled: false)
  447. creatBtnView.loading = !isAvailability
  448. }
  449. updateVipView()
  450. }
  451. }
  452. extension TSPTPInputVC {
  453. @objc func keyboardWillShow(_ notification: Notification) {
  454. guard let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { return }
  455. let scrollView = cusStackView.scrollView
  456. let keyboardHeight = keyboardFrame.height - view.safeAreaInsets.bottom
  457. let textViewFrame = scrollView.convert(customTextView.frame, from: customTextView.superview)
  458. let scrollDistance = textViewFrame.maxY - (scrollView.bounds.height - keyboardHeight)
  459. let y = scrollDistance
  460. scrollView.setContentOffset(CGPoint(x: 0, y: y), animated: true)
  461. }
  462. // MARK: - 键盘隐藏时恢复
  463. @objc private func keyboardWillHide(_ notification: Notification) {
  464. cusStackView.scrollView.contentOffset = CGPoint(x: 0, y: 0)
  465. }
  466. }
  467. extension TSPTPInputVC {
  468. func updateTextFiledView() {
  469. if viewModel.selectedPTPStyleModel.input {
  470. promptTextView.isHidden = false
  471. } else {
  472. promptTextView.isHidden = true
  473. }
  474. UIView.animate(withDuration: 0.3) {
  475. self.cusStackView.layoutIfNeeded()
  476. }
  477. }
  478. }
  479. extension TSPTPInputVC {
  480. @objc func autoSelectImageToImageStyle(notify: Notification) {
  481. if let objc = notify.userInfo as? [String: String] {
  482. WindowHelper.resetToTabBarController(at: 0)
  483. let styleId = objc.safeString(forKey: "styleId")
  484. selectStyleView.selectStyle(styleId: styleId)
  485. }
  486. }
  487. }