TSPTPInputVC.swift 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584
  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 selectStyleViewLayout: UICollectionViewFlowLayout = {
  88. let layout = UICollectionViewFlowLayout()
  89. layout.scrollDirection = .horizontal
  90. let w = (k_ScreenWidth - 32.0 - 30.0 - 2.0) / 4.0
  91. layout.itemSize = CGSize(width: w, height: 108.0)
  92. layout.minimumInteritemSpacing = 0.0
  93. layout.minimumLineSpacing = 10.0
  94. layout.sectionInset = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16)
  95. return layout
  96. }()
  97. ///风格视图
  98. lazy var selectVc: TSGennertatorSelectStyleListVc = {
  99. let selectStyleVC = TSGennertatorSelectStyleListVc()
  100. selectStyleVC.viewModel.currentIndexPath = IndexPath(item: viewModel.selectedStyleIndex, section: 0)
  101. selectStyleVC.viewModel.layout = selectStyleViewLayout
  102. selectStyleVC.selectedValueBlock = { [weak self] indexPath, model in
  103. guard let self = self else { return }
  104. viewModel.selectedPTPStyleModel = model
  105. viewModel.selectedStyleIndex = indexPath.item
  106. updateVipView()
  107. updateTextFiledView()
  108. }
  109. return selectStyleVC
  110. }()
  111. // ###################################### 输入框 ######################################
  112. lazy var customTextView: TSPlaceholderTextView = {
  113. let customTextView = TSPlaceholderTextView(
  114. placeholder: "Describe how you want to transform".localized,
  115. text: "",
  116. font: .font(size: 14),
  117. textColor: .white,
  118. backgroundColor: .clear
  119. )
  120. customTextView.delegate = self
  121. customTextView.returnKeyType = .done
  122. return customTextView
  123. }()
  124. lazy var clearBtn: TSUIExpandedTouchButton = {
  125. let clearBtn = TSUIExpandedTouchButton()
  126. clearBtn.setUpButton(
  127. image: UIImage(named: "clear_text")
  128. ) { [weak self] in
  129. guard let self = self else { return }
  130. customTextView.text = ""
  131. textViewDidChange(customTextView)
  132. }
  133. clearBtn.isHidden = true
  134. return clearBtn
  135. }()
  136. var promptTextViewH: CGFloat = 96.0
  137. lazy var promptTextView: UIView = {
  138. let promptTextView = UIView()
  139. promptTextView.isHidden = true
  140. promptTextView.clipsToBounds = true
  141. let bgView = UIView()
  142. bgView.backgroundColor = "#333333".uiColor
  143. bgView.cornerRadius = 16.0
  144. promptTextView.addSubview(bgView)
  145. bgView.snp.makeConstraints { make in
  146. make.edges.equalTo(UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16))
  147. }
  148. bgView.addSubview(customTextView)
  149. bgView.addSubview(clearBtn)
  150. customTextView.snp.makeConstraints { make in
  151. make.centerY.equalToSuperview()
  152. make.leading.equalTo(12.0)
  153. make.top.equalTo(12.0)
  154. make.bottom.equalTo(-12.0)
  155. make.trailing.equalTo(-20)
  156. }
  157. clearBtn.snp.makeConstraints { make in
  158. make.width.height.equalTo(16.0)
  159. make.trailing.equalTo(-12)
  160. make.bottom.equalTo(-12.0)
  161. }
  162. return promptTextView
  163. }()
  164. // ###################################### 集合视图 ######################################
  165. private var collectionViewObserver: CollectionViewObserver!
  166. let collectionViewBtootm: CGFloat = 80
  167. lazy var collectionComponent: TSCollectionViewComponent = {
  168. let layout = UICollectionViewFlowLayout()
  169. let cp = TSCollectionViewComponent(frame: CGRect.zero, layout: layout, attributes: [:])
  170. cp.collectionView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: collectionViewBtootm, right: 0)
  171. cp.collectionView.isScrollEnabled = false
  172. // 禁用自动 contentInset 调整
  173. if #available(iOS 11.0, *) {
  174. cp.collectionView.contentInsetAdjustmentBehavior = .never
  175. } else {
  176. automaticallyAdjustsScrollViewInsets = false
  177. }
  178. cp.sectionActionHandler = { [weak self] cellCp, _ in
  179. guard let self = self else { return }
  180. if let cmd = cellCp as? String {
  181. if cmd == "delete" {
  182. showCustomAlert(message: "Are you sure to delete".localized, deleteHandler: {
  183. self.viewModel.removeAllHistoryList()
  184. self.updataCollectionView()
  185. })
  186. } else if cmd == "more" {
  187. let historyVC = TSPTPHistoryVC()
  188. kPushVC(target: self, modelVC: historyVC)
  189. }
  190. }
  191. }
  192. cp.itemActionHandler = { [weak self] object, indexPath in
  193. guard let self = self else { return }
  194. // 删除过期的任务
  195. if let cmd = object as? String, cmd == "delete_task_expired" {
  196. if let sections = viewModel.colDataArray.safeObj(At: indexPath.section) as? TSGenmojiCoLSectionModel,
  197. let currentActionInfoModel = sections.items.safeObj(At: indexPath.item) {
  198. TSRMShared.ptpDBHistory.deleteListModel(id: currentActionInfoModel.dataModel.id)
  199. updataCollectionView()
  200. }
  201. }
  202. }
  203. cp.itemDidSelectedHandler = { [weak self] _, indexPath in
  204. guard let self = self else { return }
  205. if let sections = viewModel.colDataArray.safeObj(At: indexPath.section) as? TSGenmojiCoLSectionModel,
  206. let dataModel = sections.items.safeObj(At: indexPath.item)?.dataModel {
  207. var dataModelArray: [TSActionInfoModel] = []
  208. for itemModel in sections.items {
  209. if itemModel.dataModel.status == "success" || itemModel.dataModel.modelType == .example {
  210. dataModelArray.append(itemModel.dataModel)
  211. }
  212. }
  213. let browseVC = TSAIPhotoBrowseVC()
  214. browseVC.dataModelArray = dataModelArray
  215. browseVC.currentIndex = dataModelArray.firstIndex(of: dataModel) ?? 0
  216. kPresentModalVC(target: self, modelVC: browseVC, transitionStyle: .crossDissolve)
  217. }
  218. }
  219. cp.collectionView.keyboardDismissMode = .interactive
  220. return cp
  221. }()
  222. // ###################################### Button ######################################
  223. lazy var creatBtnView: TSAppBtnView = {
  224. let creatBtnView = TSAppBtnView()
  225. creatBtnView.setUpButton(style: .generate, vipFreeNumType: .picToPic) { [weak self] in
  226. guard let self = self else { return }
  227. generateCheck()
  228. }
  229. creatBtnView.setBtnEnabled(isEnabled: false)
  230. creatBtnView.isIconVipBlock = { [weak self] in
  231. guard let self = self else { return false }
  232. var showVip = kPurchaseDefault.generateVipShow(type: .picToPic)
  233. if showVip == false {
  234. showVip = self.viewModel.selectedPTPStyleModel.vip
  235. }
  236. return showVip
  237. }
  238. creatBtnView.isClickVipBlock = { [weak self] in
  239. guard let self = self else { return false }
  240. var isVip = kPurchaseDefault.freeNumAvailable(type: .picToPic) == false
  241. if viewModel.selectedPTPStyleModel.vip == true {
  242. isVip = true
  243. }
  244. return isVip
  245. }
  246. return creatBtnView
  247. }()
  248. override func viewDidLoad() {
  249. super.viewDidLoad()
  250. }
  251. override func createView() {
  252. let tapGesture = UITapGestureRecognizer(target: self, action: #selector(clickView))
  253. tapGesture.cancelsTouchesInView = false
  254. view.addGestureRecognizer(tapGesture)
  255. navBarContentView.addSubview(navBarView)
  256. navBarView.snp.makeConstraints { make in
  257. make.edges.equalToSuperview()
  258. }
  259. contentView.addSubview(cusStackView)
  260. cusStackView.snp.makeConstraints { make in
  261. make.edges.equalToSuperview()
  262. }
  263. contentView.addSubview(creatBtnView)
  264. creatBtnView.snp.makeConstraints { make in
  265. make.bottom.equalTo(-16)
  266. make.leading.equalTo(16)
  267. make.trailing.equalTo(-16)
  268. make.height.equalTo(48)
  269. }
  270. setUpCusStackView()
  271. }
  272. override func dealThings() {
  273. updataCollectionView()
  274. // 监听 vip 变化
  275. NotificationCenter.default.addObserver(self, selector: #selector(vipInfoChanged), name: .kPurchaseDidChanged, object: nil)
  276. updateVipView()
  277. TSAIListHintBaseVC.userDefaultsKey = "isFirstUploadImagePTP"
  278. // 监听键盘事件
  279. NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
  280. NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil)
  281. // 监听collectionView 的 contentSize
  282. collectionViewObserver = CollectionViewObserver(collectionView: collectionComponent.collectionView)
  283. collectionViewObserver.onContentSizeChange = { [weak self] size in
  284. guard let self = self else { return }
  285. print("collectionViewObserver 内容大小变化: \(size)")
  286. self.collectionComponent.collectionView.snp.updateConstraints { make in
  287. make.height.equalTo(size.height)
  288. }
  289. }
  290. // 监听按钮任务生成中
  291. NotificationCenter.default.addObserver(forName: .kBaseOperationQueueCountChanged, object: nil, queue: nil) { [weak self] _ in
  292. guard let self = self else { return }
  293. setCreatBtnEnabled()
  294. }
  295. // 后台生成 UI 任务,刷新 UI界面
  296. NotificationCenter.default.addObserver(forName: .kGeneratePTPOperationChanged, object: nil, queue: nil) { [weak self] notification in
  297. guard let self = self else { return }
  298. if let userInfo = notification.userInfo as? [String: Any], let state = userInfo["state"] as? TSProgressState {
  299. dePrint("TSBaseOperation stateDatauPblished 收到 = \(state)")
  300. // if state.isResult {//有结果,一定要刷新
  301. // updataCollectionView()
  302. // }
  303. // else if self.isViewVisible == false {
  304. //// dePrint("TSBaseOperation 视图不可见")
  305. // return
  306. // }
  307. // else if state.reloadNewData {//主要是给pending用,让他再视图中有个位置占着
  308. // updataCollectionView()
  309. // }
  310. // else if viewModel.needReloadColView {
  311. // updataCollectionView()
  312. // }
  313. if state.reloadNewData {//主要是给pending用,让他再视图中有个位置占着,再就是有结果,一定要刷新
  314. updataCollectionView()
  315. }
  316. else if self.isViewVisible == false {
  317. return
  318. }
  319. updataCollectionView()
  320. }
  321. }
  322. // 同时 VC主动刷新UI界面
  323. NotificationCenter.default.addObserver(forName: .kPTPDataChanged, object: nil, queue: nil) { [weak self] _ in
  324. guard let self = self else { return }
  325. DispatchQueue.main.async {
  326. debugPrint("kPTPDataChanged listModels.first=\(self.viewModel.listModels.first?.toJSONString())")
  327. self.updataCollectionView()
  328. }
  329. }
  330. }
  331. func updataCollectionView() {
  332. dePrint("updataCollectionView")
  333. viewModel.updateRecentData()
  334. collectionComponent.clear()
  335. collectionComponent.reloadView(with: viewModel.colDataArray)
  336. }
  337. }
  338. extension TSPTPInputVC {
  339. func presentModalHintVC() {
  340. hintBaseVC = TSAIListHintBaseVC(config: .defaultConfig) { [weak self] image in
  341. guard let self = self else { return }
  342. viewModel.upLoadImage = image
  343. uploadView.upLoadImage = image
  344. hintBaseVC.dismissPageVC()
  345. }
  346. kPresentModalVC(target: self, modelVC: hintBaseVC, transitionStyle: .crossDissolve)
  347. }
  348. func setUpCusStackView() {
  349. let uploadPhotoTitleView = TSTitleView.creatTitleView(title: "Upload Photo".localized, subTitle: "")
  350. cusStackView.addSubviewToStack(uploadPhotoTitleView)
  351. uploadPhotoTitleView.snp.makeConstraints { make in
  352. make.height.equalTo(uploadPhotoTitleView.viewH)
  353. make.width.equalTo(k_ScreenWidth)
  354. }
  355. let hintBtn = TSUIExpandedTouchButton()
  356. hintBtn.setUpButton(image: UIImage(named: "ptp_hint")) { [weak self] in
  357. guard let self = self else { return }
  358. presentModalHintVC()
  359. }
  360. uploadPhotoTitleView.contentView.addSubview(hintBtn)
  361. hintBtn.snp.makeConstraints { make in
  362. make.centerY.equalToSuperview().offset(kSectionTitleViewCenterYOffset)
  363. make.trailing.equalTo(-16)
  364. make.width.height.equalTo(16)
  365. }
  366. cusStackView.addSubviewToStack(uploadView)
  367. uploadView.snp.makeConstraints { make in
  368. make.height.equalTo(uploadView.viewH)
  369. make.width.equalTo(k_ScreenWidth)
  370. }
  371. let selectStyleTitleView = TSTitleMoreView()
  372. selectStyleTitleView.setTitle(title: "Select Style".localized) { [weak self] in
  373. guard let self = self else { return }
  374. let selectStyleVC = TSGennertatorSelectStyleVC()
  375. selectStyleVC.viewModel.currentIndexPath = IndexPath(item: viewModel.selectedStyleIndex, section: 0)
  376. selectStyleVC.viewModel.initData = self.selectVc.viewModel.datas
  377. selectStyleVC.selectedValueBlock = { [weak self] indexPath, model in
  378. guard let self = self else { return }
  379. viewModel.selectedPTPStyleModel = model
  380. self.selectVc.selectIndex(indexPath: indexPath,dir: .centeredHorizontally)
  381. self.updateVipView()
  382. self.updateTextFiledView()
  383. }
  384. kPresentModalVC(target: self, modelVC: selectStyleVC, transitionStyle: .coverVertical)
  385. }
  386. cusStackView.addSubviewToStack(selectStyleTitleView)
  387. selectStyleTitleView.snp.makeConstraints { make in
  388. make.height.equalTo(selectStyleTitleView.viewH)
  389. make.width.equalTo(k_ScreenWidth)
  390. }
  391. cusStackView.addSubviewToStack(selectVc.view)
  392. selectVc.view.snp.makeConstraints { make in
  393. make.height.equalTo(selectVc.viewModel.viewH)
  394. make.width.equalTo(k_ScreenWidth)
  395. }
  396. addChild(selectVc)
  397. selectVc.didMove(toParent: self)
  398. cusStackView.addSubviewToStack(promptTextView, length: promptTextViewH)
  399. promptTextView.isHidden = !viewModel.selectedPTPStyleModel.input
  400. cusStackView.addSubviewToStack(collectionComponent.collectionView)
  401. collectionComponent.collectionView.snp.makeConstraints { make in
  402. make.height.equalTo(0)
  403. make.width.equalTo(k_ScreenWidth)
  404. }
  405. cusStackView.addSpacing(length: collectionViewBtootm)
  406. }
  407. }
  408. extension TSPTPInputVC: UITextViewDelegate {
  409. // 实现 UITextViewDelegate 协议方法,控制 return 键行为
  410. func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
  411. if text == "\n" {
  412. // 当输入为换行符(即按下 return 键)时,执行相应操作
  413. // sendBolck?(textView.text)
  414. clickView()
  415. return false
  416. }
  417. return true
  418. }
  419. func textViewDidChange(_ textView: UITextView) {
  420. clearBtn.isHidden = textView.text.count <= 0
  421. viewModel.selectedPTPStyleModel.inputText = textView.text
  422. viewModel.gennerateChange()
  423. }
  424. }
  425. extension TSPTPInputVC {
  426. @objc func vipInfoChanged() {
  427. kExecuteOnMainThread {
  428. self.updateVipView()
  429. }
  430. }
  431. func updateVipView() {
  432. kExecuteOnMainThread {
  433. self.vipBtn.isHidden = PurchaseManager.default.isVip
  434. self.creatBtnView.updateVipView()
  435. }
  436. }
  437. func getVipText() -> String {
  438. return "Generate".localized
  439. }
  440. }
  441. extension TSPTPInputVC {
  442. @objc func clickView() {
  443. view.endEditing(true)
  444. }
  445. func generateCheck() {
  446. TSNetworkShared.generateCheck(checkURLType: .imageRewriteV2, animationView: self.contentView) { [weak self] data, error in
  447. if error == nil{
  448. self?.generateImage()
  449. }
  450. }
  451. }
  452. func generateImage() {
  453. viewModel.selectedPTPStyleModel.upLoadImage = viewModel.upLoadImage
  454. viewModel.selectedPTPStyleModel.upLoadImageUrl = nil
  455. let gennerateVC = TSPTPGeneratorVC(generateStyleModel: viewModel.selectedPTPStyleModel) { [weak self] _ in
  456. guard let self = self else { return }
  457. updateVipView()
  458. }
  459. gennerateVC.reloadViewBlock = { [weak self] in
  460. guard let self = self else { return }
  461. updataCollectionView()
  462. }
  463. gennerateVC.closePageComplete = {
  464. [weak self] in
  465. guard let self = self else { return }
  466. updataCollectionView()
  467. }
  468. kPresentModalVC(target: self, modelVC: gennerateVC, transitionStyle: .crossDissolve)
  469. }
  470. func setCreatBtnEnabled() {
  471. let isAvailability = TSGeneratePTPOperationQueue.shared.isAvailability
  472. if viewModel.isCanGennerate, isAvailability {
  473. creatBtnView.setBtnEnabled(isEnabled: true)
  474. creatBtnView.loading = false
  475. dePrint("TSTextGeneralPicVC setCreatBtnEnabled false")
  476. } else {
  477. dePrint("TSTextGeneralPicVC setCreatBtnEnabled = \(isAvailability)")
  478. creatBtnView.setBtnEnabled(isEnabled: false)
  479. creatBtnView.loading = !isAvailability
  480. }
  481. updateVipView()
  482. }
  483. }
  484. extension TSPTPInputVC {
  485. @objc func keyboardWillShow(_ notification: Notification) {
  486. guard let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { return }
  487. let scrollView = cusStackView.scrollView
  488. let keyboardHeight = keyboardFrame.height - view.safeAreaInsets.bottom
  489. let textViewFrame = scrollView.convert(customTextView.frame, from: customTextView.superview)
  490. let scrollDistance = textViewFrame.maxY - (scrollView.bounds.height - keyboardHeight)
  491. let y = scrollDistance
  492. scrollView.setContentOffset(CGPoint(x: 0, y: y), animated: true)
  493. }
  494. // MARK: - 键盘隐藏时恢复
  495. @objc private func keyboardWillHide(_ notification: Notification) {
  496. cusStackView.scrollView.contentOffset = CGPoint(x: 0, y: 0)
  497. }
  498. }
  499. extension TSPTPInputVC {
  500. func updateTextFiledView() {
  501. if viewModel.selectedPTPStyleModel.input {
  502. promptTextView.isHidden = false
  503. } else {
  504. promptTextView.isHidden = true
  505. }
  506. UIView.animate(withDuration: 0.3) {
  507. self.cusStackView.layoutIfNeeded()
  508. }
  509. }
  510. }