KMNewUserGuideWindowController.swift 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  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. model.name = NSLocalizedString("AI Credits Giveaway", comment: "")
  79. model.imageName = "newUserGuideAITools"
  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. model.despAttri = attri
  85. model.viewH = _calculateViewHeight(model: model, maxWidth: maxWidth)
  86. } else if type == .note {
  87. model.name = NSLocalizedString("Annotate PDF", comment: "")
  88. model.imageName = "newUserGuideAnnotation"
  89. let attri = NSAttributedString(string: NSLocalizedString("Add Annotations, Images, and Stamps to PDFs", comment: ""), attributes: [
  90. .font : NSFont.SFProTextRegularFont(11),
  91. .foregroundColor : listDespColor
  92. ])
  93. model.despAttri = attri
  94. model.viewH = _calculateViewHeight(model: model, maxWidth: maxWidth)
  95. } else if type == .editPDF {
  96. model.name = NSLocalizedString("Edit PDF", comment: "")
  97. model.imageName = "newUserGuideEdit"
  98. let attri = NSAttributedString(string: NSLocalizedString("Edit PDF Text, Images, and Watermarks", comment: ""), attributes: [
  99. .font : NSFont.SFProTextRegularFont(11),
  100. .foregroundColor : listDespColor
  101. ])
  102. model.despAttri = attri
  103. model.viewH = _calculateViewHeight(model: model, maxWidth: maxWidth)
  104. } else if type == .pageEdit {
  105. model.name = NSLocalizedString("Page Edit", comment: "")
  106. model.imageName = "KMImageNameNewUserGuidePageEdit"
  107. let attri = NSAttributedString(string: NSLocalizedString("Insert, Delete, Rotate, and Extract the Pages in PDFs", comment: ""), attributes: [
  108. .font : NSFont.SFProTextRegularFont(11),
  109. .foregroundColor : listDespColor
  110. ])
  111. model.despAttri = attri
  112. model.viewH = _calculateViewHeight(model: model, maxWidth: maxWidth)
  113. } else if type == .convert {
  114. model.name = NSLocalizedString("PDF Converter", comment: "")
  115. model.imageName = "KMImageNameNewUserGuideConvert"
  116. let attri = NSAttributedString(string: NSLocalizedString("Convert to/from MS Office, Images, and More", comment: ""), attributes: [
  117. .font : NSFont.SFProTextRegularFont(11),
  118. .foregroundColor : listDespColor
  119. ])
  120. model.despAttri = attri
  121. model.viewH = _calculateViewHeight(model: model, maxWidth: maxWidth)
  122. } else if type == .ocr {
  123. model.name = NSLocalizedString("OCR", comment: "")
  124. model.imageName = "KMImageNameNewUserGuideOCR"
  125. let attri = NSAttributedString(string: NSLocalizedString("Scanned PDFs to Editable & Searchable PDFs in 90+ Languages", comment: ""), attributes: [
  126. .font : NSFont.SFProTextRegularFont(11),
  127. .foregroundColor : listDespColor
  128. ])
  129. model.despAttri = attri
  130. model.viewH = _calculateViewHeight(model: model, maxWidth: maxWidth)
  131. } else if type == .fillForms {
  132. model.name = NSLocalizedString("Create & Fill Forms", comment: "")
  133. model.imageName = "KMImageNameNewUserGuideFillForms"
  134. let attri = NSAttributedString(string: NSLocalizedString("Support to Create & Fill Interactive PDF Forms", comment: ""), attributes: [
  135. .font : NSFont.SFProTextRegularFont(11),
  136. .foregroundColor : listDespColor
  137. ])
  138. model.despAttri = attri
  139. model.viewH = _calculateViewHeight(model: model, maxWidth: maxWidth)
  140. }
  141. return model
  142. }
  143. private func _calculateViewHeight(model: KMNewUserGuideModel, maxWidth: CGFloat) -> CGFloat {
  144. guard let attri = model.despAttri else {
  145. return defaultViewH_
  146. }
  147. let size = attri.boundingRect(with: .init(width: maxWidth, height: CGFLOAT_MAX), options: .usesLineFragmentOrigin).size
  148. return (4+20+8+size.height+4)+4+4
  149. }
  150. private func _trackEvent(type: KMNewUserGuideType) {
  151. if type == .ai {
  152. trackEvent(eventName: "PUW", params: ["PUW_Btn" : "FunctionIntro_AI"], platform: .AppCenter)
  153. } else if type == .note {
  154. trackEvent(eventName: "PUW", params: ["PUW_Btn" : "FunctionIntro_AnnotatePDF"], platform: .AppCenter)
  155. } else if type == .editPDF {
  156. trackEvent(eventName: "PUW", params: ["PUW_Btn" : "FunctionIntro_EditPDF"], platform: .AppCenter)
  157. } else if type == .pageEdit {
  158. trackEvent(eventName: "PUW", params: ["PUW_Btn" : "FunctionIntro_PageEditor"], platform: .AppCenter)
  159. } else if type == .convert {
  160. trackEvent(eventName: "PUW", params: ["PUW_Btn" : "FunctionIntro_Convert"], platform: .AppCenter)
  161. } else if type == .ocr {
  162. trackEvent(eventName: "PUW", params: ["PUW_Btn" : "FunctionIntro_OCR"], platform: .AppCenter)
  163. } else if type == .fillForms {
  164. trackEvent(eventName: "PUW", params: ["PUW_Btn" : "FunctionIntro_Form"], platform: .AppCenter)
  165. }
  166. }
  167. private func _fetchBrowserWindowC() -> KMBrowserWindowController? {
  168. if let win = NSApp.mainWindow as? CTBrowserWindow {
  169. return win.windowController as? KMBrowserWindowController
  170. }
  171. if let win = NSApp.keyWindow as? CTBrowserWindow {
  172. return win.windowController as? KMBrowserWindowController
  173. }
  174. for win in NSApp.windows {
  175. if win.isVisible == false {
  176. continue
  177. }
  178. guard let winC = win.windowController as? KMBrowserWindowController else {
  179. continue
  180. }
  181. if winC.browser == nil {
  182. continue
  183. }
  184. return winC
  185. }
  186. return nil
  187. }
  188. private func _showPopGuide() {
  189. if KMMemberInfo.shared.isLogin == false { // 未登陆
  190. return
  191. }
  192. if KMMemberInfo.shared.isMemberAllFunction { // 有权益
  193. return
  194. }
  195. guard let winC = _fetchBrowserWindowC() else { // 没找到有效的窗口
  196. return
  197. }
  198. guard let itemV = winC.rightMessageVC?.discountItemView, itemV.isHidden == false else {
  199. return
  200. }
  201. guard let doc = winC.browser.activeTabContents() as? KMMainDocument else {
  202. return
  203. }
  204. if doc.isHome || doc.isNewTab {
  205. doc.homeViewController?.loadOpenFileFunctionGuide(.messageDiscount)
  206. return
  207. }
  208. doc.mainViewController?.loadOpenFileFunctionGuide(.messageDiscount)
  209. }
  210. override func interfaceThemeDidChanged(_ appearance: NSAppearance.Name) {
  211. super.interfaceThemeDidChanged(appearance)
  212. KMMainThreadExecute {
  213. var listDespColor = NSColor(hex: "#42464D")
  214. if KMAppearance.isDarkMode() {
  215. self.titleLabel.textColor = .white
  216. listDespColor = .white
  217. } else {
  218. self.titleLabel.textColor = NSColor(hex: "#002143")
  219. }
  220. let maxWidth: CGFloat = 224
  221. for model in self.datas_ {
  222. if let data = model.despAttri?.string {
  223. let attri = NSAttributedString(string: data, attributes: [
  224. .font : NSFont.SFProTextRegularFont(11),
  225. .foregroundColor : listDespColor
  226. ])
  227. model.despAttri = attri
  228. model.viewH = self._calculateViewHeight(model: model, maxWidth: maxWidth)
  229. }
  230. }
  231. self.tableView.reloadData()
  232. }
  233. }
  234. // MARK: - Actions
  235. @objc func closeAction() {
  236. _showPopGuide()
  237. closeWindow()
  238. }
  239. // MARK: - public Methods
  240. public func openWindow() {
  241. self.showWindow(nil)
  242. self._showCenter(animate: true)
  243. self.window?.makeKeyAndOrderFront(nil)
  244. selectType(showType)
  245. saveShowRecord()
  246. trackEvent(eventName: "PUW", params: ["PUW_Exposure" : "PUW_FunctionIntro"], platform: .AppCenter)
  247. }
  248. public func selectType(_ type: KMNewUserGuideType) {
  249. var selectedModel: KMNewUserGuideModel?
  250. for model in datas_ {
  251. model.isSelected = (model.type == type)
  252. if model.isSelected {
  253. selectedModel = model
  254. }
  255. }
  256. KMMainThreadExecute {
  257. // self.tableView.reloadData()
  258. self.tableView.animator().reloadData()
  259. if let data = selectedModel?.imageName {
  260. self.displayView_.imageView.image = NSImage(named: data)
  261. self.displayView_.imageView.image?.size = NSMakeSize(480, 320)
  262. }
  263. }
  264. }
  265. public func closeWindow() {
  266. window?.close()
  267. }
  268. public func saveShowRecord() {
  269. KMDataManager.ud_set(true, forKey: hasShowKey_)
  270. }
  271. public func needShow() -> Bool {
  272. // 已经显示
  273. if KMDataManager.ud_bool(forKey: hasShowKey_) {
  274. return false
  275. }
  276. if KMMemberInfo.shared.isMemberAllFunction { // 有权益(本地 或 账号)
  277. return false
  278. }
  279. // 不是新用户
  280. if KMMemberInfo.shared.isFreeAccount == false {
  281. return false
  282. }
  283. // 新用户首次显示
  284. return true
  285. }
  286. private func _showCenter(animate: Bool){
  287. guard let screenFrame = NSScreen.main?.frame else {
  288. return
  289. }
  290. var frame: CGRect = self.window!.frame
  291. frame.origin.y = (screenFrame.size.height-frame.size.height)*0.5
  292. frame.origin.x = (screenFrame.size.width-frame.size.width)*0.5
  293. self.window?.setFrame(frame, display: true, animate: animate)
  294. }
  295. }
  296. // MARK: - NSTableViewDelegate, NSTableViewDataSource
  297. extension KMNewUserGuideWindowController: NSTableViewDelegate, NSTableViewDataSource {
  298. func numberOfRows(in tableView: NSTableView) -> Int {
  299. return datas_.count
  300. }
  301. func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat {
  302. let model = datas_.safe_element(for: row) as? KMNewUserGuideModel
  303. let isSelected = model?.isSelected ?? false
  304. if isSelected == false {
  305. return defaultViewH_
  306. }
  307. return model?.viewH ?? defaultViewH_
  308. }
  309. func tableView(_ tableView: NSTableView, rowViewForRow row: Int) -> NSTableRowView? {
  310. let rowView = KMTableRowView()
  311. rowView.selectionBackgroundColorBlock = {
  312. return .clear
  313. }
  314. return rowView
  315. }
  316. func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
  317. var cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "CellId"), owner: self) as? KMNewUserGuideCellView
  318. if cell == nil {
  319. cell = KMNewUserGuideCellView()
  320. }
  321. cell?.contentInset = .init(top: 4, left: 0, bottom: 4, right: 0)
  322. guard let model = datas_.safe_element(for: row) as? KMNewUserGuideModel else {
  323. return cell
  324. }
  325. cell?.titleLabel.font = .SFProTextBoldFont(14)
  326. cell?.titleLabel.stringValue = model.name
  327. if let data = model.despAttri {
  328. cell?.subTitleLabel.attributedStringValue = data
  329. }
  330. let isSelected = model.isSelected
  331. cell?.subTitleLabel.isHidden = !isSelected
  332. cell?.leftLine.isHidden = !isSelected
  333. cell?.backgroundView.layer?.cornerRadius = 4
  334. if KMAppearance.isDarkMode() {
  335. cell?.titleLabel.textColor = .white
  336. cell?.leftLine.layer?.backgroundColor = NSColor(hex: "#227AFF").cgColor
  337. if isSelected {
  338. cell?.backgroundView.layer?.backgroundColor = NSColor(hex: "#869297").withAlphaComponent(0.4).cgColor
  339. } else {
  340. cell?.backgroundView.layer?.backgroundColor = NSColor.black.withAlphaComponent(0.8).cgColor
  341. }
  342. } else {
  343. cell?.titleLabel.textColor = NSColor(hex: "#42464D")
  344. cell?.leftLine.layer?.backgroundColor = NSColor(hex: "#4982E6").cgColor
  345. if isSelected {
  346. cell?.backgroundView.layer?.backgroundColor = NSColor(hex: "#DCF4FF").withAlphaComponent(0.4).cgColor
  347. } else {
  348. cell?.backgroundView.layer?.backgroundColor = .white
  349. }
  350. }
  351. return cell
  352. }
  353. func tableViewSelectionDidChange(_ notification: Notification) {
  354. if tableView.isEqual(to: notification.object) {
  355. let row = tableView.selectedRow
  356. guard let model = datas_.safe_element(for: row) as? KMNewUserGuideModel else {
  357. return
  358. }
  359. selectType(model.type)
  360. _trackEvent(type: model.type)
  361. }
  362. }
  363. }