// // TestEntrancesController.swift // Crush // // Created by Leon on 2025/7/12. // import UIKit import Combine import URLNavigator class TestEntrance { let title: String let destination: UIViewController.Type? let category: String let action: (() -> Void)? init(title: String, destination: UIViewController.Type? = UIViewController.self, action:(() -> Void)? = nil, category: String) { self.title = title self.destination = destination self.category = category self.action = action } } class TestEntrancesController: CLBaseViewController { private let tableView = UITableView(frame: .zero, style: .grouped) private var sections: [String] = [] private var testItems: [String: [TestEntrance]] = [:] var photoModels: [PhotoBrowserModel] = [PhotoBrowserModel]() lazy var recordTool = AudioRecordTool() override func viewDidLoad() { super.viewDidLoad() setupUI() configureTestItems() } private func setupUI() { //view.backgroundColor = .white title = "Test Entrances" // Setup TableView tableView.backgroundColor = .clear tableView.translatesAutoresizingMaskIntoConstraints = false tableView.delegate = self tableView.dataSource = self tableView.register(TestEntranceCell.self, forCellReuseIdentifier: TestEntranceCell.reuseIdentifier) view.addSubview(tableView) tableView.snp.makeConstraints { make in //make.edges.equalToSuperview() make.leading.trailing.bottom.equalToSuperview() make.top.equalTo(navigationView.snp.bottom) } let headView = TestEntrancesHeadView(frame: CGRect(x: 0, y: 0, width: UIScreen.width, height: 48)) tableView.tableHeaderView = headView } private func configureTestItems() { let common = "Common" let auth = "Auth" let role = "Role" let data = "Data" let chat = "Chat" let user = "User" let discover = "Discover" let network = "Network" let ui = "UI" let customOrder = [common,ui, auth, role, data,chat, user,discover, network] // Sample test configurations let items = [ TestEntrance(title: "TapCommon01", action: {[weak self] in self?.tapCommon01()}, category: common), TestEntrance(title: "TapCommon02", action: {[weak self] in self?.tapCommon02()}, category: common), TestEntrance(title: "TapCommon03", action: {[weak self] in self?.tapCommon03()}, category: common), TestEntrance(title: "TapCommon04", action: {[weak self] in self?.tapCommon04()}, category: common), TestEntrance(title: "TapCommon05 Data", action: {[weak self] in self?.tapCommon05()}, category: common), TestEntrance(title: "Audio Record and upload", action: {[weak self] in self?.tapCommon06()}, category: common), TestEntrance(title: "TapCommon07", action: {[weak self] in self?.tapCommon07()}, category: common), TestEntrance(title: "UI Components", destination: UITestViewController.self, category: ui), TestEntrance(title: "TestUI1Controller", destination: TestUI1Controller.self, category: ui), TestEntrance(title: "Login Test", destination: CLLoginMainController.self, category: auth), TestEntrance(title: "Role Classification", destination: RoleClassificationSelectController.self, category: role), TestEntrance(title: "Role Voice", destination: RoleVoiceSetController.self, category: role), TestEntrance(title: "Role Figure Generate", destination: RoleFigureGenerateController.self, category: role), TestEntrance(title: "Role Home Page", destination: RoleHomePagerController.self, category: role), TestEntrance(title: "Role Photo Generate", destination: RolePhotoGenerateController.self, category: role), TestEntrance(title: "Database Test", destination: DatabaseTestViewController.self, category: data), TestEntrance(title: "IAP Test", destination: DatabaseTestViewController.self, category: data), TestEntrance(title: "Data Tool", destination: TestDatasTableController.self, category: data), TestEntrance(title: "Chat Setting", destination: ChatSettingListController.self, category: chat), TestEntrance(title: "Phone Calling", destination: PhoneCallController.self, category: chat), TestEntrance(title: "Profile Test", destination: ProfileTestViewController.self, category: user), TestEntrance(title: "Setting", destination: SettingListController.self, category: user), TestEntrance(title: "Meet Drag test", destination: MeetDragCardExampleViewController.self, category: discover), TestEntrance(title: "Network Test", destination: NetworkTestViewController.self, category: network), ] // Group items by category testItems = Dictionary(grouping: items, by: { $0.category }) // Get all categories in their original order let allCategories = items.map { $0.category }.uniqued() // Sort categories: prioritize customOrder, then append remaining categories in original order sections = customOrder + allCategories.filter { !customOrder.contains($0) } tableView.reloadData() } } extension TestEntrancesController{ // Functions private func tapCommon01(){ // let vc = UnlockPriceSetDialogController() // vc.show() // Alert.showAIRoleCreateSuccessAlert() // let photo = UploadPhotoM() // photo.image = UIImage(named: "egpic") // photo.addThisItemTimeStamp = Date().timeStamp // // Hud.showIndicator() // CloudStorage.shared.s3BatchAddPhotos([photo], bucket: .HEAD_IMAGE) { result in // Hud.hideInidcator() // dlog("✅测试上传图片:\(result)") // } // let color = CLGlobalToken.color(token: .glo_color_violet_10) // dlog("当前颜色:\(color)") // let vc = ChatModePickSheet() // vc.show() } private func tapCommon02(){ let sheet = BuyCreditsSheetView() sheet.show() } private func tapCommon03(){ let cropViewController = CropViewController(image: UIImage(named: "egpic")!) { [unowned self] croppedImage in dlog("crop image: \(croppedImage)") } let navc = CLNavigationController(rootViewController: cropViewController) UIWindow.getTopViewController()?.present(navc, animated: true, completion: nil) } private func tapCommon04(){ //var photoModels: [EGPhotoBrowserModel] = [EGPhotoBrowserModel]() photoModels.removeAll() do{ let model = PhotoBrowserModel() // model.image = UIImage(named: "egpic") model.imageUrl = "https://img2.baidu.com/it/u=3634539496,393899961&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=563" photoModels.append(model) } do{ let model = PhotoBrowserModel() // model.image = UIImage(named: "egpic") model.imageUrl = "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.nga.178.com%2Fattachments%2Fmon_202011%2F28%2F7nQ5-gg1iZ2hT3cS16p-23w.jpg&refer=http%3A%2F%2Fimg.nga.178.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1756093859&t=f7aa3f1da2fcbda3e44de75aaa5f224a" photoModels.append(model) } ImageBrowser.show(models: photoModels, index: 0, type: .roleMine) } private func tapCommon05(){ let mockAIUserRequest = AIUserRequest( aiId: 123, nickname: "MockAI", sex: .female, headImg: "https://example.com/avatar.jpg", birthday: "2000-01-01", roleCode: "mentor", characterCode: "kind", tagCode: "funny", introduction: "I'm a mock AI used for testing.", permission: 1, imageUrl: "https://example.com/image.jpg", imageWidth: 720, imageHeight: 1280, aiUserExt: AIUserRequestExt( profile: "Deep personality profile here", dialogueStyle: "Friendly", dialoguePrologue: "Hey there! Nice to meet you.", dialogueTimbreCode: "warm", dialoguePitch: "medium", dialogueSpeechRate: "normal", imageStyleCode: "realistic", imageDesc: "A cheerful character in a futuristic setting", imageReferenceUrl: "https://example.com/reference.jpg" ) ) let jsonString:String = CodableHelper.encodeToJSONString(mockAIUserRequest)! dlog("AIUserRequest: \(jsonString)") let model = CodableHelper.decode(AIUserRequest.self, from: jsonString) dlog("model: \(String(describing: model))") } private func tapCommon06(){ if recordTool.audioRecorder?.isRecording ?? false{ dlog("audio结束录制") recordTool.stopRecord() }else{ dlog("audio开始录制") recordTool.startRecord { path in if let pathInSandbox = path { do { let data = try Data(contentsOf: pathInSandbox) // ✅ data 就是文件的二进制数据 let model = UploadModel() model.addThisItemTimeStamp = Date().timeStamp model.fileData = data CloudStorage.shared.s3AddUploadAudio(model) { result in dlog("Audio upload结果:\(result)") } } catch { print("❌读取文件audio mp3失败: \(error)") } } } } } private func tapCommon07(){ // AICowProvider.request(.voiceAsr2(url: "https://snd.crushlevel.ai/dev/main/sound/439213911113729/1756776603015.mp3"), modelType: EmptyModel.self) { result in // switch result { // case .success(let model): // dlog(model) // case .failure: // break // } // } // let v = CoinsRechargeSheet() // v.show() // CLPurchase.shared.showIAPBuyCoinSheet() // Hud.showIndicator() } } extension Sequence where Element: Hashable { func uniqued() -> [Element] { var seen = Set() return filter { seen.insert($0).inserted } } } // MARK: - UITableViewDataSource extension TestEntrancesController: UITableViewDataSource { func numberOfSections(in tableView: UITableView) -> Int { return sections.count } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { let category = sections[section] return testItems[category]?.count ?? 0 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: TestEntranceCell.reuseIdentifier, for: indexPath) as! TestEntranceCell let category = sections[indexPath.section] if let item = testItems[category]?[indexPath.row] { cell.configure(with: item) } return cell } func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { return sections[section] } } // MARK: - UITableViewDelegate extension TestEntrancesController: UITableViewDelegate { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) let category = sections[indexPath.section] if let item = testItems[category]?[indexPath.row] { // item.destination is UIViewController.Type guard let type = item.destination else{ fatalError() } if let action = item.action{ action() return } if let baseVCType = type as? CLBaseViewController.Type { let shouldShow = baseVCType.shouldPresentThisVc if shouldShow { let destinationVC = type.init() let navc = CLNavigationController(rootViewController: destinationVC) navc.modalPresentationStyle = .fullScreen present(navc, animated: true) return } } let destinationVC = type.init() navigationController?.pushViewController(destinationVC, animated: true) } } } // MARK: - Custom Cell class TestEntranceCell: UITableViewCell { static let reuseIdentifier = "TestEntranceCell" private let titleLabel = UILabel() private let categoryLabel = UILabel() private let arrowLabel = UILabel() override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) setupUI() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupUI() { backgroundView?.backgroundColor = .clear backgroundColor = .clear contentView.backgroundColor = .clear // Configure labels titleLabel.font = .systemFont(ofSize: 16, weight: .medium) titleLabel.textColor = .white categoryLabel.font = .systemFont(ofSize: 14, weight: .regular) categoryLabel.textColor = .gray arrowLabel.text = "→" arrowLabel.textColor = .white arrowLabel.font = .systemFont(ofSize: 16) // Setup stack views for three-column layout let stackView = UIStackView(arrangedSubviews: [categoryLabel, titleLabel, arrowLabel]) stackView.axis = .horizontal stackView.spacing = 16 stackView.distribution = .fill stackView.alignment = .center stackView.translatesAutoresizingMaskIntoConstraints = false contentView.addSubview(stackView) // Constraints NSLayoutConstraint.activate([ stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16), stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16), stackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8), stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8), categoryLabel.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 0.3), titleLabel.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 0.6), ]) } func configure(with entrance: TestEntrance) { titleLabel.text = entrance.title categoryLabel.text = entrance.category } } // MARK: - Sample Test View Controllers class LoginTestViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .white title = "Login Test" } } class ProfileTestViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .white title = "Profile Test" } } class NetworkTestViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .white title = "Network Test" } } class UITestViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .white title = "UI Components Test" } } class TestEntrancesHeadView: UIView{ var textFiled: CLTextField! var operateButton : StyleButton! private var cancellables = Set() override init(frame: CGRect) { super.init(frame: frame) operateButton = { let v = StyleButton() v.primary(size: .small) v.addTarget(self, action: #selector(tapOperateButton), for: .touchUpInside) addSubview(v) v.snp.makeConstraints { make in make.trailing.equalToSuperview().offset(-16) make.centerY.equalToSuperview() } v.isEnabled = false return v }() textFiled = { let v = CLTextField() v.placeholder = "aiId" addSubview(v) v.snp.makeConstraints { make in make.leading.equalToSuperview().offset(16) make.centerY.equalToSuperview() make.trailing.equalTo(operateButton.snp.leading).offset(-16) } return v }() operateButton.setTitle("进AI主页", for: .normal) textFiled.textPublisher.sink {[weak self] text in guard let str = text else{ self?.operateButton.isEnabled = false return } self?.operateButton.isEnabled = str.count > 10 }.store(in: &cancellables) // 从缓存中加载之前存储的aiId loadCachedAIId() } private func setupData(){ textFiled.text = "444190968774657" textFiled.sendTextChangedNoti() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } // MARK: - 缓存相关方法 /// 从缓存中加载aiId private func loadCachedAIId() { if let cachedAIId = AppCache.fetchCache(key: CacheKey.testRecordAIId.rawValue, type: Int.self) { textFiled.text = "\(cachedAIId)" textFiled.sendTextChangedNoti() dlog("✅ 从缓存加载aiId: \(cachedAIId)") } } /// 将aiId存储到缓存 private func saveAIIdToCache(_ aiId: Int) { AppCache.cache(key: CacheKey.testRecordAIId.rawValue, value: aiId) dlog("✅ 已存储aiId到缓存: \(aiId)") } @objc private func tapOperateButton(){ guard let text = textFiled.text, let num = Int(text) else{ return } guard text.count > 10 else{ return } // 存储aiId到缓存 saveAIIdToCache(num) // 437416915828737 AppRouter.goAIRoleHome(aiId: num) } }