Files
VoidLink/Limelight/ViewControllers/CommandManagerViewController.swift
T
2025-06-06 12:00:23 +08:00

352 lines
16 KiB
Swift

//
// KeyManagerViewController.swift
// Voidex
//
// Created by True砖家 on 2024/7/23.
// Copyright © 2024 True砖家 on Bilibili. All rights reserved.
//
import UIKit
@objc public class CommandManagerViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
public let tableView = UITableView()
private let addButton = UIButton(type: .system)
private let deleteButton = UIButton(type: .system)
private let editButton = UIButton(type: .system)
private let exitButton = UIButton(type: .system)
private let pinButton = UIButton(type: .system)
private let viewBackgroundColor = UIColor(white: 0.2, alpha: 0.8);
private let highlightColor = UIColor(white: 0.39, alpha: 0.8);
private let titleLabel = UILabel()
@objc public var specialEntries : NSMutableArray = ["widgetTool"]
private let specialEntryAliasDic : [String:String] = [
"widgetTool":SwiftLocalizationHelper.localizedString(forKey: "[ Open On-Screen Widget Tool ]")
]
private var viewPinned: Bool = false
private var isEditingMode: Bool = false {
didSet {
updateEditingMode()
}
}
override public func viewDidLoad() {
super.viewDidLoad()
setupViews()
updateEditingMode()
tableView.dataSource = self
tableView.delegate = self
// Register the cell class
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
//pass self to the CommandManager
CommandManager.shared.viewController = self
}
override public func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
setupConstraints()
reloadTableView()
}
private func setupViews() {
// Set corner radius
view.layer.cornerRadius = 20 // Adjust the corner radius
view.layer.masksToBounds = true
// Set up the title label
titleLabel.text = SwiftLocalizationHelper.localizedString(forKey: "Command Tool")
titleLabel.font = UIFont.boldSystemFont(ofSize: 20) // Adjust font size as needed
titleLabel.textColor = UIColor.white // Adjust color as needed
titleLabel.textAlignment = .center
view.addSubview(titleLabel)
view.backgroundColor = viewBackgroundColor
tableView.backgroundColor = .clear
tableView.rowHeight = 60
tableView.separatorColor = highlightColor
// Configure buttons
addButton.setTitle(SwiftLocalizationHelper.localizedString(forKey: "Add / Duplicate"), for: .normal)
deleteButton.setTitle(SwiftLocalizationHelper.localizedString(forKey: "Delete"), for: .normal)
editButton.setTitle(SwiftLocalizationHelper.localizedString(forKey: "Edit"), for: .normal)
exitButton.setTitle(SwiftLocalizationHelper.localizedString(forKey: "Exit"), for: .normal)
pinButton.setTitle("📌", for: .normal)
addButton.titleLabel?.font = UIFont.systemFont(ofSize: 20) // Adjust the size as needed
deleteButton.titleLabel?.font = UIFont.systemFont(ofSize: 20)
editButton.titleLabel?.font = UIFont.systemFont(ofSize: 20)
exitButton.titleLabel?.font = UIFont.systemFont(ofSize: 20)
pinButton.titleLabel?.font = UIFont.systemFont(ofSize: 20)
// Add subviews
view.addSubview(tableView)
view.addSubview(addButton)
view.addSubview(deleteButton)
view.addSubview(editButton)
view.addSubview(exitButton)
view.addSubview(pinButton)
// Setup button targets
addButton.addTarget(self, action: #selector(addButtonTapped), for: .touchUpInside)
deleteButton.addTarget(self, action: #selector(deleteButtonTapped), for: .touchUpInside)
editButton.addTarget(self, action: #selector(editButtonTapped), for: .touchUpInside)
exitButton.addTarget(self, action: #selector(exitButtonTapped), for: .touchUpInside)
pinButton.addTarget(self, action: #selector(pinButtonTapped), for: .touchUpInside)
pinButton.contentEdgeInsets = UIEdgeInsets(top: 7, left: 20, bottom: 7, right: 20)
}
private func setupConstraints() {
guard view.superview != nil else {
return
}
view.translatesAutoresizingMaskIntoConstraints = false
tableView.translatesAutoresizingMaskIntoConstraints = false
addButton.translatesAutoresizingMaskIntoConstraints = false
deleteButton.translatesAutoresizingMaskIntoConstraints = false
editButton.translatesAutoresizingMaskIntoConstraints = false
exitButton.translatesAutoresizingMaskIntoConstraints = false
pinButton.translatesAutoresizingMaskIntoConstraints = false
titleLabel.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
// Set the width and height of the view
view.widthAnchor.constraint(equalTo: view.superview!.widthAnchor, multiplier: 1),
view.heightAnchor.constraint(equalTo: view.superview!.heightAnchor, multiplier: 1),
// Center the view horizontally and vertically
view.centerXAnchor.constraint(equalTo: view.superview!.centerXAnchor),
view.centerYAnchor.constraint(equalTo: view.superview!.centerYAnchor),
// ViewTitle constrains
titleLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 13.5),
titleLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
titleLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10),
// TableView constraints
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10),
tableView.topAnchor.constraint(equalTo: view.topAnchor, constant: 50),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -60),
// ExitButton constraints
exitButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 25),
exitButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -10),
// EditButton constraints
editButton.leadingAnchor.constraint(equalTo: exitButton.trailingAnchor, constant: 50),
editButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -10),
// AddButton constraints
addButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -25),
addButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -10),
// DeleteButton constraints
deleteButton.trailingAnchor.constraint(equalTo: addButton.leadingAnchor, constant: -50),
deleteButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -10),
// DeleteButton constraints
pinButton.trailingAnchor.constraint(equalTo: deleteButton.leadingAnchor, constant: -50),
pinButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -10),
])
}
private func updateEditingMode() {
addButton.isEnabled = isEditingMode
deleteButton.isEnabled = isEditingMode
if(isEditingMode){ editButton.setTitle(SwiftLocalizationHelper.localizedString(forKey: "Done"), for: .normal) }
else{ editButton.setTitle(SwiftLocalizationHelper.localizedString(forKey: "Edit"), for: .normal) }
}
@objc private func pinButtonTapped() {
viewPinned = !viewPinned
if viewPinned {
pinButton.backgroundColor = highlightColor
}
else{
pinButton.backgroundColor = viewBackgroundColor
}
}
@objc private func addButtonTapped() {
let previouslySelectedIndexPath = tableView.indexPathForSelectedRow //memorize selected indexpath
let alert = UIAlertController(title: SwiftLocalizationHelper.localizedString(forKey: "New Command"), message: SwiftLocalizationHelper.localizedString(forKey: "Enter a new command and alias"), preferredStyle: .alert)
alert.addTextField { $0.placeholder = SwiftLocalizationHelper.localizedString(forKey:"Command") }
alert.addTextField { $0.placeholder = SwiftLocalizationHelper.localizedString(forKey: "Alias (optional)") }
alert.textFields?[0].keyboardType = .asciiCapable
alert.textFields?[0].autocorrectionType = .no
alert.textFields?[0].spellCheckingType = .no
alert.textFields?[1].keyboardType = .asciiCapable
alert.textFields?[1].autocorrectionType = .no
alert.textFields?[1].spellCheckingType = .no
let submitAction = UIAlertAction(title: SwiftLocalizationHelper.localizedString(forKey: "Add"), style: .default) { [unowned alert] _ in
let keyboardCmdString = alert.textFields?[0].text ?? ""
let alias = alert.textFields?[1].text ?? keyboardCmdString
let newCommand = RemoteCommand(cmdString: keyboardCmdString, alias: alias)
let addCommandSuceeded = CommandManager.shared.addCommand(newCommand)
//self.reloadTableView() // don't know why but this reload has to be called from the CommandManager, it doesn't work here.
//if previouslySelectedIndexPath == nil { return }
if addCommandSuceeded {
let lastRow = self.tableView.numberOfRows(inSection: 0) - 1 //for now there's only 1 section for the tableview, just use setion 0
let newEntryIndexPath = IndexPath(row: lastRow, section: 0)
self.tableView.selectRow(at: newEntryIndexPath, animated: true, scrollPosition: .middle) // shift the highlight to the newly added entry
}
else {
self.tableView.selectRow(at: previouslySelectedIndexPath, animated: true, scrollPosition: .middle) // keep the highlight on the previous entry if failed to add command
}
}
let cancelAction = UIAlertAction(title: SwiftLocalizationHelper.localizedString(forKey:"Cancel"), style: .cancel)
alert.addAction(submitAction)
alert.addAction(cancelAction)
if let selectedIndexPath = self.tableView.indexPathForSelectedRow {
if isSpecialEntrySelected() {return}
let selectedCommand = CommandManager.shared.getAllCommands()[selectedIndexPath.row-specialEntries.count]
alert.textFields?[0].text = selectedCommand.cmdString // load selected keyboard cmd string
//alert.textFields?[1].text = selectedCommand.alias // leave the alias input field empty
}
self.present(alert, animated: true)
}
@objc private func deleteButtonTapped() {
guard let selectedIndexPath = tableView.indexPathForSelectedRow else {
return
}
if isSpecialEntrySelected() {return}
let previouslySelectedIndexPath = selectedIndexPath // memorize selected row before reloading tableview
CommandManager.shared.deleteCommand(at: selectedIndexPath.row-specialEntries.count)
reloadTableView()
// target new entry after deletion
var targetRowAfterDel = previouslySelectedIndexPath.row - 1
if targetRowAfterDel < 0 { targetRowAfterDel = 0 }
let targetIndexPathAfterDel = IndexPath(row: targetRowAfterDel, section: previouslySelectedIndexPath.section)
if targetRowAfterDel < tableView.numberOfRows(inSection: previouslySelectedIndexPath.section) {
tableView.selectRow(at: targetIndexPathAfterDel, animated: true, scrollPosition: .middle) // shift highlight to the target entry after deletion
}
}
@objc private func editButtonTapped() {
isEditingMode.toggle()
deleteButton.isEnabled = !isSpecialEntrySelected()
addButton.isEnabled = !isSpecialEntrySelected()
}
@objc private func exitButtonTapped() {
dismiss(animated: true, completion: nil)
}
@objc public func reloadTableView() {
tableView.layoutIfNeeded()
tableView.reloadData()
/*
let previouslySelectedIndexPath = tableView.indexPathForSelectedRow
if let indexPath = previouslySelectedIndexPath {
// Make sure the indexPath is still valid and scroll to the selected indexPath
if indexPath.row < tableView.numberOfRows(inSection: indexPath.section) {
tableView.selectRow(at: indexPath, animated: true, scrollPosition: .middle) // keep the entry of previous index selected.
}
} */
}
// UITableViewDataSource
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return specialEntries.count + CommandManager.shared.getAllCommands().count
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
// Configure cell appearance
cell.backgroundColor = .clear // Set background color of the cell
cell.textLabel?.textColor = UIColor.white // Set font color of the text
// Set selected background view
let selectedBackgroundView = UIView()
selectedBackgroundView.backgroundColor = highlightColor // Color for the selected state
cell.selectedBackgroundView = selectedBackgroundView
if indexPath.row < specialEntries.count {
cell.textLabel?.text = specialEntryAliasDic[specialEntries[indexPath.row] as! String]
}
else {
let command = CommandManager.shared.getAllCommands()[indexPath.row-specialEntries.count]
cell.textLabel?.text = command.alias
}
return cell
}
// UITableViewDelegate
public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) {
// Set the cell background color to the flashing color
// Animate the flash effect
UIView.animate(withDuration: 0.1, animations: {
cell.selectedBackgroundView?.backgroundColor = .clear
}) { _ in
// Reset the cell background color after the animation
UIView.animate(withDuration: 0.1) {
cell.selectedBackgroundView?.backgroundColor = self.highlightColor
}
}
}
if !isEditingMode {
// Sending keyboard command
if indexPath.row < specialEntries.count{
handleSpecialEntries(index: indexPath.row)
}
else {
let command = CommandManager.shared.getAllCommands()[indexPath.row-specialEntries.count]
sendKeyboardCommand(command)
}
if !viewPinned { dismiss(animated: false, completion: nil) } // dimiss the view in sending mode & the view is not pinned
}
else {
let specialEntrySelected = indexPath.row < specialEntries.count
addButton.isEnabled = !specialEntrySelected
deleteButton.isEnabled = !specialEntrySelected
}
}
private func isSpecialEntrySelected() -> Bool {
return self.tableView.indexPathForSelectedRow?.row ?? specialEntries.count+1 < specialEntries.count
}
private func handleSpecialEntries(index:Int){
switch specialEntries[index] as? String {
case "widgetTool":
usleep(1000*150)
dismiss(animated: true, completion: nil)
NotificationCenter.default.post(name: Notification.Name("OscLayoutToolOpenedFromCmdToolNotification"),object: nil) // inform streamFrameViewController to open osc tool
default: break
}
}
private func sendKeyboardCommand(_ cmd: RemoteCommand) {
print("Sending key-value")
let keyboardCmdStrings = CommandManager.shared.extractKeyStringsFromComboCommand(from: cmd.cmdString)
CommandManager.shared.sendKeyComboCommand(keyboardCmdStrings: keyboardCmdStrings!)
}
}