// // TSPTPInputVC.swift // AIEmoji // // Created by 100Years on 2025/4/7. // import PhotosUI class TSPTPInputVC: TSBaseVC { lazy var viewModel: TSPTPInputVM = { let viewModel = TSPTPInputVM() viewModel.isCanGennerateBlock = { [weak self] isCan in guard let self = self else { return } // submitBtn.isEnabled = isCan // creatBtnView.setBtnEnabled(isEnabled: isCan) setCreatBtnEnabled() } return viewModel }() lazy var photoPickerManager: TSPhotoPickerManager = { let photoPickerManager = TSPhotoPickerManager(viewController: self) return photoPickerManager }() private var keyboardHeight: CGFloat = 0 var hintBaseVC = TSAIListHintBaseVC(config: .defaultConfig) //###################################### 导航栏 view ###################################### lazy var vipBtn: UIButton = { let vipBtn = UIButton.createButton(image: UIImage(named: "nav_vip")) { [weak self] in guard let self = self else { return } TSPurchaseVC.show(target: self) {} } return vipBtn }() lazy var navBarView: TSBaseNavContentBarView = { let navBarView = TSBaseNavContentBarView() let titleImageView = UIImageView.createImageView(imageName: "nav_title_pic",contentMode: .scaleToFill) navBarView.barView.addSubview(titleImageView) titleImageView.snp.makeConstraints { make in make.centerY.equalToSuperview() make.left.equalTo(16) } navBarView.barView.addSubview(vipBtn) vipBtn.snp.makeConstraints { make in make.centerY.equalToSuperview() make.trailing.equalTo(-16)//(-60) make.width.height.equalTo(24) } return navBarView }() //###################################### cusStackView ###################################### lazy var cusStackView: TSCustomStackView = { let cusStackView = TSCustomStackView(axis: .vertical,spacing: 0) cusStackView.scrollView.isScrollEnabled = true return cusStackView }() //###################################### 上传图片 ###################################### lazy var uploadView: TSPTPUploadView = { let uploadView = TSPTPUploadView() uploadView.clickHandel = { [weak self] index in guard let self = self else { return } if index == 0 {//删除 viewModel.upLoadImage = nil uploadView.upLoadImage = nil }else{//添加 if TSAIListHintBaseVC.isShowUploadImageHint{ TSAIListHintBaseVC.isShowUploadImageHint = false presentModalHintVC() }else { pickSinglePhoto() } } } return uploadView }() func pickSinglePhoto() { photoPickerManager.pickCustomSinglePhoto() { [weak self] image, errorString in guard let self = self else { return } if let errorString = errorString { TSToastShared.showToast(text: errorString) }else{ viewModel.upLoadImage = image uploadView.upLoadImage = image } kDelayMainShort { self.photoPickerManager.dismissPageVC() } } } //###################################### 选择风格 ###################################### lazy var selectStyleView: TSPTPSelectStyleView = { let selectStyleView = TSPTPSelectStyleView() selectStyleView.currentIndexPath = IndexPath(item: viewModel.selectedStyleIndex, section: 0) selectStyleView.dataArray = viewModel.ptpStyleModels selectStyleView.clickHandle = { [weak self] indexPath ,model in guard let self = self else { return } viewModel.selectedPTPStyleModel = model viewModel.selectedStyleIndex = indexPath.item updateVipView() updateTextFiledView() } return selectStyleView }() //###################################### 输入框 ###################################### lazy var customTextView: TSPlaceholderTextView = { let customTextView = TSPlaceholderTextView( placeholder: "Describe how you want to transform".localized, text: "", font: .font(size: 14), textColor: .white, backgroundColor: .clear ) customTextView.delegate = self customTextView.returnKeyType = .done return customTextView }() lazy var clearBtn: TSUIExpandedTouchButton = { let clearBtn = TSUIExpandedTouchButton() clearBtn.setUpButton( image: UIImage(named: "clear_text") ) { [weak self] in guard let self = self else { return } customTextView.text = "" textViewDidChange(customTextView) } clearBtn.isHidden = true return clearBtn }() var promptTextViewH:CGFloat = 96.0 lazy var promptTextView:UIView = { let promptTextView = UIView() promptTextView.isHidden = true promptTextView.clipsToBounds = true let bgView = UIView() bgView.backgroundColor = "#333333".uiColor bgView.cornerRadius = 16.0 promptTextView.addSubview(bgView) bgView.snp.makeConstraints { make in make.edges.equalTo(UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16)) } bgView.addSubview(customTextView) bgView.addSubview(clearBtn) customTextView.snp.makeConstraints { make in make.centerY.equalToSuperview() make.leading.equalTo(12.0) make.top.equalTo(12.0) make.bottom.equalTo(-12.0) make.trailing.equalTo(-20) } clearBtn.snp.makeConstraints { make in make.width.height.equalTo(16.0) make.trailing.equalTo(-12) make.bottom.equalTo(-12.0) } return promptTextView }() //###################################### 集合视图 ###################################### private var collectionViewObserver: CollectionViewObserver! let collectionViewBtootm:CGFloat = 80 lazy var collectionComponent: TSCollectionViewComponent = { let layout = UICollectionViewFlowLayout() let cp = TSCollectionViewComponent(frame: CGRect.zero, layout: layout, attributes: [:]) cp.collectionView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: collectionViewBtootm, right: 0) cp.collectionView.isScrollEnabled = false // 禁用自动 contentInset 调整 if #available(iOS 11.0, *) { cp.collectionView.contentInsetAdjustmentBehavior = .never } else { automaticallyAdjustsScrollViewInsets = false } cp.sectionActionHandler = { [weak self] cellCp, indexPath in guard let self = self else { return } if let cmd = cellCp as? String { if cmd == "delete" { showCustomAlert(message: "Are you sure to delete".localized, deleteHandler: { self.viewModel.removeAllHistoryList() self.updataCollectionView() }) }else if cmd == "more" { let historyVC = TSPTPHistoryVC() kPushVC(target: self, modelVC: historyVC) } } } cp.itemActionHandler = { [weak self] (object, indexPath) in guard let self = self else { return } //删除过期的任务 if let cmd = object as? String, cmd == "delete_task_expired" { if let sections = viewModel.colDataArray.safeObj(At: indexPath.section) as? TSGenmojiCoLSectionModel, let currentActionInfoModel = sections.items.safeObj(At: indexPath.item) { TSRMShared.ptpDBHistory.deleteListModel(id: currentActionInfoModel.dataModel.id) updataCollectionView() } } } cp.itemDidSelectedHandler = { [weak self] (object, indexPath) in guard let self = self else { return } if let sections = viewModel.colDataArray.safeObj(At: indexPath.section) as? TSGenmojiCoLSectionModel, let dataModel = sections.items.safeObj(At: indexPath.item)?.dataModel { var dataModelArray:[TSActionInfoModel] = [] for itemModel in sections.items { if itemModel.dataModel.status == "success" || itemModel.dataModel.modelType == .example{ dataModelArray.append(itemModel.dataModel) } } let browseVC = TSAIPhotoBrowseVC() browseVC.dataModelArray = dataModelArray browseVC.currentIndex = dataModelArray.firstIndex(of: dataModel) ?? 0 kPresentModalVC(target: self, modelVC: browseVC,transitionStyle: .crossDissolve) } } cp.collectionView.keyboardDismissMode = .interactive return cp }() //###################################### Button ###################################### lazy var creatBtnView:TSAppBtnView = { let creatBtnView = TSAppBtnView() creatBtnView.setUpButton(style: .generate, vipFreeNumType: .picToPic) { [weak self] in guard let self = self else { return } generateImage() } creatBtnView.setBtnEnabled(isEnabled: false) creatBtnView.isIconVipBlock = { [weak self] in guard let self = self else { return false} var showVip = kPurchaseDefault.generateVipShow(type: .picToPic) if showVip == false { showVip = self.viewModel.selectedPTPStyleModel.isVip } return showVip } creatBtnView.isClickVipBlock = { [weak self] in guard let self = self else { return false} var isVip = kPurchaseDefault.freeNumAvailable(type: .picToPic) == false if viewModel.selectedPTPStyleModel.isVip == true { isVip = true } return isVip } return creatBtnView }() override func createView() { let tapGesture = UITapGestureRecognizer(target: self, action: #selector(clickView)) tapGesture.cancelsTouchesInView = false view.addGestureRecognizer(tapGesture) navBarContentView.addSubview(navBarView) navBarView.snp.makeConstraints { make in make.edges.equalToSuperview() } contentView.addSubview(cusStackView) cusStackView.snp.makeConstraints { make in make.edges.equalToSuperview() } contentView.addSubview(creatBtnView) creatBtnView.snp.makeConstraints { make in make.bottom.equalTo(-16) make.leading.equalTo(16) make.trailing.equalTo(-16) make.height.equalTo(48) } setUpCusStackView() } override func dealThings() { updataCollectionView() //监听 vip 变化 NotificationCenter.default.addObserver(self, selector: #selector(vipInfoChanged), name: .kPurchaseDidChanged, object: nil) updateVipView() TSAIListHintBaseVC.userDefaultsKey = "isFirstUploadImagePTP" // 监听键盘事件 NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil) //监听collectionView 的 contentSize collectionViewObserver = CollectionViewObserver(collectionView: collectionComponent.collectionView) collectionViewObserver.onContentSizeChange = {[weak self] size in guard let self = self else { return } print("collectionViewObserver 内容大小变化: \(size)") self.collectionComponent.collectionView.snp.updateConstraints { make in make.height.equalTo(size.height) } } //监听按钮任务生成中 NotificationCenter.default.addObserver(forName: .kBaseOperationQueueCountChanged, object: nil, queue: nil) { [weak self] notification in guard let self = self else { return } setCreatBtnEnabled() } //后台生成 UI 任务,刷新 UI界面 NotificationCenter.default.addObserver(forName: .kGeneratePTPOperationChanged, object: nil, queue: nil) { [weak self] notification in guard let self = self else { return } updateVipView() if let userInfo = notification.userInfo as? [String: Any],let state = userInfo["state"] as? TSProgressState { dePrint("TSBaseOperation stateDatauPblished 收到 = \(state)") if state.isResult {//有结果,一定要刷新 updataCollectionView() }else if self.isViewVisible == false { dePrint("TSBaseOperation 视图不可见") return }else if state.reloadNewData {//主要是给pending用,让他再视图中有个位置占着 updataCollectionView() } } } //同时 VC主动刷新UI界面 NotificationCenter.default.addObserver(forName: .kPTPDataChanged, object: nil, queue: nil) { [weak self] notification in guard let self = self else { return } updataCollectionView() } } func updataCollectionView(){ self.viewModel.updateRecentData() collectionComponent.clear() collectionComponent.reloadView(with:viewModel.colDataArray) } } extension TSPTPInputVC { func presentModalHintVC(){ hintBaseVC = TSAIListHintBaseVC(config: .defaultConfig) { [weak self] image in guard let self = self else { return } viewModel.upLoadImage = image uploadView.upLoadImage = image hintBaseVC.dismissPageVC() } kPresentModalVC(target: self, modelVC: hintBaseVC,transitionStyle: .crossDissolve) } func setUpCusStackView(){ let uploadPhotoTitleView = TSTitleView.creatTitleView(title: "Upload Photo".localized, subTitle: "") cusStackView.addSubviewToStack(uploadPhotoTitleView) uploadPhotoTitleView.snp.makeConstraints { make in make.height.equalTo(uploadPhotoTitleView.viewH) make.width.equalTo(k_ScreenWidth) } let hintBtn = TSUIExpandedTouchButton() hintBtn.setUpButton(image: UIImage(named: "ptp_hint")){ [weak self] in guard let self = self else { return } presentModalHintVC() } uploadPhotoTitleView.contentView.addSubview(hintBtn) hintBtn.snp.makeConstraints { make in make.centerY.equalToSuperview().offset(kSectionTitleViewCenterYOffset) make.trailing.equalTo(-16) make.width.height.equalTo(16) } cusStackView.addSubviewToStack(uploadView) uploadView.snp.makeConstraints { make in make.height.equalTo(uploadView.viewH) make.width.equalTo(k_ScreenWidth) } let selectStyleTitleView = TSTitleView.creatTitleView(title: "Select Style".localized) cusStackView.addSubviewToStack(selectStyleTitleView) selectStyleTitleView.snp.makeConstraints { make in make.height.equalTo(selectStyleTitleView.viewH) make.width.equalTo(k_ScreenWidth) } cusStackView.addSubviewToStack(selectStyleView) selectStyleView.snp.makeConstraints { make in make.height.equalTo(selectStyleView.viewH) make.width.equalTo(k_ScreenWidth) } cusStackView.addSubviewToStack(promptTextView,length: promptTextViewH,animate: false) promptTextView.isHidden = !viewModel.selectedPTPStyleModel.input cusStackView.addSubviewToStack(collectionComponent.collectionView) collectionComponent.collectionView.snp.makeConstraints { make in make.height.equalTo(0) make.width.equalTo(k_ScreenWidth) } cusStackView.addSpacing(length: collectionViewBtootm) } } extension TSPTPInputVC: UITextViewDelegate{ // 实现 UITextViewDelegate 协议方法,控制 return 键行为 func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { if text == "\n" { // 当输入为换行符(即按下 return 键)时,执行相应操作 //sendBolck?(textView.text) clickView() return false } return true } func textViewDidChange(_ textView: UITextView){ clearBtn.isHidden = textView.text.count <= 0 viewModel.selectedPTPStyleModel.inputText = textView.text viewModel.gennerateChange() } } extension TSPTPInputVC { @objc func vipInfoChanged() { kExecuteOnMainThread { self.updateVipView() } } func updateVipView() { kExecuteOnMainThread { self.vipBtn.isHidden = PurchaseManager.default.isVip self.creatBtnView.updateVipView() } } func getVipText()->String{ return "Generate".localized } } extension TSPTPInputVC { @objc func clickView() { view.endEditing(true) } func generateImage() { viewModel.selectedPTPStyleModel.upLoadImage = viewModel.upLoadImage viewModel.selectedPTPStyleModel.upLoadImageUrl = nil let gennerateVC = TSPTPGeneratorVC(generateStyleModel: viewModel.selectedPTPStyleModel) { [weak self] model in guard let self = self else { return } updateVipView() } gennerateVC.reloadViewBlock = { [weak self] in guard let self = self else { return } updataCollectionView() } gennerateVC.closePageComplete = { [weak self] in guard let self = self else { return } updataCollectionView() } kPresentModalVC(target: self, modelVC: gennerateVC,transitionStyle: .crossDissolve) } func setCreatBtnEnabled() { let isAvailability = TSGeneratePTPOperationQueue.shared.isAvailability if viewModel.isCanGennerate,isAvailability { creatBtnView.setBtnEnabled(isEnabled: true) creatBtnView.loading = false dePrint("TSTextGeneralPicVC setCreatBtnEnabled false") }else{ dePrint("TSTextGeneralPicVC setCreatBtnEnabled = \(isAvailability)") creatBtnView.setBtnEnabled(isEnabled: false) creatBtnView.loading = !isAvailability } } } extension TSPTPInputVC { @objc func keyboardWillShow(_ notification: Notification) { guard let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { return } let scrollView = cusStackView.scrollView let keyboardHeight = keyboardFrame.height - view.safeAreaInsets.bottom let textViewFrame = scrollView.convert(customTextView.frame, from: customTextView.superview) let scrollDistance = textViewFrame.maxY - (scrollView.bounds.height - keyboardHeight) let y = scrollDistance scrollView.setContentOffset(CGPoint(x: 0, y: y),animated: true) } // MARK: - 键盘隐藏时恢复 @objc private func keyboardWillHide(_ notification: Notification) { cusStackView.scrollView.contentOffset = CGPoint(x: 0, y: 0) } } extension TSPTPInputVC { func updateTextFiledView () { if viewModel.selectedPTPStyleModel.input { promptTextView.isHidden = false }else{ promptTextView.isHidden = true } UIView.animate(withDuration: 0.3) { self.cusStackView.layoutIfNeeded() } } }