TSPTPInputVC.swift 20 KB

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