// // KMNBetaFeedbackWindowController.swift // PDF Reader Pro Beta // // Created by kdanmobile on 2025/3/1. // import Cocoa import KMComponentLibrary class KMNBetaFeedbackWindowController: KMNBaseWindowController, NSWindowDelegate { @IBOutlet var titleLabel: NSTextField! @IBOutlet var stateImageView: NSImageView! @IBOutlet var stateLabel: NSTextField! @IBOutlet var stateImageTopConstraint:NSLayoutConstraint! @IBOutlet var feedbackLabel: NSTextField! @IBOutlet var feedbackTextView: NSTextView! @IBOutlet var feedbackBox: NSBox! @IBOutlet var addFileLabel: NSTextField! @IBOutlet var addFileToolTip: ComponentToolTipsHelp! @IBOutlet var listTabelView: NSTableView! @IBOutlet var emailLabel: NSTextField! @IBOutlet var webLabel: NSTextField! @IBOutlet var addFileButton: ComponentButton! @IBOutlet var cancelButton: ComponentButton! @IBOutlet var applyButton: ComponentButton! @IBOutlet var addFileWidthButton:NSLayoutConstraint! @IBOutlet var cancelWidthButton:NSLayoutConstraint! @IBOutlet var applyWidthButton:NSLayoutConstraint! @objc var submitActionCallback: ((_ isSuccess: Bool)->Void)? private var _fileFormats: [String] = ["jpg", "jpeg", "png", "gif", "bmp", "tiff", "tif", "psd", "svg", "pdf", "mp4", "mov", "avi", "mkv", "wmv", "flv", "mpg", "3gp", "doc", "docx", "xls" ,"xlsx", "ppt", "pptx", "txt", "rtf", "nfo"] @objc static let shared: KMNBetaFeedbackWindowController = { let windowC = KMNBetaFeedbackWindowController(windowNibName: "KMNBetaFeedbackWindowController") return windowC }() private var _filePaths: [String] = [] var datas: [KMUserFbListModel] = [] { didSet { listTabelView.reloadData() } } var isSupportOpenApp: Bool! { get { //需拿远端的控制的时间 return KMNBetaFeedbackManager.defalutManager.isSupportOpenApp } } override func windowDidLoad() { super.windowDidLoad() self.window?.delegate = self } func windowWillClose(_ notification: Notification) { if(KMNBetaFeedbackManager.defalutManager.isSupportOpenApp == false) { NSApplication.shared.terminate(nil) } } override func initContentView() { feedbackTextView.delegate = self titleLabel.font = ComponentLibrary.shared.getFontFromKey("mac/body-l-medium") stateLabel.font = ComponentLibrary.shared.getFontFromKey ("mac/body-xs-regular") feedbackLabel.font = ComponentLibrary.shared.getFontFromKey("mac/body-m-medium") feedbackTextView.font = ComponentLibrary.shared.getFontFromKey("mac/body-m-medium") addFileLabel.font = ComponentLibrary.shared.getFontFromKey("mac/body-m-medium") applyButton.properties = ComponentButtonProperty(type: .primary, size: .s, state: .normal, isDisable: true, buttonText: KMLocalizedString("Submit")) applyButton.setTarget(self, action: #selector(submitButtonClicked(_ :))) applyButton.keyEquivalent = KMKeyEquivalent.enter cancelButton.properties = ComponentButtonProperty(type: .default_tertiary, size: .s, state: .normal, buttonText: KMLocalizedString("Cancel")) cancelButton.setTarget(self, action: #selector(cancelButtonClicked(_ :))) cancelButton.keyEquivalent = KMKeyEquivalent.esc.string() addFileButton.properties = ComponentButtonProperty(type: .secondary, size: .s, state: .normal, showLeftIcon:true, buttonText: KMLocalizedString("Add File"), icon:NSImage(named: "KMNImageNameBetaAddFile")) addFileButton.setTarget(self, action: #selector(addFileButtonClicked(_ :))) if(KMMemberInfo.shared.vip_levels != "1") { if KMLocalizedString("USD", tableName: "MemberCenterLocalizable", comment: "") == "CN" { stateImageView.image = NSImage(named: "KMNImageNameBetaFeedbackAIZh") } else { stateImageView.image = NSImage(named: "KMNImageNameBetaFeedbackAIEn") } } else { if KMLocalizedString("USD", tableName: "MemberCenterLocalizable", comment: "") == "CN" { stateImageView.image = NSImage(named: "KMNImageNameBetaFeedbackZh") } else { stateImageView.image = NSImage(named: "KMNImageNameBetaFeedbackEn") } } if(!isSupportOpenApp) { stateImageTopConstraint.constant = 46 titleLabel.isHidden = false } else { stateImageTopConstraint.constant = 8 titleLabel.isHidden = true } listTabelView.dataSource = self listTabelView.delegate = self listTabelView.rowHeight = 40 listTabelView.register(.init(nibNamed: "KMNBetaFeedbackTableCellView", bundle: nil), forIdentifier: .init("KMNBetaFeedbackTableCellView")) listTabelView.enclosingScrollView?.wantsLayer = true listTabelView.enclosingScrollView?.borderType = .noBorder listTabelView.enclosingScrollView?.drawsBackground = false listTabelView.enclosingScrollView?.backgroundColor = .clear listTabelView.enclosingScrollView?.layer?.backgroundColor = .clear listTabelView.backgroundColor = .clear listTabelView.registerForDraggedTypes([.fileURL]) listTabelView.intercellSpacing = NSSize(width: 8, height: 8) // 设置水平和垂直间距 } override func updateUIThemeColor() { window?.contentView?.wantsLayer = true window?.contentView?.layer?.backgroundColor = ComponentLibrary.shared.getComponentColorFromKey("colorBg/popup").cgColor feedbackBox.borderColor = ComponentLibrary.shared.getComponentColorFromKey("comp-field/colorBorder-nor"); titleLabel.textColor = ComponentLibrary.shared.getComponentColorFromKey("colorError/base") stateLabel.textColor = ComponentLibrary.shared.getComponentColorFromKey ("colorText/2") feedbackLabel.textColor = ComponentLibrary.shared.getComponentColorFromKey("comp-form/colorText-label") feedbackTextView.textColor = ComponentLibrary.shared.getComponentColorFromKey("comp-form/colorText-label") addFileLabel.textColor = ComponentLibrary.shared.getComponentColorFromKey("comp-form/colorText-label") cancelButton.reloadData() applyButton.reloadData() addFileButton.reloadData() updateLabel() } override func updateUILanguage() { titleLabel.stringValue = KMLocalizedString("Beta's Usage Rights Has Expired") stateLabel.stringValue = "* " + KMLocalizedString("Your attachments are strictly confidential and will only be used for the current operation.") feedbackLabel.stringValue = KMLocalizedString("Feedback (required)") feedbackTextView.km_placeholderString = KMLocalizedString("Please describe your suggestions in detail so that we can better improve our product.") addFileLabel.stringValue = KMLocalizedString("Add Attachment Document") var toolTips = "-" + KMLocalizedString("Limit file size to 20M, upload up to 10 files") toolTips = toolTips + "\n" + "-" + "No limitations on file formats ( PDF, Excel, Word, Powerpoint, TXT, HTML, MP4, Images and so on)" addFileToolTip.toolTip = toolTips cancelButton.properties.buttonText = KMLocalizedString("Cancel") applyButton.properties.buttonText = KMLocalizedString("Submit") addFileButton.properties.buttonText = KMLocalizedString("Add File") cancelButton.reloadData() applyButton.reloadData() addFileButton.reloadData() cancelWidthButton.constant = cancelButton.properties.propertyInfo.viewWidth applyWidthButton.constant = applyButton.properties.propertyInfo.viewWidth addFileWidthButton.constant = addFileButton.properties.propertyInfo.viewWidth updateLabel() } func updateLabel() { let text = KMLocalizedString("Reward will be distributed to your PDF Reader Pro account: %@") let email = KMMemberInfo.shared.userEmail let fullString = String(format: text, email) let attrStr = NSMutableAttributedString(string: fullString) attrStr.addAttribute(.font, value: ComponentLibrary.shared.getFontFromKey("mac/body-xs-regular"), range: NSRange(location: 0, length: fullString.count)) attrStr.addAttribute(.foregroundColor, value: ComponentLibrary.shared.getComponentColorFromKey("colorText/2"), range: NSRange(location: 0, length: fullString.count)) attrStr.addAttribute(.foregroundColor, value: ComponentLibrary.shared.getComponentColorFromKey("colorPrimary/textLight"), range: (fullString as NSString).range(of: email)) let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.alignment = .center attrStr.addAttribute(.paragraphStyle, value: paragraphStyle, range: NSRange(location: 0, length: fullString.count)) // 应用富文本 emailLabel.attributedStringValue = attrStr let linkText = KMLocalizedString("Please visit our Official Website to Sign in to check your reward.") let linkAttrStr = NSMutableAttributedString(string: linkText) let link = KMLocalizedString("Official Website") // 设置超链接文本属性 let url = URL(string: "https://www.pdfreaderpro.com")! linkAttrStr.addAttribute(.link, value: url, range: (linkText as NSString).range(of: link)) // 设置字体和颜色 linkAttrStr.addAttribute(.font, value: ComponentLibrary.shared.getFontFromKey("mac/body-xs-regular"), range: NSRange(location: 0, length: linkText.count)) linkAttrStr.addAttribute(.foregroundColor, value: ComponentLibrary.shared.getComponentColorFromKey("colorText/2"), range: NSRange(location: 0, length: linkText.count)) linkAttrStr.addAttribute(.foregroundColor, value: ComponentLibrary.shared.getComponentColorFromKey("colorPrimary/textLight"), range: (linkText as NSString).range(of: link)) linkAttrStr.addAttribute(.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: (linkText as NSString).range(of: link)) linkAttrStr.addAttribute(.paragraphStyle, value: paragraphStyle, range: NSRange(location: 0, length: linkText.count)) webLabel.isEditable = false webLabel.isSelectable = true webLabel.allowsEditingTextAttributes = true // 应用富文本到 NSTextField webLabel.attributedStringValue = linkAttrStr } private func _updateListData() { var datas: [KMUserFbListModel] = [] let maxSize: Double = 20 * 1024 * 1024 var fileSize: Double = 0 var filePaths: [String] = [] var showFileSizeLimit = false var showFileCountLimit = false for (i, fileP) in _filePaths.enumerated() { let model = KMUserFbListModel() model.filePath = fileP let url = URL(fileURLWithPath: fileP) model.fileName = url.lastPathComponent let attri = try?FileManager.default.attributesOfItem(atPath: fileP) model.fileSize = attri?[FileAttributeKey.size] as? Double ?? 0 fileSize = model.fileSize if fileSize >= maxSize { showFileSizeLimit = true continue } if i >= 10 { showFileCountLimit = true break } model.fileSizeString = self.fileSizeString(model.fileSize) datas.append(model) filePaths.append(fileP) } _filePaths = filePaths if showFileCountLimit { _ = KMNCustomAlertView.alertView(message: KMLocalizedString("Add failed: upload up to 10 files"), type: .warning, fromView: self.window?.contentView ?? NSView(), point:CGPointMake(CGRectGetMidX(self.window?.contentView?.bounds ?? CGRect.zero), CGRectGetMidY(self.window?.contentView?.bounds ?? CGRect.zero))) } else if showFileSizeLimit { _ = KMNCustomAlertView.alertView(message: KMLocalizedString("Add failed: attachment size cannot exceed 20M"), type: .warning, fromView: self.window?.contentView ?? NSView(), point:CGPointMake(CGRectGetMidX(self.window?.contentView?.bounds ?? CGRect.zero), CGRectGetMidY(self.window?.contentView?.bounds ?? CGRect.zero))) } if datas.count >= 10 { addFileButton.properties.state = .pressed } else { addFileButton.properties.state = .normal } self.datas = datas } func fileSizeString(_ fSize: Double) -> String { let fileSize = fSize / 1024 let size = fileSize >= 1024 ? (fileSize < 1048576 ? fileSize/1024 : fileSize/1048576.0) : fileSize let unit = fileSize >= 1024 ? (fileSize < 1048576 ? "M" : "G") : "K" return String(format: "%0.1f %@", size, unit) } private func _fileFormatIsContains(_ exn: String) -> Bool { let _exn = exn.lowercased() return _fileFormats.contains(_exn) } private func _addWaitingView(to view: NSView) { _removeWaitingView(from: view) let waitingView = WaitingView(frame: view.bounds) waitingView.autoresizingMask = [.width, .height] view.addSubview(waitingView) waitingView.startAnimation() } private func _removeWaitingView(from view: NSView) { KMMainThreadExecute { for subview in view.subviews { if subview is WaitingView { subview.removeFromSuperview() break } } } } //MARK: - Action @objc func cancelButtonClicked(_ sender: NSView) { close() own_closeEndSheet() } @objc func submitButtonClicked(_ sender: NSView) { _addWaitingView(to: self.window?.contentView ?? NSView()) KMNBetaFeedbackManager.defalutManager.feekbackAction(filePaths: self._filePaths, content: feedbackTextView.string) {[weak self] success, result in if (success) { if(!(self?.isSupportOpenApp == false)) { } else { self?.submitActionCallback?(true) } self?._removeWaitingView(from: self?.window?.contentView ?? NSView()) KMNBetaFeedbackManager.defalutManager.resetHaveFeedback() KMNBetaFeedbackManager.defalutManager.havingFeekbackAction { success, result in }; self?.close() self?.own_closeEndSheet() } } } @objc func addFileButtonClicked(_ sender: NSView) { let panel = NSOpenPanel() panel.allowedFileTypes = _fileFormats panel.allowsMultipleSelection = true panel.beginSheetModal(for: self.window!) { [self] resp in if resp == .cancel { return } for url in panel.urls { self._filePaths.append(url.path) } self.addFileButton.properties.state = .normal self.addFileButton.reloadData() _updateListData() } } } extension KMNBetaFeedbackWindowController: NSTableViewDelegate, NSTableViewDataSource { func numberOfRows(in tableView: NSTableView) -> Int { return datas.count } func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { var cell = tableView.makeView(withIdentifier: .init("KMNBetaFeedbackTableCellView"), owner: self) if cell == nil { cell = KMNBetaFeedbackTableCellView.createFromNib() } let cellView = cell as? KMNBetaFeedbackTableCellView let model = datas[row] cellView?.fileNameLabel.stringValue = model.fileName ?? "" cellView?.fileSizeLabel.stringValue = model.fileSizeString ?? "" cellView?.deleteItemClick = { [weak self] idx in if let cnt = self?._filePaths.count, row < cnt { self?._filePaths.remove(at: row) self?._updateListData() } } return cell } func tableView(_ tableView: NSTableView, validateDrop info: NSDraggingInfo, proposedRow row: Int, proposedDropOperation dropOperation: NSTableView.DropOperation) -> NSDragOperation { let datas = _filePaths.count if datas >= 10 { return NSDragOperation(rawValue: 0) } return .generic } func tableView(_ tableView: NSTableView, acceptDrop info: NSDraggingInfo, row: Int, dropOperation: NSTableView.DropOperation) -> Bool { let pborad = info.draggingPasteboard guard let _ = pborad.availableType(from: [.fileURL]) else { return false } guard let items = pborad.pasteboardItems else { return false } for item in items { let string = item.propertyList(forType: .fileURL) as? String ?? "" if let path = URL(string: string)?.path { let contains = _fileFormatIsContains(URL(string: string)!.pathExtension) if contains { _filePaths.append(path) } } } self._updateListData() return true } } extension KMNBetaFeedbackWindowController: NSTextViewDelegate { func textViewDidChangeSelection(_ notification: Notification) { if feedbackTextView.isEqual(to: notification.object) { if(feedbackTextView.string.count > 0) { applyButton.properties.isDisabled = false feedbackTextView.placeholderLabel.isHidden = true } else { feedbackTextView.placeholderLabel.isHidden = false applyButton.properties.isDisabled = true } applyButton.reloadData() } } }