TSPTPInputVC.swift 21 KB

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