// // KMCancelSubscribeSuccessWindowController.swift // PDF Reader Pro // // Created by User-Tangchao on 2024/12/18. // import Cocoa @objc enum KMCancelSubscribeSuccessType: Int { case none = 0 // 功能不满足 case unsatisfiedFunctions // 价格过高 case priceInappropriate // Bug过多 case tooManyBugs // 意外订阅 case accidentallySubscribed // 其他 case other } // 高级年订阅订阅(试用) let kKMAdvancedYearSubscribedOfTrial = "KMAdvancedYearSubscribedOfTrialKey" @objcMembers class KMCancelSubscribeSuccessWindowController: KMBaseWindowController { @IBOutlet weak var contentBox: NSBox! @IBOutlet weak var backgroundIv: NSImageView! @IBOutlet weak var titleLabel: NSTextField! @IBOutlet weak var subTitleLabel: NSTextField! @IBOutlet weak var listTitleLabel: NSTextField! @IBOutlet weak var scrollView: NSScrollView! @IBOutlet weak var tableView: NSTableView! @IBOutlet weak var buttonBox: NSBox! @IBOutlet weak var iconIv: NSImageView! static let shared = KMCancelSubscribeSuccessWindowController(windowNibName: "KMCancelSubscribeSuccessWindowController") private var datas_: [KMCancelSubscribeSuccessModel] = [] private var selectedModel_: KMCancelSubscribeSuccessModel? = nil private var couponsWinC: KMCancelSubscribeCouponsWindowController? private let hasShowKey_ = "CancelSubscribeSuccessHasShow" private let couponsShowCountKey_ = "CancelSubscribeSuccessCouponsShowCount" private let appLaunchCountOfCouponsKey_ = "CancelSubscribeSuccessAppLaunchCountOfCoupons" private lazy var submitButton_: NSButton = { let view = NSButton() view.isBordered = false return view }() private lazy var buttonIv_: NSImageView = { let view = NSImageView() return view }() override func windowDidLoad() { super.windowDidLoad() window?.title = "" window?.delegate = self window?.standardWindowButton(.zoomButton)?.isHidden = true window?.standardWindowButton(.miniaturizeButton)?.isHidden = true let closeButton = window?.standardWindowButton(.closeButton) closeButton?.target = self closeButton?.action = #selector(closeAction) backgroundIv.image = NSImage(named: "KMImageNameCancelSubscribeSuccessBg") backgroundIv.image?.size = window?.frame.size ?? NSMakeSize(470, 540) iconIv.image = NSImage(named: "KMImageNameCancelSubscribeSuccessIcon") backgroundIv.imageScaling = .scaleAxesIndependently titleLabel.stringValue = NSLocalizedString("Cancel Subscription Successfully", comment: "") titleLabel.font = .UbuntuMediumFontWithSize(24) subTitleLabel.stringValue = NSLocalizedString("Your feedback is essential for improving our product.", comment: "") subTitleLabel.font = .UbuntuMediumFontWithSize(14) listTitleLabel.stringValue = NSLocalizedString("Why didn't you continue with PDF Reader Pro?", comment: "") listTitleLabel.font = .UbuntuMediumFontWithSize(16) tableView.delegate = self tableView.dataSource = self tableView.usesAutomaticRowHeights = true tableView.backgroundColor = .clear tableView.enclosingScrollView?.drawsBackground = false tableView.enclosingScrollView?.borderType = .noBorder tableView.selectionHighlightStyle = .none buttonBox.borderWidth = 0 contentBox.contentView?.addSubview(buttonIv_, positioned: .below, relativeTo: buttonBox) buttonIv_.image = NSImage(named: "KMImageNameCancelSubscribeSuccessButton") buttonIv_.mas_makeConstraints { make in make?.centerX.mas_equalTo() make?.centerY.equalTo()(buttonBox)?.offset()(6) } buttonBox.contentView?.addSubview(submitButton_) submitButton_.frame = buttonBox.contentView?.frame ?? .zero submitButton_.autoresizingMask = [.width, .height] submitButton_.title = NSLocalizedString("Submit", comment: "") submitButton_.font = .UbuntuMediumFontWithSize(16) submitButton_.target = self submitButton_.action = #selector(submitAction) _initData() interfaceThemeDidChanged(self.window?.appearance?.name ?? .aqua) } override func interfaceThemeDidChanged(_ appearance: NSAppearance.Name) { super.interfaceThemeDidChanged(appearance) KMMainThreadExecute { if KMAppearance.isDarkMode() { self.listTitleLabel.textColor = .white self.titleLabel.textColor = .white self.subTitleLabel.textColor = .white self.submitButton_.setTitleColor(NSColor(hex: "#050022")) } else { self.listTitleLabel.textColor = NSColor(hex: "#000150") self.titleLabel.textColor = NSColor(hex: "#000150") self.subTitleLabel.textColor = NSColor(hex: "#000150") self.submitButton_.setTitleColor(.white) } self.tableView.reloadData() } } // MARK: - Public Methods public func openWindow() { self.showWindow(nil) selectType(.unsatisfiedFunctions) _showCenter(animate: false) let member = KMMemberInfo.shared if member.isLogin && member.userEmail.isEmpty == false { KMDataManager.udExtension_set(true, forKey: member.userEmail+hasShowKey_) } else { KMDataManager.ud_set(true, forKey: hasShowKey_) } trackEvent(eventName: "PUW_2", params: ["PUW_Exposure" : "PUW_CancelFreeTrial"], platform: .AppCenter) } public func selectType(_ type: KMCancelSubscribeSuccessType) { if selectedModel_?.type == type { // 不能重复选中且选中再点击取消选中 return } let preType = selectedModel_?.type for model in datas_ { model.isSelected = (model.type == type) if model.isSelected { selectedModel_ = model } } // 80+12 let height: CGFloat = 92 KMMainThreadExecute { // self.tableView.reloadData() if type == .other { var winFrame = self.window?.frame ?? .zero winFrame.size.height = NSHeight(winFrame) + height self.window?.setFrame(winFrame, display: true, animate: false) } else { if preType == .other { var winFrame = self.window?.frame ?? .zero winFrame.size.height = NSHeight(winFrame) - height self.window?.setFrame(winFrame, display: true, animate: false) } else { // no things } } self.tableView.animator().reloadData() } } public func closeWindow() { window?.close() } public func openCouponsWindow() { if couponsShowCount() > 2 { return } let member = KMMemberInfo.shared if (IAPProductsManager.default().isAvailableAllFunction() && member.userScenarioType != .lite_type8) { // 本地有权益 return } trackEvent(eventName: "PUW_2", params: ["PUW_Exposure" : "CancelFreeTrial_Price_CouponCode"], platform: .AppCenter) recordCouponsShow() recordAppLaunchCountOfCoupons() if let winC = couponsWinC { winC.openWindow() return } let winC = KMCancelSubscribeCouponsWindowController() winC.openWindow() self.couponsWinC = winC } public func saveRecord() { let member = KMMemberInfo.shared if member.isLogin == false { return } if member.is_advanced_year_subscribe() == false { return } if member.isCancelSubscribe() { // 已经退订 return } // 高级版年订阅 var isTrail = false for vip in member.activeVips { if vip.levels == "3" && vip.paymentModel == "1" && vip.cycle == 4 { isTrail = vip.isTrail == "1" break } } if isTrail == false { return } KMDataManager.ud_set(true, forKey: kKMAdvancedYearSubscribedOfTrial) } public func needShow() -> Bool { let member = KMMemberInfo.shared if member.isLogin && member.userEmail.isEmpty == false { let data = KMDataManager.udExtension_object(forKey: member.userEmail+hasShowKey_) as? Bool ?? false return data == false } return KMDataManager.ud_bool(forKey: hasShowKey_) == false } public func recordCouponsShow() { let member = KMMemberInfo.shared if member.isLogin && member.userEmail.isEmpty == false { let key = member.userEmail+couponsShowCountKey_ let cnt = KMDataManager.ud_integer(forKey: key) KMDataManager.ud_set(cnt+1, forKey: key) } else { let cnt = KMDataManager.ud_integer(forKey: couponsShowCountKey_) KMDataManager.ud_set(cnt+1, forKey: couponsShowCountKey_) } } public func couponsShowCount() -> Int { let member = KMMemberInfo.shared if member.isLogin && member.userEmail.isEmpty == false { let key = member.userEmail+couponsShowCountKey_ return KMDataManager.ud_integer(forKey: key) } else { return KMDataManager.ud_integer(forKey: couponsShowCountKey_) } } public func recordAppLaunchCountOfCoupons() { let member = KMMemberInfo.shared if member.isLogin && member.userEmail.isEmpty == false { let key = member.userEmail+appLaunchCountOfCouponsKey_ let cnt = KMDataManager.ud_integer(forKey: key) KMDataManager.ud_set(cnt+1, forKey: key) } else { let cnt = KMDataManager.ud_integer(forKey: appLaunchCountOfCouponsKey_) KMDataManager.ud_set(cnt+1, forKey: appLaunchCountOfCouponsKey_) } } public func appLaunchCountOfCoupons() -> Int { let member = KMMemberInfo.shared if member.isLogin && member.userEmail.isEmpty == false { let key = member.userEmail+appLaunchCountOfCouponsKey_ return KMDataManager.ud_integer(forKey: key) } else { return KMDataManager.ud_integer(forKey: appLaunchCountOfCouponsKey_) } } // MARK: - Actions @objc func submitAction() { guard let model = selectedModel_ else { return } if isConnectionAvailable() == false { showHud(msg: NSLocalizedString("Please make sure your internet connection is available.", comment: "")) return } _trackEvent(type: model.type) let modelV = KMIsDMGVersion() ? "Dmg" : "Mac" let params: [String : Any] = [ "model" : modelV, "title" : _titleString(from: model.type), "content" : model.type == .other ? model.content : "" ] KMRequestServer.Member_POST(url: kURLAPI_memberSystemSSO_user_cancelReason, params: params) { success, resultModel, error in self.closeWindow() if model.type == .priceInappropriate { self.openCouponsWindow() } } } @objc func closeAction() { if let model = selectedModel_, model.type == .priceInappropriate { self.openCouponsWindow() } trackEvent(eventName: "PUW_2", params: ["PUW_Btn" : "CancelFreeTrial_Cancel"], platform: .AppCenter) self.closeWindow() } // MARK: - Private Methods private func _initData() { datas_.removeAll() let types: [KMCancelSubscribeSuccessType] = [.unsatisfiedFunctions, .priceInappropriate, .tooManyBugs, .accidentallySubscribed, .other] for type in types { datas_.append(_initModel(type: type)) } KMMainThreadExecute { self.tableView.reloadData() } } private func _initModel(type: KMCancelSubscribeSuccessType) -> KMCancelSubscribeSuccessModel { let model = KMCancelSubscribeSuccessModel() model.type = type if type == .unsatisfiedFunctions { model.titleText = NSLocalizedString("1. Unsatisfied functions", comment: "") } else if type == .priceInappropriate { model.titleText = NSLocalizedString("2. The price is inappropriate", comment: "") } else if type == .tooManyBugs { model.titleText = NSLocalizedString("3. Too many bugs and unsatisfied experience", comment: "") } else if type == .accidentallySubscribed { model.titleText = NSLocalizedString("4. Accidentally subscribed", comment: "") } else if type == .other { model.titleText = NSLocalizedString("5. Others", comment: "") } return model } private func _updateCellView(_ cellView: KMCancelSubscribeSuccessCellView?, model: KMCancelSubscribeSuccessModel) { KMMainThreadExecute { cellView?.contentBox.cornerRadius = 8 // cell?.radio.font = .UbuntuMediumFontWithSize(16) cellView?.radio.title = model.titleText cellView?.radio.state = model.isSelected ? .on : .off if KMAppearance.isDarkMode() { cellView?.contentBox.borderColor = NSColor(hex: "#5023B8").withAlphaComponent(0.15) cellView?.contentBox.fillColor = NSColor(hex: "#1A0D32") cellView?.radio.setTitleColor(.white) if let data = cellView as? KMCancelSubscribeSuccessOtherCellView { data.inputBox.cornerRadius = 8 data.inputBox.borderColor = NSColor(hex: "#5023B8").withAlphaComponent(0.15) data.inputBox.fillColor = NSColor(hex: "#1A0D32") } } else { cellView?.contentBox.borderColor = NSColor(hex: "#8A58FF").withAlphaComponent(0.15) cellView?.contentBox.fillColor = .white cellView?.radio.setTitleColor(NSColor(hex: "#42464D")) if let data = cellView as? KMCancelSubscribeSuccessOtherCellView { data.inputBox.cornerRadius = 8 data.inputBox.borderColor = NSColor(hex: "#8A58FF").withAlphaComponent(0.15) data.inputBox.fillColor = .white } } } } private func _titleString(from type: KMCancelSubscribeSuccessType) -> String { switch type { case .unsatisfiedFunctions: return "1" case .priceInappropriate: return "5" case .tooManyBugs: return "2" case .accidentallySubscribed: return "3" case .other: return "4" case .none: return "1" } } private func _trackEvent(type: KMCancelSubscribeSuccessType) { if type == .unsatisfiedFunctions { trackEvent(eventName: "PUW_2", params: ["PUW_Btn" : "CancelFreeTrial_LackFunction"], platform: .AppCenter) } else if type == .priceInappropriate { trackEvent(eventName: "PUW_2", params: ["PUW_Btn" : "CancelFreeTrial_Price"], platform: .AppCenter) } else if type == .tooManyBugs { trackEvent(eventName: "PUW_2", params: ["PUW_Btn" : "CancelFreeTrial_Bugs"], platform: .AppCenter) } else if type == .accidentallySubscribed { trackEvent(eventName: "PUW_2", params: ["PUW_Btn" : "CancelFreeTrial_AccidentlyPurchase"], platform: .AppCenter) } else if type == .other { trackEvent(eventName: "PUW_2", params: ["PUW_Btn" : "CancelFreeTrial_Others"], platform: .AppCenter) } } private func _showCenter(animate: Bool){ guard let screenFrame = NSScreen.main?.frame else { return } guard let win = self.window else { return } var frame = win.frame frame.origin.y = (screenFrame.size.height-frame.size.height)*0.5 frame.origin.x = (screenFrame.size.width-frame.size.width)*0.5 win.setFrame(frame, display: true, animate: animate) } } // MARK: - NSWindowDelegate extension KMCancelSubscribeSuccessWindowController: NSWindowDelegate { func windowDidResize(_ notification: Notification) { guard let data = window?.isEqual(to: notification.object), data == true else { return } backgroundIv.image?.size = window?.frame.size ?? NSMakeSize(470, 540) } } extension KMCancelSubscribeSuccessWindowController: NSTableViewDelegate, NSTableViewDataSource { func numberOfRows(in tableView: NSTableView) -> Int { return self.datas_.count } func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { let model = datas_[row] if model.type == .other && model.isSelected { var cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "cellID"), owner: self) as? KMCancelSubscribeSuccessOtherCellView if cell == nil { cell = KMCancelSubscribeSuccessOtherCellView() } _updateCellView(cell, model: model) window?.makeFirstResponder(cell?.textView) cell?.itemClick = { [unowned self] in selectType(model.type) } cell?.valueDidChange = { content, _ in model.content = content as? String ?? "" } return cell } var cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "cellID"), owner: self) as? KMCancelSubscribeSuccessCellView if cell == nil { cell = KMCancelSubscribeSuccessCellView() } _updateCellView(cell, model: model) cell?.itemClick = { [unowned self] in selectType(model.type) } return cell } func tableViewSelectionDidChange(_ notification: Notification) { let row = self.tableView.selectedRow let model = datas_[row] selectType(model.type) } }