KMNewUserGuideWindowController.swift 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  1. //
  2. // KMNewUserGuideWindowController.swift
  3. // PDF Reader Pro
  4. //
  5. // Created by User-Tangchao on 2024/12/18.
  6. //
  7. import Cocoa
  8. @objc enum KMNewUserGuideType: Int {
  9. case none = 0
  10. case ai
  11. case note
  12. case editPDF
  13. case pageEdit
  14. case convert
  15. case ocr
  16. case fillForms
  17. }
  18. // 新手引导 Layout 模块
  19. @objcMembers class KMNewUserGuideWindowController: KMBaseWindowController {
  20. @IBOutlet weak var contentBox: NSBox!
  21. @IBOutlet weak var backgroundIv: NSImageView!
  22. @IBOutlet weak var titleLabel: NSTextField!
  23. @IBOutlet weak var scrollView: NSScrollView!
  24. @IBOutlet weak var tableView: NSTableView!
  25. @IBOutlet weak var rightBox: NSBox!
  26. static let shared = KMNewUserGuideWindowController(windowNibName: "KMNewUserGuideWindowController")
  27. private lazy var displayView_ = KMNewUserGuideDisplayView()
  28. private var datas_: [KMNewUserGuideModel] = []
  29. var showType: KMNewUserGuideType = .ai {
  30. didSet {
  31. selectType(showType)
  32. }
  33. }
  34. // 28+8
  35. private var defaultViewH_: CGFloat = 36
  36. private let hasShowKey_ = "newUserGuidehasShow"
  37. override func windowDidLoad() {
  38. super.windowDidLoad()
  39. window?.title = ""
  40. window?.standardWindowButton(.zoomButton)?.isHidden = true
  41. window?.standardWindowButton(.miniaturizeButton)?.isHidden = true
  42. let closeButton = window?.standardWindowButton(.closeButton)
  43. closeButton?.target = self
  44. closeButton?.action = #selector(closeAction)
  45. backgroundIv.image = NSImage(named: "KMImageNameNewUserGuideBg")
  46. backgroundIv.image?.size = window?.frame.size ?? .zero
  47. titleLabel.font = .SFProTextSemiboldFont(20)
  48. titleLabel.stringValue = NSLocalizedString("The Beginner's Guide", comment: "")
  49. tableView.delegate = self
  50. tableView.dataSource = self
  51. tableView.enclosingScrollView?.borderType = .noBorder
  52. tableView.backgroundColor = .clear
  53. tableView.enclosingScrollView?.drawsBackground = false
  54. rightBox.borderWidth = 0
  55. rightBox.contentView = displayView_
  56. displayView_.contentInset = .init(top: 0, left: 10, bottom: 34, right: 18)
  57. interfaceThemeDidChanged(self.window?.appearance?.name ?? .vibrantLight)
  58. _initData()
  59. tableView.reloadData()
  60. }
  61. // MARK: - Private Methods
  62. private func _initData() {
  63. datas_.removeAll()
  64. let types: [KMNewUserGuideType] = [.ai, .editPDF, .note, .pageEdit, .convert, .ocr, .fillForms]
  65. for type in types {
  66. datas_.append(_initModel(type: type))
  67. }
  68. }
  69. private func _initModel(type: KMNewUserGuideType) -> KMNewUserGuideModel {
  70. let model = KMNewUserGuideModel()
  71. model.type = type
  72. let maxWidth: CGFloat = 224
  73. var listDespColor = NSColor(hex: "#42464D")
  74. if KMAppearance.isDarkMode() {
  75. listDespColor = .white
  76. }
  77. if type == .ai {
  78. #if VERSION_FREE
  79. model.name = NSLocalizedString("AI Credits Giveaway", comment: "")
  80. let attri = NSAttributedString(string: NSLocalizedString("Free 40 AI Credits for 7-day VIP Free Trial Users", comment: ""), attributes: [
  81. .font : NSFont.SFProTextRegularFont(11),
  82. .foregroundColor : listDespColor
  83. ])
  84. #else
  85. model.name = NSLocalizedString("Edit with AI Robot", comment: "")
  86. let attri = NSAttributedString(string: NSLocalizedString("AI Translate, Summarize, Proofread, Rewrite", comment: ""), attributes: [
  87. .font : NSFont.SFProTextRegularFont(11),
  88. .foregroundColor : listDespColor
  89. ])
  90. #endif
  91. model.imageName = "newUserGuideAITools"
  92. model.despAttri = attri
  93. model.viewH = _calculateViewHeight(model: model, maxWidth: maxWidth)
  94. } else if type == .note {
  95. model.name = NSLocalizedString("Annotate PDF", comment: "")
  96. model.imageName = "newUserGuideAnnotation"
  97. let attri = NSAttributedString(string: NSLocalizedString("Add Annotations, Images, and Stamps to PDFs", comment: ""), attributes: [
  98. .font : NSFont.SFProTextRegularFont(11),
  99. .foregroundColor : listDespColor
  100. ])
  101. model.despAttri = attri
  102. model.viewH = _calculateViewHeight(model: model, maxWidth: maxWidth)
  103. } else if type == .editPDF {
  104. model.name = NSLocalizedString("Edit PDF", comment: "")
  105. model.imageName = "newUserGuideEdit"
  106. let attri = NSAttributedString(string: NSLocalizedString("Edit PDF Text, Images, and Watermarks", comment: ""), attributes: [
  107. .font : NSFont.SFProTextRegularFont(11),
  108. .foregroundColor : listDespColor
  109. ])
  110. model.despAttri = attri
  111. model.viewH = _calculateViewHeight(model: model, maxWidth: maxWidth)
  112. } else if type == .pageEdit {
  113. model.name = NSLocalizedString("Page Edit", comment: "")
  114. model.imageName = "KMImageNameNewUserGuidePageEdit"
  115. let attri = NSAttributedString(string: NSLocalizedString("Insert, Delete, Rotate, and Extract the Pages in PDFs", comment: ""), attributes: [
  116. .font : NSFont.SFProTextRegularFont(11),
  117. .foregroundColor : listDespColor
  118. ])
  119. model.despAttri = attri
  120. model.viewH = _calculateViewHeight(model: model, maxWidth: maxWidth)
  121. } else if type == .convert {
  122. model.name = NSLocalizedString("PDF Converter", comment: "")
  123. model.imageName = "KMImageNameNewUserGuideConvert"
  124. let attri = NSAttributedString(string: NSLocalizedString("Convert to/from MS Office, Images, and More", comment: ""), attributes: [
  125. .font : NSFont.SFProTextRegularFont(11),
  126. .foregroundColor : listDespColor
  127. ])
  128. model.despAttri = attri
  129. model.viewH = _calculateViewHeight(model: model, maxWidth: maxWidth)
  130. } else if type == .ocr {
  131. model.name = NSLocalizedString("OCR", comment: "")
  132. model.imageName = "KMImageNameNewUserGuideOCR"
  133. let attri = NSAttributedString(string: NSLocalizedString("Scanned PDFs to Editable & Searchable PDFs in 90+ Languages", comment: ""), attributes: [
  134. .font : NSFont.SFProTextRegularFont(11),
  135. .foregroundColor : listDespColor
  136. ])
  137. model.despAttri = attri
  138. model.viewH = _calculateViewHeight(model: model, maxWidth: maxWidth)
  139. } else if type == .fillForms {
  140. model.name = NSLocalizedString("Create & Fill Forms", comment: "")
  141. model.imageName = "KMImageNameNewUserGuideFillForms"
  142. let attri = NSAttributedString(string: NSLocalizedString("Support to Create & Fill Interactive PDF Forms", comment: ""), attributes: [
  143. .font : NSFont.SFProTextRegularFont(11),
  144. .foregroundColor : listDespColor
  145. ])
  146. model.despAttri = attri
  147. model.viewH = _calculateViewHeight(model: model, maxWidth: maxWidth)
  148. }
  149. return model
  150. }
  151. private func _calculateViewHeight(model: KMNewUserGuideModel, maxWidth: CGFloat) -> CGFloat {
  152. guard let attri = model.despAttri else {
  153. return defaultViewH_
  154. }
  155. let size = attri.boundingRect(with: .init(width: maxWidth, height: CGFLOAT_MAX), options: .usesLineFragmentOrigin).size
  156. return (4+20+8+size.height+4)+4+4
  157. }
  158. private func _trackEvent(type: KMNewUserGuideType) {
  159. if type == .ai {
  160. trackEvent(eventName: "PUW", params: ["PUW_Btn" : "FunctionIntro_AI"], platform: .AppCenter)
  161. } else if type == .note {
  162. trackEvent(eventName: "PUW", params: ["PUW_Btn" : "FunctionIntro_AnnotatePDF"], platform: .AppCenter)
  163. } else if type == .editPDF {
  164. trackEvent(eventName: "PUW", params: ["PUW_Btn" : "FunctionIntro_EditPDF"], platform: .AppCenter)
  165. } else if type == .pageEdit {
  166. trackEvent(eventName: "PUW", params: ["PUW_Btn" : "FunctionIntro_PageEditor"], platform: .AppCenter)
  167. } else if type == .convert {
  168. trackEvent(eventName: "PUW", params: ["PUW_Btn" : "FunctionIntro_Convert"], platform: .AppCenter)
  169. } else if type == .ocr {
  170. trackEvent(eventName: "PUW", params: ["PUW_Btn" : "FunctionIntro_OCR"], platform: .AppCenter)
  171. } else if type == .fillForms {
  172. trackEvent(eventName: "PUW", params: ["PUW_Btn" : "FunctionIntro_Form"], platform: .AppCenter)
  173. }
  174. }
  175. private func _fetchBrowserWindowC() -> KMBrowserWindowController? {
  176. if let win = NSApp.mainWindow as? CTBrowserWindow {
  177. return win.windowController as? KMBrowserWindowController
  178. }
  179. if let win = NSApp.keyWindow as? CTBrowserWindow {
  180. return win.windowController as? KMBrowserWindowController
  181. }
  182. for win in NSApp.windows {
  183. if win.isVisible == false {
  184. continue
  185. }
  186. guard let winC = win.windowController as? KMBrowserWindowController else {
  187. continue
  188. }
  189. if winC.browser == nil {
  190. continue
  191. }
  192. return winC
  193. }
  194. return nil
  195. }
  196. private func _showPopGuide() {
  197. if KMMemberInfo.shared.isLogin == false { // 未登陆
  198. return
  199. }
  200. if KMMemberInfo.shared.isMemberAllFunction { // 有权益
  201. return
  202. }
  203. guard let winC = _fetchBrowserWindowC() else { // 没找到有效的窗口
  204. return
  205. }
  206. guard let itemV = winC.rightMessageVC?.discountItemView, itemV.isHidden == false else {
  207. return
  208. }
  209. guard let doc = winC.browser.activeTabContents() as? KMMainDocument else {
  210. return
  211. }
  212. if doc.isHome || doc.isNewTab {
  213. doc.homeViewController?.loadOpenFileFunctionGuide(.messageDiscount)
  214. return
  215. }
  216. doc.mainViewController?.loadOpenFileFunctionGuide(.messageDiscount)
  217. }
  218. override func interfaceThemeDidChanged(_ appearance: NSAppearance.Name) {
  219. super.interfaceThemeDidChanged(appearance)
  220. KMMainThreadExecute {
  221. var listDespColor = NSColor(hex: "#42464D")
  222. if KMAppearance.isDarkMode() {
  223. self.titleLabel.textColor = .white
  224. listDespColor = .white
  225. } else {
  226. self.titleLabel.textColor = NSColor(hex: "#002143")
  227. }
  228. let maxWidth: CGFloat = 224
  229. for model in self.datas_ {
  230. if let data = model.despAttri?.string {
  231. let attri = NSAttributedString(string: data, attributes: [
  232. .font : NSFont.SFProTextRegularFont(11),
  233. .foregroundColor : listDespColor
  234. ])
  235. model.despAttri = attri
  236. model.viewH = self._calculateViewHeight(model: model, maxWidth: maxWidth)
  237. }
  238. }
  239. self.tableView.reloadData()
  240. }
  241. }
  242. // MARK: - Actions
  243. @objc func closeAction() {
  244. _showPopGuide()
  245. closeWindow()
  246. }
  247. // MARK: - public Methods
  248. public func openWindow() {
  249. self.showWindow(nil)
  250. self._showCenter(animate: true)
  251. self.window?.makeKeyAndOrderFront(nil)
  252. selectType(showType)
  253. saveShowRecord()
  254. trackEvent(eventName: "PUW", params: ["PUW_Exposure" : "PUW_FunctionIntro"], platform: .AppCenter)
  255. }
  256. public func selectType(_ type: KMNewUserGuideType) {
  257. var selectedModel: KMNewUserGuideModel?
  258. for model in datas_ {
  259. model.isSelected = (model.type == type)
  260. if model.isSelected {
  261. selectedModel = model
  262. }
  263. }
  264. KMMainThreadExecute {
  265. // self.tableView.reloadData()
  266. self.tableView.animator().reloadData()
  267. if let data = selectedModel?.imageName {
  268. self.displayView_.imageView.image = NSImage(named: data)
  269. self.displayView_.imageView.image?.size = NSMakeSize(480, 320)
  270. }
  271. }
  272. }
  273. public func closeWindow() {
  274. window?.close()
  275. }
  276. public func saveShowRecord() {
  277. KMDataManager.ud_set(true, forKey: hasShowKey_)
  278. }
  279. public func needShow() -> Bool {
  280. // 已经显示
  281. if KMDataManager.ud_bool(forKey: hasShowKey_) {
  282. return false
  283. }
  284. #if VERSION_FREE
  285. if KMMemberInfo.shared.isMemberAllFunction { // 有权益(本地 或 账号)
  286. return false
  287. }
  288. #endif
  289. // 不是新用户
  290. if KMMemberInfo.shared.isFreeAccount == false {
  291. return false
  292. }
  293. // 新用户首次显示
  294. return true
  295. }
  296. private func _showCenter(animate: Bool){
  297. guard let screenFrame = NSScreen.main?.frame else {
  298. return
  299. }
  300. var frame: CGRect = self.window!.frame
  301. frame.origin.y = (screenFrame.size.height-frame.size.height)*0.5
  302. frame.origin.x = (screenFrame.size.width-frame.size.width)*0.5
  303. self.window?.setFrame(frame, display: true, animate: animate)
  304. }
  305. }
  306. // MARK: - NSTableViewDelegate, NSTableViewDataSource
  307. extension KMNewUserGuideWindowController: NSTableViewDelegate, NSTableViewDataSource {
  308. func numberOfRows(in tableView: NSTableView) -> Int {
  309. return datas_.count
  310. }
  311. func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat {
  312. let model = datas_.safe_element(for: row) as? KMNewUserGuideModel
  313. let isSelected = model?.isSelected ?? false
  314. if isSelected == false {
  315. return defaultViewH_
  316. }
  317. return model?.viewH ?? defaultViewH_
  318. }
  319. func tableView(_ tableView: NSTableView, rowViewForRow row: Int) -> NSTableRowView? {
  320. let rowView = KMTableRowView()
  321. rowView.selectionBackgroundColorBlock = {
  322. return .clear
  323. }
  324. return rowView
  325. }
  326. func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
  327. var cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "CellId"), owner: self) as? KMNewUserGuideCellView
  328. if cell == nil {
  329. cell = KMNewUserGuideCellView()
  330. }
  331. cell?.contentInset = .init(top: 4, left: 0, bottom: 4, right: 0)
  332. guard let model = datas_.safe_element(for: row) as? KMNewUserGuideModel else {
  333. return cell
  334. }
  335. cell?.titleLabel.font = .SFProTextBoldFont(14)
  336. cell?.titleLabel.stringValue = model.name
  337. if let data = model.despAttri {
  338. cell?.subTitleLabel.attributedStringValue = data
  339. }
  340. let isSelected = model.isSelected
  341. cell?.subTitleLabel.isHidden = !isSelected
  342. cell?.leftLine.isHidden = !isSelected
  343. cell?.backgroundView.layer?.cornerRadius = 4
  344. if KMAppearance.isDarkMode() {
  345. cell?.titleLabel.textColor = .white
  346. cell?.leftLine.layer?.backgroundColor = NSColor(hex: "#227AFF").cgColor
  347. if isSelected {
  348. cell?.backgroundView.layer?.backgroundColor = NSColor(hex: "#869297").withAlphaComponent(0.4).cgColor
  349. } else {
  350. cell?.backgroundView.layer?.backgroundColor = NSColor.black.withAlphaComponent(0.8).cgColor
  351. }
  352. } else {
  353. cell?.titleLabel.textColor = NSColor(hex: "#42464D")
  354. cell?.leftLine.layer?.backgroundColor = NSColor(hex: "#4982E6").cgColor
  355. if isSelected {
  356. cell?.backgroundView.layer?.backgroundColor = NSColor(hex: "#DCF4FF").withAlphaComponent(0.4).cgColor
  357. } else {
  358. cell?.backgroundView.layer?.backgroundColor = .white
  359. }
  360. }
  361. return cell
  362. }
  363. func tableViewSelectionDidChange(_ notification: Notification) {
  364. if tableView.isEqual(to: notification.object) {
  365. let row = tableView.selectedRow
  366. guard let model = datas_.safe_element(for: row) as? KMNewUserGuideModel else {
  367. return
  368. }
  369. selectType(model.type)
  370. _trackEvent(type: model.type)
  371. }
  372. }
  373. }