|
|
@@ -0,0 +1,883 @@
|
|
|
+//
|
|
|
+// KeyboardViewController.swift
|
|
|
+// AiKeyboard
|
|
|
+//
|
|
|
+// Created by Destiny on 2025/4/23.
|
|
|
+//
|
|
|
+
|
|
|
+import UIKit
|
|
|
+import Lottie
|
|
|
+import SnapKit
|
|
|
+import MarqueeLabel
|
|
|
+
|
|
|
+enum KeyboardType: Int {
|
|
|
+ case help = 0 // 帮聊
|
|
|
+ case teach = 1 // 教你说
|
|
|
+ case prologue = 2 // 开场白
|
|
|
+}
|
|
|
+
|
|
|
+class KeyboardViewController: UIInputViewController {
|
|
|
+
|
|
|
+ struct UX {
|
|
|
+ static let keyboardHeight = 292.0
|
|
|
+ }
|
|
|
+
|
|
|
+ // 选择的键盘类型
|
|
|
+ var chooseType: KeyboardType = .help {
|
|
|
+ didSet {
|
|
|
+ self.updateMainViewUI()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 键盘列表
|
|
|
+ var keyboardList: [KeyboardModel]?
|
|
|
+ // 选择的键盘
|
|
|
+ var chooseKeyboard: KeyboardModel?
|
|
|
+ // 人设列表
|
|
|
+ var characterList: [CharacterModel]?
|
|
|
+ // 开场白列表
|
|
|
+ var prologueList: [PrologueModel]?
|
|
|
+
|
|
|
+ // 开始持续删除
|
|
|
+ private var deleteTimer: Timer?
|
|
|
+ private var deleteAcceleration: TimeInterval = 0.1
|
|
|
+
|
|
|
+ var heightConstriaint: NSLayoutConstraint?
|
|
|
+
|
|
|
+ lazy var bgImageView: UIImageView = {
|
|
|
+
|
|
|
+ let imageView = UIImageView()
|
|
|
+ imageView.image = UIImage(named: "keyboard_bg")
|
|
|
+ return imageView
|
|
|
+ }()
|
|
|
+
|
|
|
+ lazy var menuBtn: UIButton = {
|
|
|
+
|
|
|
+ let button = UIButton()
|
|
|
+ button.setImage(UIImage(named: "keyboard_menu_btn"), for: .normal)
|
|
|
+ button.addTarget(self, action: #selector(menuBtnClickAction), for: .touchUpInside)
|
|
|
+ return button
|
|
|
+ }()
|
|
|
+
|
|
|
+ lazy var heartAnimation: LottieAnimationView = {
|
|
|
+
|
|
|
+ let animate = LottieAnimationView(name: "heart")
|
|
|
+ animate.loopMode = .loop
|
|
|
+ animate.backgroundColor = .clear
|
|
|
+ return animate
|
|
|
+ }()
|
|
|
+
|
|
|
+ lazy var exchangeBtn: UIButton = {
|
|
|
+
|
|
|
+ let button = UIButton()
|
|
|
+ button.setImage(UIImage(named: "keyboard_exchange_btn"), for: .normal)
|
|
|
+ return button
|
|
|
+ }()
|
|
|
+
|
|
|
+ lazy var chooseView: UIView = {
|
|
|
+
|
|
|
+ let view = UIView()
|
|
|
+ view.backgroundColor = .white
|
|
|
+ view.layer.cornerRadius = 17
|
|
|
+ return view
|
|
|
+ }()
|
|
|
+
|
|
|
+ lazy var functionView: UIView = {
|
|
|
+
|
|
|
+ let view = UIView()
|
|
|
+ view.backgroundColor = .hexStringColor(hexString: "#DDCFFD")
|
|
|
+ view.layer.cornerRadius = 16
|
|
|
+
|
|
|
+ view.isUserInteractionEnabled = true
|
|
|
+ let tap = UITapGestureRecognizer(target: self, action: #selector(functionBtnClickAction))
|
|
|
+ view.addGestureRecognizer(tap)
|
|
|
+ return view
|
|
|
+ }()
|
|
|
+
|
|
|
+ lazy var functionLabel: UILabel = {
|
|
|
+
|
|
|
+ let label = UILabel()
|
|
|
+ label.text = "帮聊"
|
|
|
+ label.font = .boldSystemFont(ofSize: 12)
|
|
|
+ label.textColor = .hexStringColor(hexString: "#000000", alpha: 0.8)
|
|
|
+ return label
|
|
|
+ }()
|
|
|
+
|
|
|
+ lazy var arrowIcon: UIImageView = {
|
|
|
+
|
|
|
+ let imageView = UIImageView()
|
|
|
+ imageView.image = UIImage(named: "icon_arrow_down")
|
|
|
+ return imageView
|
|
|
+ }()
|
|
|
+
|
|
|
+ lazy var userChangeBtn: UIButton = {
|
|
|
+
|
|
|
+ let btn = UIButton()
|
|
|
+ btn.setTitle("小蕾", for: .normal)
|
|
|
+ btn.titleLabel?.font = .systemFont(ofSize: 12, weight: .medium)
|
|
|
+ btn.setTitleColor(.hexStringColor(hexString: "#000000").withAlphaComponent(0.8), for: .normal)
|
|
|
+ btn.setImage(UIImage(named: "keyboard_user_change_icon"), for: .normal)
|
|
|
+ btn.setImageTitleLayout(.imgRight, spacing: 8)
|
|
|
+ btn.addTarget(self, action: #selector(changeBtnClickAction), for: .touchUpInside)
|
|
|
+ return btn
|
|
|
+ }()
|
|
|
+
|
|
|
+ lazy var userChangeView: UIView = {
|
|
|
+
|
|
|
+ let view = UIView()
|
|
|
+ let tap = UITapGestureRecognizer(target: self, action: #selector(changeBtnClickAction))
|
|
|
+ view.addGestureRecognizer(tap)
|
|
|
+ return view
|
|
|
+ }()
|
|
|
+
|
|
|
+ lazy var userChangeLabel: MarqueeLabel = {
|
|
|
+
|
|
|
+ let label = MarqueeLabel(frame: .zero, duration: 3.0, fadeLength: 0)
|
|
|
+ label.type = .leftRight
|
|
|
+ label.text = "通用键盘"
|
|
|
+ label.font = .systemFont(ofSize: 12)
|
|
|
+ label.textColor = .hexStringColor(hexString: "#000000")
|
|
|
+ label.textAlignment = .center
|
|
|
+ return label
|
|
|
+ }()
|
|
|
+
|
|
|
+ lazy var userChangeIcon: UIImageView = {
|
|
|
+
|
|
|
+ let icon = UIImageView()
|
|
|
+ icon.image = UIImage(named: "keyboard_user_change_icon")
|
|
|
+ return icon
|
|
|
+ }()
|
|
|
+
|
|
|
+ lazy var helpView: KeyboardHelpView = {
|
|
|
+
|
|
|
+ let view = KeyboardHelpView()
|
|
|
+ view.delegate = self
|
|
|
+ view.helpDelegate = self
|
|
|
+ return view
|
|
|
+ }()
|
|
|
+
|
|
|
+ lazy var teachView: KeyboardTeachView = {
|
|
|
+
|
|
|
+ let view = KeyboardTeachView()
|
|
|
+ view.delegate = self
|
|
|
+ view.teachDelegate = self
|
|
|
+ view.isHidden = true
|
|
|
+ return view
|
|
|
+ }()
|
|
|
+
|
|
|
+ lazy var prologueView: KeyboardPrologueView = {
|
|
|
+
|
|
|
+ let view = KeyboardPrologueView()
|
|
|
+ view.delegate = self
|
|
|
+ view.prologueDelegate = self
|
|
|
+ view.isHidden = true
|
|
|
+ return view
|
|
|
+ }()
|
|
|
+
|
|
|
+ lazy var exchangeView: KeyboardExchangeView = {
|
|
|
+
|
|
|
+ let view = KeyboardExchangeView()
|
|
|
+ view.isHidden = true
|
|
|
+ view.delegate = self
|
|
|
+ return view
|
|
|
+ }()
|
|
|
+
|
|
|
+ lazy var menuView: KeyboardMenuView = {
|
|
|
+
|
|
|
+ let view = KeyboardMenuView()
|
|
|
+ view.delegate = self
|
|
|
+ return view
|
|
|
+ }()
|
|
|
+
|
|
|
+ var nowShowView: UIView?
|
|
|
+
|
|
|
+ override func viewWillAppear(_ animated: Bool) {
|
|
|
+ super.viewWillAppear(animated)
|
|
|
+ self.prepareHeightConstraint()
|
|
|
+ }
|
|
|
+
|
|
|
+ override func viewDidLoad() {
|
|
|
+ super.viewDidLoad()
|
|
|
+
|
|
|
+ setUI()
|
|
|
+
|
|
|
+ KeyboardApi.initParams()
|
|
|
+ requestKeyboardList()
|
|
|
+ requestPrologueList()
|
|
|
+ startMonitoringPasteboard()
|
|
|
+// self.nextKeyboardButton.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true
|
|
|
+// self.nextKeyboardButton.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
|
|
|
+ }
|
|
|
+
|
|
|
+ override func viewWillLayoutSubviews() {
|
|
|
+ super.viewWillLayoutSubviews()
|
|
|
+ }
|
|
|
+
|
|
|
+ override func updateViewConstraints() {
|
|
|
+ super.updateViewConstraints()
|
|
|
+
|
|
|
+ if self.view.frame.size.width == 0 && self.view.frame.size.height == 0 {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ self.prepareHeightConstraint()
|
|
|
+ }
|
|
|
+
|
|
|
+ override func textWillChange(_ textInput: UITextInput?) {
|
|
|
+ // The app is about to change the document's contents. Perform any preparation here.
|
|
|
+ }
|
|
|
+
|
|
|
+ override func textDidChange(_ textInput: UITextInput?) {
|
|
|
+ // The app has just changed the document's contents, the document context has been updated.
|
|
|
+
|
|
|
+ var textColor: UIColor
|
|
|
+ let proxy = self.textDocumentProxy
|
|
|
+ if proxy.keyboardAppearance == UIKeyboardAppearance.dark {
|
|
|
+ textColor = UIColor.white
|
|
|
+ } else {
|
|
|
+ textColor = UIColor.black
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ func prepareHeightConstraint() {
|
|
|
+
|
|
|
+ if self.heightConstriaint == nil {
|
|
|
+
|
|
|
+ if let view = self.view {
|
|
|
+ self.heightConstriaint = NSLayoutConstraint(item: view, attribute: NSLayoutConstraint.Attribute.height, relatedBy: NSLayoutConstraint.Relation.equal, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 0.0, constant: UX.keyboardHeight)
|
|
|
+ self.heightConstriaint?.priority = UILayoutPriority(750)
|
|
|
+ self.view.addConstraint(self.heightConstriaint!)
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ self.heightConstriaint?.constant = UX.keyboardHeight
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// MARK: - 网络请求
|
|
|
+extension KeyboardViewController {
|
|
|
+
|
|
|
+ // 获取键盘列表
|
|
|
+ func requestKeyboardList() {
|
|
|
+
|
|
|
+ KeyboardNetworkManager.request(.keyboard_list(params: [:])) { response in
|
|
|
+
|
|
|
+ if let keyboardInfos = try? response.mapArray(KeyboardModel.self, atKeyPath: "data.keyboardInfos") {
|
|
|
+ self.keyboardList = keyboardInfos
|
|
|
+ self.updateUserChangeBtnUI()
|
|
|
+ self.requestCharacterList()
|
|
|
+ print("成功解析数组,数量:\(keyboardInfos.count)")
|
|
|
+ }
|
|
|
+ } fail: { code, error in
|
|
|
+
|
|
|
+ self.view.toast(text: error)
|
|
|
+ print(error)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取键盘人设列表
|
|
|
+ func requestCharacterList() {
|
|
|
+
|
|
|
+ var params: [String: Any] = [String: Any]()
|
|
|
+ if let chooseKeyboard = self.chooseKeyboard {
|
|
|
+ params = ["keyboardId": chooseKeyboard.id ?? ""]
|
|
|
+ }
|
|
|
+ KeyboardNetworkManager.request(.character_list(params: params)) { response in
|
|
|
+
|
|
|
+ if let characterInfos = try? response.mapArray(CharacterModel.self, atKeyPath: "data.characterInfos") {
|
|
|
+ self.characterList = characterInfos
|
|
|
+ self.updateCharacterUI()
|
|
|
+ print("成功解析数组,数量:\(characterInfos.count)")
|
|
|
+ }
|
|
|
+ } fail: { code, error in
|
|
|
+
|
|
|
+ self.view.toast(text: error)
|
|
|
+ print(error)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取键盘人设列表
|
|
|
+ func requestPrologueList() {
|
|
|
+
|
|
|
+ KeyboardNetworkManager.request(.prologue_list(params: [:])) { response in
|
|
|
+
|
|
|
+ if let prologueList = try? response.mapArray(PrologueModel.self, atKeyPath: "data.prologues") {
|
|
|
+ self.prologueList = prologueList
|
|
|
+ self.updatePrologueUI()
|
|
|
+ }
|
|
|
+ } fail: { code, error in
|
|
|
+
|
|
|
+ self.view.toast(text: error)
|
|
|
+ print(error)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 选择键盘
|
|
|
+ func requestChooseKeyboard(keyboardId: String, success: @escaping (() -> Void)) {
|
|
|
+
|
|
|
+ var params: [String: Any] = [String: Any]()
|
|
|
+ params = ["keyboardId": keyboardId]
|
|
|
+ KeyboardNetworkManager.request(.keyboard_choose(params: params)) { response in
|
|
|
+
|
|
|
+ self.view.toast(text: "保存成功")
|
|
|
+ success()
|
|
|
+ } fail: { code, error in
|
|
|
+
|
|
|
+ self.view.toast(text: error)
|
|
|
+ print(error)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 请求获取超会回
|
|
|
+ func requestSuperReply(characterId: String, content: String, complete: @escaping ((String) -> ())) {
|
|
|
+
|
|
|
+ var params: [String: Any] = [String: Any]()
|
|
|
+ params = ["keyboardId": self.chooseKeyboard?.id ?? "",
|
|
|
+ "characterId": characterId,
|
|
|
+ "content": content]
|
|
|
+ KeyboardNetworkManager.request(.chat_super_reply(params: params)) { response in
|
|
|
+
|
|
|
+ if let contentModel = try? response.mapObject(ReplyModel.self, atKeyPath: "data") {
|
|
|
+ complete(contentModel.content ?? "")
|
|
|
+ } else {
|
|
|
+ complete("")
|
|
|
+ }
|
|
|
+ } fail: { code, error in
|
|
|
+
|
|
|
+ self.view.toast(text: error)
|
|
|
+ print(error)
|
|
|
+ complete("")
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 请求获取超会说
|
|
|
+ func requestSuperSpeak(characterId: String, content: String, complete: @escaping (([String]) -> ())) {
|
|
|
+
|
|
|
+ var params: [String: Any] = [String: Any]()
|
|
|
+ params = ["keyboardId": self.chooseKeyboard?.id ?? "",
|
|
|
+ "characterId": characterId,
|
|
|
+ "content": content]
|
|
|
+ KeyboardNetworkManager.request(.chat_super_speak(params: params)) { response in
|
|
|
+
|
|
|
+ if let contentModel = try? response.mapObject(SpeakModel.self, atKeyPath: "data"), let list = contentModel.list {
|
|
|
+ complete(list)
|
|
|
+ } else {
|
|
|
+ complete([String]())
|
|
|
+ }
|
|
|
+ } fail: { code, error in
|
|
|
+
|
|
|
+ self.view.toast(text: error)
|
|
|
+ print(error)
|
|
|
+ complete([String]())
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 请求获取超会说
|
|
|
+ func requestSuperPrologue(name: String, complete: @escaping (([String]) -> ())) {
|
|
|
+
|
|
|
+ var params: [String: Any] = [String: Any]()
|
|
|
+ params = ["name": name]
|
|
|
+ KeyboardNetworkManager.request(.chat_super_prologue(params: params)) { response in
|
|
|
+
|
|
|
+ if let contentModel = try? response.mapObject(SpeakModel.self, atKeyPath: "data"), let list = contentModel.list {
|
|
|
+ complete(list)
|
|
|
+ } else {
|
|
|
+ complete([String]())
|
|
|
+ }
|
|
|
+ } fail: { code, error in
|
|
|
+
|
|
|
+ self.view.toast(text: error)
|
|
|
+ print(error)
|
|
|
+ complete([String]())
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// MARK: - 点击事件
|
|
|
+extension KeyboardViewController: KeyboardMenuViewDelegate, KeyboardExchangeViewDelegate {
|
|
|
+
|
|
|
+ // 功能按钮选择点击
|
|
|
+ @objc func functionBtnClickAction() {
|
|
|
+
|
|
|
+ KeyboardFunctionPopView.show(view: self.view, selectType: self.chooseType) { type in
|
|
|
+ self.chooseType = type
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 菜单按钮点击
|
|
|
+ @objc func menuBtnClickAction() {
|
|
|
+
|
|
|
+ clearPopView()
|
|
|
+
|
|
|
+ self.view.addSubview(menuView)
|
|
|
+ menuView.snp.makeConstraints { make in
|
|
|
+ make.left.right.bottom.equalTo(0)
|
|
|
+ make.top.equalTo(menuBtn.snp.bottom)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 菜单按钮返回
|
|
|
+ func menuBackBtnClickAction() {
|
|
|
+
|
|
|
+ clearPopView()
|
|
|
+ self.nowShowView?.isHidden = false
|
|
|
+ }
|
|
|
+
|
|
|
+ // 选择键盘按钮点击
|
|
|
+ @objc func changeBtnClickAction() {
|
|
|
+
|
|
|
+ clearPopView()
|
|
|
+ self.exchangeView.keyboardList = self.keyboardList
|
|
|
+ self.exchangeView.isHidden = false
|
|
|
+ }
|
|
|
+
|
|
|
+ // 选择键盘后返回
|
|
|
+ func exchangeViewBackClickAction() {
|
|
|
+
|
|
|
+ clearPopView()
|
|
|
+ self.nowShowView?.isHidden = false
|
|
|
+ }
|
|
|
+
|
|
|
+ // 点击保存按钮
|
|
|
+ func exchangeViewSaveClickAction(keyboardList: [KeyboardModel], success: @escaping (() -> ())) {
|
|
|
+
|
|
|
+ var keyboardId = ""
|
|
|
+ for keyboard in keyboardList {
|
|
|
+ if keyboard.isChoose == true {
|
|
|
+ keyboardId = keyboard.id ?? ""
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ requestChooseKeyboard(keyboardId: keyboardId) {
|
|
|
+ self.keyboardList = keyboardList
|
|
|
+ for keyboard in keyboardList {
|
|
|
+ if keyboard.isChoose == true {
|
|
|
+ self.chooseKeyboard = keyboard
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ success()
|
|
|
+ self.userChangeLabel.text = self.chooseKeyboard?.name
|
|
|
+ self.clearPopView()
|
|
|
+ self.nowShowView?.isHidden = false
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+extension KeyboardViewController: KeyboardHelpViewDelegate, KeyboardTeachViewDelegate, KeyboardPrologueViewDelegate {
|
|
|
+
|
|
|
+ // 帮聊点击cell
|
|
|
+ func helpCollectionViewDidSelectItem(characterId: String, content: String, complete: @escaping (() -> ())) {
|
|
|
+
|
|
|
+ self.requestSuperReply(characterId: characterId, content: content) { text in
|
|
|
+ self.insertText(text)
|
|
|
+ complete()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 教你说点击cell
|
|
|
+ func teachCollectionViewDidSelectItem(characterId: String, content: String, complete: @escaping (([String]) -> ())) {
|
|
|
+
|
|
|
+ self.requestSuperSpeak(characterId: characterId, content: content) { list in
|
|
|
+ complete(list)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 教你说点击 TableViewCell
|
|
|
+ func teachTableViewDidSelectItem(content: String) {
|
|
|
+
|
|
|
+ self.insertText(content)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 开场白点击cell
|
|
|
+ func prologueCollectionViewDidSelectItem(name: String, complete: @escaping (([String]) -> ())) {
|
|
|
+
|
|
|
+ self.requestSuperPrologue(name: name) { list in
|
|
|
+ complete(list)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 开场白点击 TableViewCell
|
|
|
+ func prologueTableViewDidSelectItem(content: String) {
|
|
|
+
|
|
|
+ self.insertText(content)
|
|
|
+ }
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+// MARK: - 处理键盘操作
|
|
|
+extension KeyboardViewController: KeyboardBaseViewDelegate {
|
|
|
+
|
|
|
+ // 插入文字
|
|
|
+ func insertText(_ text: String) {
|
|
|
+
|
|
|
+ self.textDocumentProxy.insertText(text)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 点击粘贴按钮
|
|
|
+ func pasteBtnClickAction() {
|
|
|
+
|
|
|
+ detectPasteboardPermissionAlert { isShowing in
|
|
|
+ if isShowing {
|
|
|
+ print("系统正在显示剪贴板权限弹窗")
|
|
|
+ self.view.toast(text: "请允许访问剪贴板")
|
|
|
+ }
|
|
|
+ if let content = self.getPasteboardContent() {
|
|
|
+ print("获取到剪贴板内容: \(content)")
|
|
|
+ self.view.toast(text: "获取到剪贴板内容")
|
|
|
+ self.helpView.setPasteStr(content: content)
|
|
|
+ self.teachView.setPasteStr(content: content)
|
|
|
+ } else {
|
|
|
+ print("无法获取剪贴板内容")
|
|
|
+ self.view.toast(text: "无法获取剪贴板内容")
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 删除按钮
|
|
|
+ func deleteBtnClickAction() {
|
|
|
+
|
|
|
+ self.textDocumentProxy.deleteBackward()
|
|
|
+ }
|
|
|
+
|
|
|
+ // 清除按钮
|
|
|
+ func clearBtnClickAction() {
|
|
|
+
|
|
|
+ // 获取当前文本
|
|
|
+ let proxy = self.textDocumentProxy
|
|
|
+
|
|
|
+ // 清空所有文本
|
|
|
+ for _ in 0..<100 {
|
|
|
+ proxy.deleteBackward()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 发送按钮
|
|
|
+ func sendBtnClickAction() {
|
|
|
+
|
|
|
+ self.textDocumentProxy.insertText("\n")
|
|
|
+ }
|
|
|
+
|
|
|
+ func deleteBtnLongPressBegin() {
|
|
|
+
|
|
|
+ // 停止已有的定时器
|
|
|
+ stopContinuousDelete()
|
|
|
+
|
|
|
+ // 创建新的定时器,初始间隔为0.5秒
|
|
|
+ deleteTimer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(continuousDeleteAction), userInfo: nil, repeats: false)
|
|
|
+ }
|
|
|
+
|
|
|
+ func deleteBtnLongPressEnd() {
|
|
|
+ stopContinuousDelete()
|
|
|
+ }
|
|
|
+
|
|
|
+ // 停止删除
|
|
|
+ func stopContinuousDelete() {
|
|
|
+ deleteTimer?.invalidate()
|
|
|
+ deleteTimer = nil
|
|
|
+ deleteAcceleration = 0.1 // 重置加速度
|
|
|
+ }
|
|
|
+
|
|
|
+ @objc func continuousDeleteAction() {
|
|
|
+ // 执行删除操作
|
|
|
+ self.textDocumentProxy.deleteBackward()
|
|
|
+
|
|
|
+ // 停止当前定时器
|
|
|
+ deleteTimer?.invalidate()
|
|
|
+
|
|
|
+ // 加速删除(最快0.05秒一次)
|
|
|
+ let newInterval = max(0.05, 0.5 - deleteAcceleration)
|
|
|
+ deleteAcceleration += 0.05 // 每次加速0.05秒
|
|
|
+
|
|
|
+ // 创建新的更快的定时器
|
|
|
+ deleteTimer = Timer.scheduledTimer(timeInterval: newInterval, target: self, selector: #selector(continuousDeleteAction), userInfo: nil, repeats: false)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 监听剪贴板变化
|
|
|
+ func startMonitoringPasteboard() {
|
|
|
+
|
|
|
+ // 记录当前剪贴板变化计数
|
|
|
+ var initialChangeCount = UIPasteboard.general.changeCount
|
|
|
+ // 记录上一次的剪贴板内容
|
|
|
+ var lastPasteboardContent: String? = UIPasteboard.general.string
|
|
|
+
|
|
|
+ // 创建定时器定期检查剪贴板变化
|
|
|
+ Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { [weak self] timer in
|
|
|
+ guard let self = self else {
|
|
|
+ timer.invalidate()
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ let currentChangeCount = UIPasteboard.general.changeCount
|
|
|
+ if currentChangeCount != initialChangeCount {
|
|
|
+ // 剪贴板计数已变化,检查内容是否真的变化
|
|
|
+ if let copiedString = UIPasteboard.general.string {
|
|
|
+ // 只有当内容真的不同时才处理
|
|
|
+ if !copiedString.isEmpty {
|
|
|
+ print("检测到新复制的内容: \(copiedString)")
|
|
|
+ // 更新记录的内容和计数
|
|
|
+ lastPasteboardContent = copiedString
|
|
|
+ initialChangeCount = currentChangeCount
|
|
|
+ // 处理复制的内容
|
|
|
+ self.handleCopiedContent(copiedString)
|
|
|
+ } else {
|
|
|
+ // 内容相同或为空,只更新计数
|
|
|
+ initialChangeCount = currentChangeCount
|
|
|
+ print("剪贴板计数变化但内容未变或为空")
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 剪贴板内容为nil,更新计数
|
|
|
+ initialChangeCount = currentChangeCount
|
|
|
+ lastPasteboardContent = nil
|
|
|
+ print("剪贴板内容被清空")
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理复制的内容
|
|
|
+ func handleCopiedContent(_ content: String) {
|
|
|
+
|
|
|
+ self.helpView.setPasteStr(content: content)
|
|
|
+ self.teachView.setPasteStr(content: content)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查键盘权限
|
|
|
+ func checkPasteboardPermission() -> Bool {
|
|
|
+
|
|
|
+ var hasPasteboardPermission = false
|
|
|
+ // iOS 14及以上版本需要检查权限
|
|
|
+ if #available(iOS 14.0, *) {
|
|
|
+ // 尝试读取剪贴板内容来检查权限
|
|
|
+ let pasteboard = UIPasteboard.general
|
|
|
+
|
|
|
+ if let _ = pasteboard.string {
|
|
|
+ hasPasteboardPermission = true
|
|
|
+ print("键盘扩展有剪贴板访问权限")
|
|
|
+ } else {
|
|
|
+ hasPasteboardPermission = false
|
|
|
+ print("键盘扩展没有剪贴板访问权限")
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // iOS 14以下版本默认有权限
|
|
|
+ hasPasteboardPermission = true
|
|
|
+ }
|
|
|
+
|
|
|
+ return hasPasteboardPermission
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检测系统是否弹出剪贴板权限弹窗
|
|
|
+ func detectPasteboardPermissionAlert(completion: @escaping (Bool) -> Void) {
|
|
|
+ // 记录访问前的时间
|
|
|
+ let startTime = Date()
|
|
|
+
|
|
|
+ // 尝试访问剪贴板
|
|
|
+ let _ = UIPasteboard.general.string
|
|
|
+
|
|
|
+ // 检查访问后的时间延迟
|
|
|
+ let timeDelay = Date().timeIntervalSince(startTime)
|
|
|
+
|
|
|
+ // 如果访问剪贴板的时间超过一定阈值,可能是因为系统弹出了权限弹窗
|
|
|
+ // 通常正常访问剪贴板的时间应该很短,如果超过100毫秒,可能是弹出了弹窗
|
|
|
+ if timeDelay > 0.1 {
|
|
|
+ completion(true)
|
|
|
+ } else {
|
|
|
+ completion(false)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取剪贴板内容
|
|
|
+ func getPasteboardContent() -> String? {
|
|
|
+ let pasteboard = UIPasteboard.general
|
|
|
+ return pasteboard.string
|
|
|
+ }
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+extension KeyboardViewController {
|
|
|
+
|
|
|
+ // 关闭其他弹出页
|
|
|
+ func clearPopView() {
|
|
|
+
|
|
|
+ self.menuView.removeFromSuperview()
|
|
|
+ self.exchangeView.isHidden = true
|
|
|
+ self.helpView.isHidden = true
|
|
|
+ self.teachView.isHidden = true
|
|
|
+ self.prologueView.isHidden = true
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新主要界面
|
|
|
+ func updateMainViewUI() {
|
|
|
+
|
|
|
+ clearPopView()
|
|
|
+
|
|
|
+ switch chooseType {
|
|
|
+ case .help:
|
|
|
+// self.helpView.characterList = self.characterList
|
|
|
+ self.helpView.isHidden = false
|
|
|
+ self.nowShowView = self.helpView
|
|
|
+ self.functionLabel.text = "帮聊"
|
|
|
+ break
|
|
|
+ case .teach:
|
|
|
+// self.teachView.characterList = self.characterList
|
|
|
+ self.teachView.isHidden = false
|
|
|
+ self.nowShowView = self.teachView
|
|
|
+ self.functionLabel.text = "教你说"
|
|
|
+ break
|
|
|
+ case .prologue:
|
|
|
+ self.prologueView.isHidden = false
|
|
|
+ self.nowShowView = self.prologueView
|
|
|
+ self.functionLabel.text = "开场白"
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新人设列表
|
|
|
+ func updateCharacterUI() {
|
|
|
+
|
|
|
+ self.helpView.characterList = self.characterList
|
|
|
+ self.teachView.characterList = self.characterList
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新开场白
|
|
|
+ func updatePrologueUI() {
|
|
|
+
|
|
|
+ self.prologueView.prologueList = self.prologueList
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新键盘选择按钮
|
|
|
+ func updateUserChangeBtnUI() {
|
|
|
+
|
|
|
+ if self.keyboardList?.count == 1 {
|
|
|
+
|
|
|
+ self.keyboardList?[0].isChoose = true
|
|
|
+ self.chooseKeyboard = self.keyboardList?.first
|
|
|
+ } else {
|
|
|
+
|
|
|
+ if let keyboardList = self.keyboardList {
|
|
|
+ for keyboard in keyboardList {
|
|
|
+ if keyboard.isChoose == true {
|
|
|
+ self.chooseKeyboard = keyboard
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if let chooseKeyboard = self.chooseKeyboard {
|
|
|
+ self.userChangeLabel.text = chooseKeyboard.name
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ func setUI() {
|
|
|
+
|
|
|
+ self.view.addSubview(bgImageView)
|
|
|
+ bgImageView.snp.makeConstraints { make in
|
|
|
+ make.edges.equalToSuperview()
|
|
|
+ }
|
|
|
+
|
|
|
+ self.view.addSubview(menuBtn)
|
|
|
+ menuBtn.snp.makeConstraints { make in
|
|
|
+ make.size.equalTo(CGSize(width: 32, height: 32))
|
|
|
+ make.left.equalTo(13)
|
|
|
+ make.top.equalTo(17)
|
|
|
+ }
|
|
|
+
|
|
|
+ self.view.addSubview(heartAnimation)
|
|
|
+ heartAnimation.snp.makeConstraints { make in
|
|
|
+ make.size.equalTo(CGSize(width: 35, height: 30))
|
|
|
+ make.right.equalTo(-10)
|
|
|
+ make.centerY.equalTo(menuBtn.snp.centerY)
|
|
|
+ }
|
|
|
+ heartAnimation.play()
|
|
|
+
|
|
|
+ self.view.addSubview(exchangeBtn)
|
|
|
+ exchangeBtn.snp.makeConstraints { make in
|
|
|
+ make.size.equalTo(CGSize(width: 34, height: 34))
|
|
|
+ make.right.equalTo(heartAnimation.snp.left).offset(-10)
|
|
|
+ make.centerY.equalTo(menuBtn.snp.centerY)
|
|
|
+ }
|
|
|
+
|
|
|
+ self.view.addSubview(teachView)
|
|
|
+ teachView.snp.makeConstraints { make in
|
|
|
+ make.left.right.bottom.equalTo(0)
|
|
|
+ make.top.equalTo(60)
|
|
|
+ }
|
|
|
+
|
|
|
+ self.view.addSubview(helpView)
|
|
|
+ helpView.snp.makeConstraints { make in
|
|
|
+ make.left.right.bottom.equalTo(0)
|
|
|
+ make.top.equalTo(60)
|
|
|
+ }
|
|
|
+
|
|
|
+ self.view.addSubview(prologueView)
|
|
|
+ prologueView.snp.makeConstraints { make in
|
|
|
+ make.left.right.bottom.equalTo(0)
|
|
|
+ make.top.equalTo(menuBtn.snp.bottom)
|
|
|
+ }
|
|
|
+
|
|
|
+ self.view.addSubview(chooseView)
|
|
|
+ chooseView.snp.makeConstraints { make in
|
|
|
+ make.width.equalTo(147)
|
|
|
+ make.height.equalTo(34)
|
|
|
+ make.left.equalTo(menuBtn.snp.right).offset(9)
|
|
|
+ make.centerY.equalTo(menuBtn.snp.centerY)
|
|
|
+ }
|
|
|
+
|
|
|
+ self.view.addSubview(exchangeView)
|
|
|
+ exchangeView.snp.makeConstraints { make in
|
|
|
+ make.top.equalTo(menuBtn.snp.bottom)
|
|
|
+ make.left.right.bottom.equalTo(0)
|
|
|
+ }
|
|
|
+
|
|
|
+ chooseView.addSubview(functionView)
|
|
|
+ functionView.snp.makeConstraints { make in
|
|
|
+ make.top.left.equalTo(1)
|
|
|
+ make.bottom.equalTo(-1)
|
|
|
+ make.width.equalTo(85)
|
|
|
+ }
|
|
|
+
|
|
|
+ functionView.addSubview(functionLabel)
|
|
|
+ functionLabel.snp.makeConstraints { make in
|
|
|
+ make.left.equalTo(12)
|
|
|
+ make.centerY.equalToSuperview()
|
|
|
+ }
|
|
|
+
|
|
|
+ functionView.addSubview(arrowIcon)
|
|
|
+ arrowIcon.snp.makeConstraints { make in
|
|
|
+ make.size.equalTo(CGSize(width: 18, height: 18))
|
|
|
+ make.centerY.equalToSuperview()
|
|
|
+ make.right.equalTo(-5)
|
|
|
+ }
|
|
|
+
|
|
|
+ chooseView.addSubview(userChangeView)
|
|
|
+ userChangeView.snp.makeConstraints { make in
|
|
|
+ make.left.equalTo(functionView.snp.right)
|
|
|
+ make.right.equalToSuperview()
|
|
|
+ make.top.bottom.equalTo(0)
|
|
|
+ }
|
|
|
+
|
|
|
+ userChangeView.addSubview(userChangeIcon)
|
|
|
+ userChangeIcon.snp.makeConstraints { make in
|
|
|
+ make.size.equalTo(CGSize(width: 12, height: 12))
|
|
|
+ make.centerY.equalToSuperview()
|
|
|
+ make.right.equalTo(-8)
|
|
|
+ }
|
|
|
+
|
|
|
+ userChangeView.addSubview(userChangeLabel)
|
|
|
+ userChangeLabel.snp.makeConstraints { make in
|
|
|
+ make.right.equalTo(userChangeIcon.snp.left).offset(-4)
|
|
|
+ make.left.equalTo(4)
|
|
|
+ make.centerY.equalToSuperview()
|
|
|
+ }
|
|
|
+
|
|
|
+// chooseView.addSubview(userChangeBtn)
|
|
|
+// userChangeBtn.snp.makeConstraints { make in
|
|
|
+// make.left.equalTo(functionView.snp.right)
|
|
|
+// make.right.equalToSuperview()
|
|
|
+// make.top.bottom.equalTo(0)
|
|
|
+// }
|
|
|
+
|
|
|
+ self.nowShowView = self.helpView
|
|
|
+ }
|
|
|
+}
|