// // KMVerificationCodeView.swift // PDF Reader Pro // // Created by lizhe on 2023/2/23. // import Cocoa typealias KMVerificationCodeViewCancelAction = (_ view: KMVerificationCodeView) -> Void typealias KMVerificationCodeViewVerificationCodeAction = (_ view: KMVerificationCodeView, _ data: KMRegisterModel, _ codeString: String) -> Void typealias KMVerificationCodeViewDoneAction = (_ view: KMVerificationCodeView, _ data: KMRegisterModel, _ sender: NSButton) -> Void typealias KMVerificationCodeViewCloseAction = (_ view: KMVerificationCodeView) -> Void typealias KMVerificationCodeViewReSendAction = (_ view: KMVerificationCodeView, _ sender: NSTextView) -> Void class KMVerificationCodeView: KMBaseXibView { @IBOutlet weak var titleLabel: NSTextField! @IBOutlet weak var describeLabel: NSTextField! @IBOutlet var timerTextView: NSTextView! @IBOutlet weak var cancellationButton: NSButton! @IBOutlet weak var cancelButton: NSButton! @IBOutlet weak var closeButton: NSButton! @IBOutlet weak var textFieldContentView: NSView! @IBOutlet weak var code1ContentView: NSView! @IBOutlet weak var code1TextField: KMBaseTextField! @IBOutlet weak var code2ContentView: NSView! @IBOutlet weak var code2TextField: KMBaseTextField! @IBOutlet weak var code3ContentView: NSView! @IBOutlet weak var code3TextField: KMBaseTextField! @IBOutlet weak var code4ContentView: NSView! @IBOutlet weak var code4TextField: KMBaseTextField! @IBOutlet weak var code5ContentView: NSView! @IBOutlet weak var code5TextField: KMBaseTextField! @IBOutlet weak var code6ContentView: NSView! @IBOutlet weak var code6TextField: KMBaseTextField! @IBOutlet weak var doneButtonTopConstraint: NSLayoutConstraint! @IBOutlet weak var alertView: KMLightMemberAlertView! @IBOutlet weak var alertHeightConstraint: NSLayoutConstraint! @IBOutlet weak var closeBox: KMBox! var cancellationButtonVC: KMDesignButton! var cancelButtonVC: KMDesignButton! var cancelAction: KMVerificationCodeViewCancelAction? var doneAction: KMVerificationCodeViewDoneAction? var closeAction: KMVerificationCodeViewCloseAction? var reSendAction: KMVerificationCodeViewReSendAction? var verificationCodeAction: KMVerificationCodeViewVerificationCodeAction? var timer: Timer? var time: Int = -1 let startTime = 60 //验证码 var verificationCode: String { get { return self.code1TextField.textField.stringValue + self.code2TextField.textField.stringValue + self.code3TextField.textField.stringValue + self.code4TextField.textField.stringValue + self.code5TextField.textField.stringValue + self.code6TextField.textField.stringValue } } var verificationCodeState: KMRequestServerErrorCodeType = .unknown var inputType: KMRegisterLogType = .login { didSet { switch self.inputType { case .register: self.verifyCodeType = .register case .loginInputPassword: self.verifyCodeType = .resetPassword case .accountInfo: self.verifyCodeType = .logOff default: KMPrint("") } self.cleanVerificationCode() self.reloadData() self.updateLanguage() } } var verifyCodeType: KMVerifyCodeType = .unknown { didSet { self.reloadData() self.updateLanguage() } } var isNetworking: Bool = false //是否正在进行网络请求 var codeIsTrue: Bool = false //验证码是否正确 var model: KMRegisterModel = KMRegisterModel() deinit { self.endTimer() KMPrint("KMVerificationCodeView dealloc") } override func draw(_ dirtyRect: NSRect) { super.draw(dirtyRect) // Drawing code here. } override func setup() { super.setup() self.backgroundColor(NSColor.km_init(hex: "#FFFFFF")) self.titleLabel.font = NSFont.SFProTextSemiboldFont(20.0) self.titleLabel.textColor = NSColor.km_init(hex: "#252629") self.describeLabel.font = NSFont.SFProTextRegularFont(14.0) self.describeLabel.textColor = NSColor.km_init(hex: "#252629") self.timerTextView.delegate = self self.timerTextView.frame = (self.timerTextView.enclosingScrollView?.contentView.bounds)! self.timerTextView.autoresizingMask = [.width, .height] let textFieldArray = [self.code1TextField,self.code2TextField,self.code3TextField,self.code4TextField,self.code5TextField,self.code6TextField] for i in 0...textFieldArray.count - 1 { let textField = textFieldArray[i] if textField != nil { textField!.textField.textColor = NSColor.km_init(hex: "#252629") textField!.textField.font = NSFont.SFProTextSemiboldFont(20.0) textField?.maxLen = 1 textField?.model.isCanNull = true textField?.textField.tag = i + 10 textField!.textField.alignment = .center textField?.superview?.border(NSColor.km_init(hex: "#DFE1E5"), 1, 2) textField?.textField.onFocus = { [unowned self] in self.cancelAllTextFieldFouce() textField?.superview?.border(NSColor.km_init(hex: "#1770F4"), 1, 2) } textField?.textDidEndEditing = { [unowned self] string in textField?.superview?.border(NSColor.km_init(hex: "#DFE1E5"), 1, 2) } textField?.textDidChange = { [unowned self] string in //自动验证验证码 self.autoVerificationCode() var isNext = true if string == "" { isNext = false } for item in textFieldArray { let t = textField?.textField var tag = t!.tag + 1 if !isNext { tag = t!.tag - 1 } tag = max(10, tag) if item?.textField.tag == tag { item?.textField.becomeFirstResponder() item?.superview?.border(NSColor.km_init(hex: "#1770F4"), 1, 2) break } } } textField?.textDeleteAction = { [unowned self] string in if string == "" { for item in textFieldArray { let t = textField?.textField let tag = max(10, t!.tag - 1) if item?.textField.tag == tag { item?.textField.becomeFirstResponder() item?.superview?.border(NSColor.km_init(hex: "#1770F4"), 1, 2) break } } } } } } self.cancellationButtonVC = KMDesignButton(withType: .Text) self.cancellationButton.addSubview(self.cancellationButtonVC.view) self.cancellationButtonVC?.view.frame = self.cancellationButton.bounds self.cancellationButtonVC.target = self self.cancellationButtonVC.action = #selector(doneButtonAction) self.cancellationButtonVC.button(type: .Cta, size: .m) self.cancellationButtonVC.button.keyEquivalent = KMKeyEquivalent.enter self.cancelButtonVC = KMDesignButton(withType: .Text) self.cancelButton.addSubview(self.cancelButtonVC.view) self.cancelButtonVC?.view.frame = self.cancelButton.bounds self.cancelButtonVC.target = self self.cancelButtonVC.action = #selector(cancelButtonAction) self.cancelButtonVC.button(type: .Sec, size: .m) self.cancelButtonVC.button.keyEquivalent = KMKeyEquivalent.enter self.closeBox.moveCallback = { [weak self] (mouseEntered, mouseBox) in if mouseEntered { self?.closeButton.image = NSImage(named: "control_btn_icon_close_hov") } else { self?.closeButton.image = NSImage(named: "control_btn_icon_close") } } } override func reloadData() { super.reloadData() if inputType == .accountInfo { self.closeButton.isHidden = false self.cancelButtonVC.button(type: .Sec, size: .m) self.cancelButtonVC.updateUI() } else { self.closeButton.isHidden = true self.cancelButtonVC.button(type: .Text, size: .m) self.cancelButtonVC.updateUI() } let textFieldArray = [self.code1TextField,self.code2TextField,self.code3TextField,self.code4TextField,self.code5TextField,self.code6TextField] for item in textFieldArray { item?.isEnabled = !self.isNetworking } if self.verificationCode.count == 6 && // self.time != -1 && self.codeIsTrue { self.cancellationButtonVC.enabled = true } else { self.cancellationButtonVC.enabled = false self.showAlert(result: Result(code: 0)) } } override func updateLanguage() { super.updateLanguage() if self.verifyCodeType == .unknown { self.titleLabel.stringValue = "" } else if self.verifyCodeType == .register { self.titleLabel.stringValue = NSLocalizedString("Sign Up", comment: "") } else if self.verifyCodeType == .logOff { self.titleLabel.stringValue = NSLocalizedString("Cancel Account", comment: "") } else if self.verifyCodeType == .resetPassword { self.titleLabel.stringValue = NSLocalizedString("Reset Password", comment: "") } self.describeLabel.stringValue = NSLocalizedString("Enter the verification code", comment: "") if inputType == .unknown { self.cancelButtonVC.stringValue = "" } else if inputType == .accountInfo { self.cancelButtonVC.stringValue = NSLocalizedString("Cancel", comment: "") } else { self.cancelButtonVC.stringValue = NSLocalizedString("Back to previous step", comment: "") } if self.verifyCodeType == .unknown { self.cancellationButtonVC.stringValue = "" } else if self.verifyCodeType == .register { self.cancellationButtonVC.stringValue = NSLocalizedString("Sign Up", comment: "") } else if self.verifyCodeType == .logOff { self.cancellationButtonVC.stringValue = NSLocalizedString("Cancellation", comment: "") } else if self.verifyCodeType == .resetPassword { self.cancellationButtonVC.stringValue = NSLocalizedString("Next Step", comment: "") } let tempTime: String = String(self.time) var timeString = "(" + tempTime + NSLocalizedString("s", comment: "") + ")" if tempTime == "60" { timeString = NSLocalizedString("Click to resend", comment: "") } else if tempTime == "-1" { timeString = " " } //singin let string = NSLocalizedString("Didn't receive the verification code?", comment: "") + " " + timeString let attributedString = NSMutableAttributedString.init(string: string) let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.alignment = .left; attributedString.addAttributes([NSAttributedString.Key.font : NSFont.SFProTextRegularFont(12.0), NSAttributedString.Key.foregroundColor : NSColor.km_init(hex: "#252629"), NSAttributedString.Key.paragraphStyle : paragraphStyle], range: NSRange(location: 0, length: string.count)) let range = string.range(of: NSLocalizedString(timeString, comment: "")) attributedString.setAttributes([NSAttributedString.Key.font : NSFont.SFProTextRegularFont(12.0), NSAttributedString.Key.foregroundColor : NSColor.km_init(hex: "#1770F4"), NSAttributedString.Key.underlineColor : NSColor.clear, NSAttributedString.Key.link : "timer://"], range: string.nsRange(from: range!)!) self.timerTextView.textStorage?.setAttributedString(attributedString) } func cleanVerificationCode() { let textFieldArray = [self.code1TextField,self.code2TextField,self.code3TextField,self.code4TextField,self.code5TextField,self.code6TextField] for item in textFieldArray { item?.stringValue = "" item?.textField.resignFirstResponder() if item != self.code1TextField { item?.superview?.border(NSColor.km_init(hex: "#DFE1E5"), 1, 2) } } self.model.verifyCode = "" } //验证码请求完成 func updateNetworkingState(complete: Bool, codeIsTure: Bool) { self.isNetworking = !complete self.codeIsTrue = codeIsTure self.reloadData() if !codeIsTure { self.code6TextField.textField.becomeFirstResponder() } } //自动验证验证码 func autoVerificationCode() { let textFieldArray = [self.code1TextField,self.code2TextField,self.code3TextField,self.code4TextField,self.code5TextField,self.code6TextField] for item in textFieldArray { item?.textField.resignFirstResponder() } if self.verificationCode.count == 6 { if self.verificationCodeAction != nil { self.isNetworking = true self.model.verifyCode = self.verificationCode self.verificationCodeAction!(self, self.model, self.verificationCode) } } } } protocol KMVerificationCodeViewTimer {} extension KMVerificationCodeView: KMVerificationCodeViewTimer { func cleanTimer() { self.time = startTime self.updateLanguage() } func resetTimer() { self.time = startTime self.beginTimer() self.updateTimerData(timer: self.timer!) } func beginTimer() { if self.timer != nil { self.endTimer() } if self.time == -1 { self.time = startTime } self.timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(updateTimerData), userInfo: nil, repeats: true) } func endTimer() { self.timer?.invalidate() self.timer = nil self.updateLanguage() } @objc func updateTimerData(timer: Timer) { self.time -= 1 if self.time == 0 { self.endTimer() self.time = startTime } self.updateLanguage() } } protocol KMVerificationCodeViewAction {} extension KMVerificationCodeView: KMVerificationCodeViewAction { func sendVerificationCode() { guard let callBack = reSendAction else { return } self.timerTextView.isSelectable = false callBack(self, self.timerTextView) } @IBAction func cancelButtonAction(_ sender: NSButton) { self.endTimer() guard let callBack = cancelAction else { return } callBack(self) } @IBAction func doneButtonAction(_ sender: NSButton) { guard let callBack = doneAction else { return } self.model.verifyCode = self.verificationCode self.changeDoneButtonState(enable: false) callBack(self, self.model, sender) } @IBAction func closeButtonAction(_ sender: Any) { guard let callBack = closeAction else { return } self.endTimer() callBack(self) } func showAlert(result: Result?) { if result != nil { self.alertView.result = result! self.alertHeightConstraint.constant = self.alertView.fetchAlertHeight() if result?.code != 0 && result?.code != 200 { self.textFieldAlert() } } } func changeDoneButtonState(enable: Bool) { self.cancellationButtonVC.enabled = enable } func cancelAllTextFieldFouce() { let textFieldArray = [self.code1TextField,self.code2TextField,self.code3TextField,self.code4TextField,self.code5TextField,self.code6TextField] for item in textFieldArray { item?.superview?.border(NSColor.km_init(hex: "#DFE1E5"), 1, 2) } } func textFieldAlert() { let textFieldArray = [self.code1TextField,self.code2TextField,self.code3TextField,self.code4TextField,self.code5TextField,self.code6TextField] for item in textFieldArray { item?.superview?.border(NSColor.red, 1, 2) } } } extension KMVerificationCodeView: NSTextViewDelegate { func textView(_ textView: NSTextView, clickedOnLink link: Any, at charIndex: Int) -> Bool { if link as! String == "timer://" && self.time == 60 { guard let callBack = reSendAction else { return true } textView.isSelectable = false callBack(self, textView) } return true } } extension KMVerificationCodeView: NSTextFieldDelegate { func controlTextDidEndEditing(_ obj: Notification) { KMPrint("controlTextDidEndEditing") let textField = obj.object as? NSTextField for view in self.textFieldContentView.subviews { let t = view.subviews.first as? FocusAwareTextField if t == textField { view.border(NSColor.km_init(hex: "#DFE1E5"), 1, 4) } } } } extension KMVerificationCodeView { override var acceptsFirstResponder: Bool { return true } override func performKeyEquivalent(with event: NSEvent) -> Bool { if event.type == .keyDown, event.modifierFlags.contains(.command), event.characters == "v" { if let pasteboardString = NSPasteboard.general.string(forType: .string) { KMPrint(pasteboardString) if let num = Int(pasteboardString) { let textFieldArray = [self.code1TextField,self.code2TextField,self.code3TextField,self.code4TextField,self.code5TextField,self.code6TextField] for index in 0...textFieldArray.count - 1 { if index < pasteboardString.count { let textField = textFieldArray[index] let fifthChar = pasteboardString[pasteboardString.index(pasteboardString.startIndex, offsetBy: index)] textField?.textField.stringValue = String(fifthChar) textField?.stringValue = String(fifthChar) textField?.textField.becomeFirstResponder() } } if pasteboardString.count == 6 { self.autoVerificationCode() } } // 处理剪贴板中的字符串 return true } } return super.performKeyEquivalent(with: event) } }