KMNBetaFeedbackWindowController.swift 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. //
  2. // KMNBetaFeedbackWindowController.swift
  3. // PDF Reader Pro Beta
  4. //
  5. // Created by kdanmobile on 2025/3/1.
  6. //
  7. import Cocoa
  8. import KMComponentLibrary
  9. class KMNBetaFeedbackWindowController: KMNBaseWindowController, NSWindowDelegate {
  10. @IBOutlet var titleLabel: NSTextField!
  11. @IBOutlet var stateImageView: NSImageView!
  12. @IBOutlet var stateLabel: NSTextField!
  13. @IBOutlet var stateImageTopConstraint:NSLayoutConstraint!
  14. @IBOutlet var feedbackLabel: NSTextField!
  15. @IBOutlet var feedbackTextView: NSTextView!
  16. @IBOutlet var feedbackBox: NSBox!
  17. @IBOutlet var addFileLabel: NSTextField!
  18. @IBOutlet var addFileToolTip: ComponentToolTipsHelp!
  19. @IBOutlet var listTabelView: NSTableView!
  20. @IBOutlet var emailLabel: NSTextField!
  21. @IBOutlet var webLabel: NSTextField!
  22. @IBOutlet var addFileButton: ComponentButton!
  23. @IBOutlet var cancelButton: ComponentButton!
  24. @IBOutlet var applyButton: ComponentButton!
  25. @IBOutlet var addFileWidthButton:NSLayoutConstraint!
  26. @IBOutlet var cancelWidthButton:NSLayoutConstraint!
  27. @IBOutlet var applyWidthButton:NSLayoutConstraint!
  28. @objc var submitActionCallback: ((_ isSuccess: Bool)->Void)?
  29. 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"]
  30. @objc static let shared: KMNBetaFeedbackWindowController = {
  31. let windowC = KMNBetaFeedbackWindowController(windowNibName: "KMNBetaFeedbackWindowController")
  32. return windowC
  33. }()
  34. private var _filePaths: [String] = []
  35. var datas: [KMUserFbListModel] = [] {
  36. didSet {
  37. listTabelView.reloadData()
  38. }
  39. }
  40. var isSupportOpenApp: Bool! {
  41. get {
  42. //需拿远端的控制的时间
  43. return KMNBetaFeedbackManager.defalutManager.isSupportOpenApp
  44. }
  45. }
  46. override func windowDidLoad() {
  47. super.windowDidLoad()
  48. self.window?.delegate = self
  49. }
  50. func windowWillClose(_ notification: Notification) {
  51. if(KMNBetaFeedbackManager.defalutManager.isSupportOpenApp == false) {
  52. NSApplication.shared.terminate(nil)
  53. }
  54. }
  55. override func initContentView() {
  56. feedbackTextView.delegate = self
  57. titleLabel.font = ComponentLibrary.shared.getFontFromKey("mac/body-l-medium")
  58. stateLabel.font = ComponentLibrary.shared.getFontFromKey ("mac/body-xs-regular")
  59. feedbackLabel.font = ComponentLibrary.shared.getFontFromKey("mac/body-m-medium")
  60. feedbackTextView.font = ComponentLibrary.shared.getFontFromKey("mac/body-m-medium")
  61. addFileLabel.font = ComponentLibrary.shared.getFontFromKey("mac/body-m-medium")
  62. applyButton.properties = ComponentButtonProperty(type: .primary,
  63. size: .s,
  64. state: .normal,
  65. isDisable: true,
  66. buttonText: KMLocalizedString("Submit"))
  67. applyButton.setTarget(self, action: #selector(submitButtonClicked(_ :)))
  68. applyButton.keyEquivalent = KMKeyEquivalent.enter
  69. cancelButton.properties = ComponentButtonProperty(type: .default_tertiary,
  70. size: .s,
  71. state: .normal,
  72. buttonText: KMLocalizedString("Cancel"))
  73. cancelButton.setTarget(self, action: #selector(cancelButtonClicked(_ :)))
  74. cancelButton.keyEquivalent = KMKeyEquivalent.esc.string()
  75. addFileButton.properties = ComponentButtonProperty(type: .secondary,
  76. size: .s,
  77. state: .normal,
  78. showLeftIcon:true,
  79. buttonText: KMLocalizedString("Add File"),
  80. icon:NSImage(named: "KMNImageNameBetaAddFile"))
  81. addFileButton.setTarget(self, action: #selector(addFileButtonClicked(_ :)))
  82. if(KMMemberInfo.shared.vip_levels != "1") {
  83. if KMLocalizedString("USD", tableName: "MemberCenterLocalizable", comment: "") == "CN" {
  84. stateImageView.image = NSImage(named: "KMNImageNameBetaFeedbackAIZh")
  85. } else {
  86. stateImageView.image = NSImage(named: "KMNImageNameBetaFeedbackAIEn")
  87. }
  88. } else {
  89. if KMLocalizedString("USD", tableName: "MemberCenterLocalizable", comment: "") == "CN" {
  90. stateImageView.image = NSImage(named: "KMNImageNameBetaFeedbackZh")
  91. } else {
  92. stateImageView.image = NSImage(named: "KMNImageNameBetaFeedbackEn")
  93. }
  94. }
  95. if(!isSupportOpenApp) {
  96. stateImageTopConstraint.constant = 46
  97. titleLabel.isHidden = false
  98. } else {
  99. stateImageTopConstraint.constant = 8
  100. titleLabel.isHidden = true
  101. }
  102. listTabelView.dataSource = self
  103. listTabelView.delegate = self
  104. listTabelView.rowHeight = 40
  105. listTabelView.register(.init(nibNamed: "KMNBetaFeedbackTableCellView", bundle: nil), forIdentifier: .init("KMNBetaFeedbackTableCellView"))
  106. listTabelView.enclosingScrollView?.wantsLayer = true
  107. listTabelView.enclosingScrollView?.borderType = .noBorder
  108. listTabelView.enclosingScrollView?.drawsBackground = false
  109. listTabelView.enclosingScrollView?.backgroundColor = .clear
  110. listTabelView.enclosingScrollView?.layer?.backgroundColor = .clear
  111. listTabelView.backgroundColor = .clear
  112. listTabelView.registerForDraggedTypes([.fileURL])
  113. listTabelView.intercellSpacing = NSSize(width: 8, height: 8) // 设置水平和垂直间距
  114. }
  115. override func updateUIThemeColor() {
  116. window?.contentView?.wantsLayer = true
  117. window?.contentView?.layer?.backgroundColor = ComponentLibrary.shared.getComponentColorFromKey("colorBg/popup").cgColor
  118. feedbackBox.borderColor = ComponentLibrary.shared.getComponentColorFromKey("comp-field/colorBorder-nor");
  119. titleLabel.textColor = ComponentLibrary.shared.getComponentColorFromKey("colorError/base")
  120. stateLabel.textColor = ComponentLibrary.shared.getComponentColorFromKey ("colorText/2")
  121. feedbackLabel.textColor = ComponentLibrary.shared.getComponentColorFromKey("comp-form/colorText-label")
  122. feedbackTextView.textColor = ComponentLibrary.shared.getComponentColorFromKey("comp-form/colorText-label")
  123. addFileLabel.textColor = ComponentLibrary.shared.getComponentColorFromKey("comp-form/colorText-label")
  124. cancelButton.reloadData()
  125. applyButton.reloadData()
  126. addFileButton.reloadData()
  127. updateLabel()
  128. }
  129. override func updateUILanguage() {
  130. titleLabel.stringValue = KMLocalizedString("Beta's Usage Rights Has Expired")
  131. stateLabel.stringValue = "* " + KMLocalizedString("Your attachments are strictly confidential and will only be used for the current operation.")
  132. feedbackLabel.stringValue = KMLocalizedString("Feedback (required)")
  133. feedbackTextView.km_placeholderString = KMLocalizedString("Please describe your suggestions in detail so that we can better improve our product.")
  134. addFileLabel.stringValue = KMLocalizedString("Add Attachment Document")
  135. var toolTips = "-" + KMLocalizedString("Limit file size to 20M, upload up to 10 files")
  136. toolTips = toolTips + "\n" + "-" + "No limitations on file formats ( PDF, Excel, Word, Powerpoint, TXT, HTML, MP4, Images and so on)"
  137. addFileToolTip.toolTip = toolTips
  138. cancelButton.properties.buttonText = KMLocalizedString("Cancel")
  139. applyButton.properties.buttonText = KMLocalizedString("Submit")
  140. addFileButton.properties.buttonText = KMLocalizedString("Add File")
  141. cancelButton.reloadData()
  142. applyButton.reloadData()
  143. addFileButton.reloadData()
  144. cancelWidthButton.constant = cancelButton.properties.propertyInfo.viewWidth
  145. applyWidthButton.constant = applyButton.properties.propertyInfo.viewWidth
  146. addFileWidthButton.constant = addFileButton.properties.propertyInfo.viewWidth
  147. updateLabel()
  148. }
  149. func updateLabel() {
  150. let text = KMLocalizedString("Reward will be distributed to your PDF Reader Pro account: %@")
  151. let email = KMMemberInfo.shared.userEmail
  152. let fullString = String(format: text, email)
  153. let attrStr = NSMutableAttributedString(string: fullString)
  154. attrStr.addAttribute(.font, value: ComponentLibrary.shared.getFontFromKey("mac/body-xs-regular"), range: NSRange(location: 0, length: fullString.count))
  155. attrStr.addAttribute(.foregroundColor, value: ComponentLibrary.shared.getComponentColorFromKey("colorText/2"), range: NSRange(location: 0, length: fullString.count))
  156. attrStr.addAttribute(.foregroundColor, value: ComponentLibrary.shared.getComponentColorFromKey("colorPrimary/textLight"), range: (fullString as NSString).range(of: email))
  157. let paragraphStyle = NSMutableParagraphStyle()
  158. paragraphStyle.alignment = .center
  159. attrStr.addAttribute(.paragraphStyle, value: paragraphStyle, range: NSRange(location: 0, length: fullString.count))
  160. // 应用富文本
  161. emailLabel.attributedStringValue = attrStr
  162. let linkText = KMLocalizedString("Please visit our Official Website to Sign in to check your reward.")
  163. let linkAttrStr = NSMutableAttributedString(string: linkText)
  164. let link = KMLocalizedString("Official Website")
  165. // 设置超链接文本属性
  166. let url = URL(string: "https://www.pdfreaderpro.com")!
  167. linkAttrStr.addAttribute(.link, value: url, range: (linkText as NSString).range(of: link))
  168. // 设置字体和颜色
  169. linkAttrStr.addAttribute(.font, value: ComponentLibrary.shared.getFontFromKey("mac/body-xs-regular"), range: NSRange(location: 0, length: linkText.count))
  170. linkAttrStr.addAttribute(.foregroundColor, value: ComponentLibrary.shared.getComponentColorFromKey("colorText/2"), range: NSRange(location: 0, length: linkText.count))
  171. linkAttrStr.addAttribute(.foregroundColor, value: ComponentLibrary.shared.getComponentColorFromKey("colorPrimary/textLight"), range: (linkText as NSString).range(of: link))
  172. linkAttrStr.addAttribute(.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: (linkText as NSString).range(of: link))
  173. linkAttrStr.addAttribute(.paragraphStyle, value: paragraphStyle, range: NSRange(location: 0, length: linkText.count))
  174. webLabel.isEditable = false
  175. webLabel.isSelectable = true
  176. webLabel.allowsEditingTextAttributes = true
  177. // 应用富文本到 NSTextField
  178. webLabel.attributedStringValue = linkAttrStr
  179. }
  180. private func _updateListData() {
  181. var datas: [KMUserFbListModel] = []
  182. let maxSize: Double = 20 * 1024 * 1024
  183. var fileSize: Double = 0
  184. var filePaths: [String] = []
  185. var showFileSizeLimit = false
  186. var showFileCountLimit = false
  187. for (i, fileP) in _filePaths.enumerated() {
  188. let model = KMUserFbListModel()
  189. model.filePath = fileP
  190. let url = URL(fileURLWithPath: fileP)
  191. model.fileName = url.lastPathComponent
  192. let attri = try?FileManager.default.attributesOfItem(atPath: fileP)
  193. model.fileSize = attri?[FileAttributeKey.size] as? Double ?? 0
  194. fileSize = model.fileSize
  195. if fileSize >= maxSize {
  196. showFileSizeLimit = true
  197. continue
  198. }
  199. if i >= 10 {
  200. showFileCountLimit = true
  201. break
  202. }
  203. model.fileSizeString = self.fileSizeString(model.fileSize)
  204. datas.append(model)
  205. filePaths.append(fileP)
  206. }
  207. _filePaths = filePaths
  208. if showFileCountLimit {
  209. _ = KMNCustomAlertView.alertView(message: KMLocalizedString("Add failed: upload up to 10 files"),
  210. type: .warning,
  211. fromView: self.window?.contentView ?? NSView(),
  212. point:CGPointMake(CGRectGetMidX(self.window?.contentView?.bounds ?? CGRect.zero), CGRectGetMidY(self.window?.contentView?.bounds ?? CGRect.zero)))
  213. } else if showFileSizeLimit {
  214. _ = KMNCustomAlertView.alertView(message: KMLocalizedString("Add failed: attachment size cannot exceed 20M"),
  215. type: .warning,
  216. fromView: self.window?.contentView ?? NSView(),
  217. point:CGPointMake(CGRectGetMidX(self.window?.contentView?.bounds ?? CGRect.zero), CGRectGetMidY(self.window?.contentView?.bounds ?? CGRect.zero)))
  218. }
  219. if datas.count >= 10 {
  220. addFileButton.properties.state = .pressed
  221. } else {
  222. addFileButton.properties.state = .normal
  223. }
  224. self.datas = datas
  225. }
  226. func fileSizeString(_ fSize: Double) -> String {
  227. let fileSize = fSize / 1024
  228. let size = fileSize >= 1024 ? (fileSize < 1048576 ? fileSize/1024 : fileSize/1048576.0) : fileSize
  229. let unit = fileSize >= 1024 ? (fileSize < 1048576 ? "M" : "G") : "K"
  230. return String(format: "%0.1f %@", size, unit)
  231. }
  232. private func _fileFormatIsContains(_ exn: String) -> Bool {
  233. let _exn = exn.lowercased()
  234. return _fileFormats.contains(_exn)
  235. }
  236. private func _addWaitingView(to view: NSView) {
  237. _removeWaitingView(from: view)
  238. let waitingView = WaitingView(frame: view.bounds)
  239. waitingView.autoresizingMask = [.width, .height]
  240. view.addSubview(waitingView)
  241. waitingView.startAnimation()
  242. }
  243. private func _removeWaitingView(from view: NSView) {
  244. KMMainThreadExecute {
  245. for subview in view.subviews {
  246. if subview is WaitingView {
  247. subview.removeFromSuperview()
  248. break
  249. }
  250. }
  251. }
  252. }
  253. //MARK: - Action
  254. @objc func cancelButtonClicked(_ sender: NSView) {
  255. close()
  256. own_closeEndSheet()
  257. }
  258. @objc func submitButtonClicked(_ sender: NSView) {
  259. _addWaitingView(to: self.window?.contentView ?? NSView())
  260. KMNBetaFeedbackManager.defalutManager.feekbackAction(filePaths: self._filePaths, content: feedbackTextView.string) {[weak self] success, result in
  261. if (success) {
  262. if(!(self?.isSupportOpenApp == false)) {
  263. } else {
  264. self?.submitActionCallback?(true)
  265. }
  266. self?._removeWaitingView(from: self?.window?.contentView ?? NSView())
  267. KMNBetaFeedbackManager.defalutManager.resetHaveFeedback()
  268. KMNBetaFeedbackManager.defalutManager.havingFeekbackAction { success, result in
  269. };
  270. self?.close()
  271. self?.own_closeEndSheet()
  272. }
  273. }
  274. }
  275. @objc func addFileButtonClicked(_ sender: NSView) {
  276. let panel = NSOpenPanel()
  277. panel.allowedFileTypes = _fileFormats
  278. panel.allowsMultipleSelection = true
  279. panel.beginSheetModal(for: self.window!) { [self] resp in
  280. if resp == .cancel {
  281. return
  282. }
  283. for url in panel.urls {
  284. self._filePaths.append(url.path)
  285. }
  286. self.addFileButton.properties.state = .normal
  287. self.addFileButton.reloadData()
  288. _updateListData()
  289. }
  290. }
  291. }
  292. extension KMNBetaFeedbackWindowController: NSTableViewDelegate, NSTableViewDataSource {
  293. func numberOfRows(in tableView: NSTableView) -> Int {
  294. return datas.count
  295. }
  296. func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
  297. var cell = tableView.makeView(withIdentifier: .init("KMNBetaFeedbackTableCellView"), owner: self)
  298. if cell == nil {
  299. cell = KMNBetaFeedbackTableCellView.createFromNib()
  300. }
  301. let cellView = cell as? KMNBetaFeedbackTableCellView
  302. let model = datas[row]
  303. cellView?.fileNameLabel.stringValue = model.fileName ?? ""
  304. cellView?.fileSizeLabel.stringValue = model.fileSizeString ?? ""
  305. cellView?.deleteItemClick = { [weak self] idx in
  306. if let cnt = self?._filePaths.count, row < cnt {
  307. self?._filePaths.remove(at: row)
  308. self?._updateListData()
  309. }
  310. }
  311. return cell
  312. }
  313. func tableView(_ tableView: NSTableView, validateDrop info: NSDraggingInfo, proposedRow row: Int, proposedDropOperation dropOperation: NSTableView.DropOperation) -> NSDragOperation {
  314. let datas = _filePaths.count
  315. if datas >= 10 {
  316. return NSDragOperation(rawValue: 0)
  317. }
  318. return .generic
  319. }
  320. func tableView(_ tableView: NSTableView, acceptDrop info: NSDraggingInfo, row: Int, dropOperation: NSTableView.DropOperation) -> Bool {
  321. let pborad = info.draggingPasteboard
  322. guard let _ = pborad.availableType(from: [.fileURL]) else {
  323. return false
  324. }
  325. guard let items = pborad.pasteboardItems else {
  326. return false
  327. }
  328. for item in items {
  329. let string = item.propertyList(forType: .fileURL) as? String ?? ""
  330. if let path = URL(string: string)?.path {
  331. let contains = _fileFormatIsContains(URL(string: string)!.pathExtension)
  332. if contains {
  333. _filePaths.append(path)
  334. }
  335. }
  336. }
  337. self._updateListData()
  338. return true
  339. }
  340. }
  341. extension KMNBetaFeedbackWindowController: NSTextViewDelegate {
  342. func textViewDidChangeSelection(_ notification: Notification) {
  343. if feedbackTextView.isEqual(to: notification.object) {
  344. if(feedbackTextView.string.count > 0) {
  345. applyButton.properties.isDisabled = false
  346. feedbackTextView.placeholderLabel.isHidden = true
  347. } else {
  348. feedbackTextView.placeholderLabel.isHidden = false
  349. applyButton.properties.isDisabled = true
  350. }
  351. applyButton.reloadData()
  352. }
  353. }
  354. }