From eb6adea47c432bbe27b04054f09f35a480437378 Mon Sep 17 00:00:00 2001 From: True Zhuanjia Date: Tue, 23 Jul 2024 23:52:20 +0800 Subject: [PATCH] UI & Data persistence for Send Special Keys menu --- Limelight/Input/KeyManager.swift | 126 ++++++++++ Limelight/Localization/Localizable.xcstrings | 14 ++ .../SwiftLocalizationHelper.swift | 17 ++ .../KeyManagerViewController.swift | 237 ++++++++++++++++++ .../StreamFrameViewController.m | 21 +- Moonlight.xcodeproj/project.pbxproj | 12 + 6 files changed, 424 insertions(+), 3 deletions(-) create mode 100644 Limelight/Input/KeyManager.swift create mode 100644 Limelight/Localization/SwiftLocalizationHelper.swift create mode 100644 Limelight/ViewControllers/KeyManagerViewController.swift diff --git a/Limelight/Input/KeyManager.swift b/Limelight/Input/KeyManager.swift new file mode 100644 index 00000000..3592de02 --- /dev/null +++ b/Limelight/Input/KeyManager.swift @@ -0,0 +1,126 @@ +// +// KeyManager.swift +// Moonlight-ZWM +// +// Created by ZWM on 2024/7/23. +// Copyright © 2024 Moonlight Game Streaming Project. All rights reserved. +// + +import Foundation +import UIKit + +@objc public class KeyManager: NSObject { + @objc public static let shared = KeyManager() + + private var strings: [String] = [] + private let keyMappings: [String: UInt16] = [ + "CTRL": 0x11, + "ALT": 0x12, + "DEL": 0x2E, + "F1": 0x70, + "F2": 0x71 + ] + + private weak var viewController: KeyManagerViewController? + + private override init() { + super.init() + loadStrings() + } + + @objc public func createTestKeyMappings() -> [String: UInt16] { + return keyMappings + } + + @objc public func extractKeyStrings(from input: String) -> [String]? { + let keys = keyMappings.keys.joined(separator: "|") + let pattern = "^((?:\(keys))(?:\\+\(keys))*)$" + + guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else { + return nil + } + + let range = NSRange(location: 0, length: input.utf16.count) + guard let match = regex.firstMatch(in: input, options: [], range: range) else { + return nil + } + + let matchedString = (input as NSString).substring(with: match.range(at: 1)) + let keyStrings = matchedString.split(separator: "+").map { String($0) } + return keyStrings + } + + @objc public func addString(_ string: String) { + /* guard isValidString(string) else { + return + } */ + strings.append(string) + saveStrings() + viewController?.reloadTableView() + } + + @objc public func deleteString(at index: Int) { + guard index >= 0 && index < strings.count else { + return + } + strings.remove(at: index) + saveStrings() + viewController?.reloadTableView() + } + + @objc public func getAllStrings() -> [String] { + return strings + } + + private func isValidString(_ string: String) -> Bool { + let keys = keyMappings.keys.joined(separator: "|") + let pattern = "^((?:\(keys))(?:\\+\(keys))*)$" + + guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else { + return false + } + + let range = NSRange(location: 0, length: string.utf16.count) + return regex.firstMatch(in: string, options: [], range: range) != nil + } + + private func loadStrings() { + if let savedStrings = UserDefaults.standard.array(forKey: "savedStrings") as? [String] { + strings = savedStrings + } + } + + private func saveStrings() { + UserDefaults.standard.set(strings, forKey: "savedStrings") + } + + @objc public func setupViewController(_ viewController: KeyManagerViewController) { + self.viewController = viewController + let addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(promptForNewString)) + viewController.navigationItem.rightBarButtonItem = addButton + viewController.navigationItem.title = "Manage Strings" + + + viewController.tableView.delegate = viewController + viewController.tableView.dataSource = viewController + //viewController.tableView.backgroundColor = UIColor.black.withAlphaComponent(0.5) + + } + + @objc private func promptForNewString() { + guard let topController = UIApplication.shared.keyWindow?.rootViewController else { + return + } + + let alert = UIAlertController(title: "New String", message: "Enter a new string", preferredStyle: .alert) + alert.addTextField() + + let submitAction = UIAlertAction(title: "Add", style: .default) { [unowned alert] _ in + let newString = alert.textFields![0].text ?? "" + self.addString(newString) + } + + alert.addAction(submitAction) + topController.present(alert, animated: true) + } +} diff --git a/Limelight/Localization/Localizable.xcstrings b/Limelight/Localization/Localizable.xcstrings index ea61bfce..bff7ab12 100644 --- a/Limelight/Localization/Localizable.xcstrings +++ b/Limelight/Localization/Localizable.xcstrings @@ -99,6 +99,17 @@ } } }, + "Add" : { + "extractionState" : "manual", + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "新增" + } + } + } + }, "Add Host Manually" : { "extractionState" : "manual", "localizations" : { @@ -732,6 +743,9 @@ } } }, + "Key" : { + "extractionState" : "manual" + }, "Launch App" : { "extractionState" : "manual", "localizations" : { diff --git a/Limelight/Localization/SwiftLocalizationHelper.swift b/Limelight/Localization/SwiftLocalizationHelper.swift new file mode 100644 index 00000000..1ebc4c23 --- /dev/null +++ b/Limelight/Localization/SwiftLocalizationHelper.swift @@ -0,0 +1,17 @@ +// +// SwiftLocalizationHelper.swift +// Moonlight-ZWM +// +// Created by ZWM on 2024/7/23. +// Copyright © 2024 Moonlight Game Streaming Project. All rights reserved. +// + +import Foundation + +class SwiftLocalizationHelper { + + static func localizedString(forKey key: String, _ args: CVarArg...) -> String { + let format = NSLocalizedString(key, tableName: "Localizable", bundle: .main, value: "", comment: "") + return String(format: format, arguments: args) + } +} diff --git a/Limelight/ViewControllers/KeyManagerViewController.swift b/Limelight/ViewControllers/KeyManagerViewController.swift new file mode 100644 index 00000000..93453ec1 --- /dev/null +++ b/Limelight/ViewControllers/KeyManagerViewController.swift @@ -0,0 +1,237 @@ +// +// KeyManagerViewController.swift +// Moonlight-ZWM +// +// Created by ZWM on 2024/7/23. +// Copyright © 2024 Moonlight Game Streaming Project. All rights reserved. +// + +import UIKit + +@objc public class KeyManagerViewController: 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 viewBackgroundColor = UIColor(white: 0.2, alpha: 1.0); + private let highlightColor = UIColor(white: 0.3, alpha: 1.0); + private let titleLabel = UILabel() + + 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") + + KeyManager.shared.setupViewController(self) + } + + override public func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + setupConstraints() + } + + 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: "Send Special Keys") + titleLabel.font = UIFont.boldSystemFont(ofSize: 24) // 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"), 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) + 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) + + // Add subviews + view.addSubview(tableView) + view.addSubview(addButton) + view.addSubview(deleteButton) + view.addSubview(editButton) + view.addSubview(exitButton) + + // 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) + } + + private func setupConstraints() { + + guard let superview = view.superview else { + return + } + + view.translatesAutoresizingMaskIntoConstraints = false + + tableView.translatesAutoresizingMaskIntoConstraints = false + addButton.translatesAutoresizingMaskIntoConstraints = false + deleteButton.translatesAutoresizingMaskIntoConstraints = false + editButton.translatesAutoresizingMaskIntoConstraints = false + exitButton.translatesAutoresizingMaskIntoConstraints = false + titleLabel.translatesAutoresizingMaskIntoConstraints = false + + NSLayoutConstraint.activate([ + + // Set the width and height of the view + view.widthAnchor.constraint(equalTo: view.superview!.widthAnchor, multiplier: 0.7), + view.heightAnchor.constraint(equalTo: view.superview!.heightAnchor, multiplier: 0.75), + + // 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: 20), + 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: 70), + 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), + ]) + } + + 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 addButtonTapped() { + let alertController = UIAlertController(title: SwiftLocalizationHelper.localizedString(forKey: "Add String"), message: nil, preferredStyle: .alert) + alertController.addTextField() + + let addAction = UIAlertAction(title: SwiftLocalizationHelper.localizedString(forKey: "Add"), style: .default) { [weak self] _ in + if let newString = alertController.textFields?.first?.text, !newString.isEmpty { + KeyManager.shared.addString(newString) + self?.reloadTableView() + } + } + let cancelAction = UIAlertAction(title: SwiftLocalizationHelper.localizedString(forKey: "Cancel"), style: .cancel, handler: nil) + alertController.addAction(addAction) + alertController.addAction(cancelAction) + present(alertController, animated: true, completion: nil) + } + + @objc private func deleteButtonTapped() { + if let selectedIndexPath = tableView.indexPathForSelectedRow { + KeyManager.shared.deleteString(at: selectedIndexPath.row) + reloadTableView() + } + } + + @objc private func editButtonTapped() { + isEditingMode.toggle() + } + + @objc private func exitButtonTapped() { + dismiss(animated: true, completion: nil) + } + + @objc public func reloadTableView() { + tableView.reloadData() + } + + // UITableViewDataSource + public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return KeyManager.shared.getAllStrings().count + } + + public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) + // Configure cell appearance + cell.backgroundColor = viewBackgroundColor // 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 + + + let keyStrings = KeyManager.shared.getAllStrings() + cell.textLabel?.text = keyStrings[indexPath.row] + 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 = self.viewBackgroundColor + }) { _ in + // Reset the cell background color after the animation + UIView.animate(withDuration: 0.1) { + cell.selectedBackgroundView?.backgroundColor = self.highlightColor + } + } + } + + if !isEditingMode { + // Call the dummy method for key-value sending implementation + dummyMethodForKeySending() + dismiss(animated: false, completion: nil) + } + } + + private func dummyMethodForKeySending() { + // Dummy implementation + print("Sending key-value") + } +} diff --git a/Limelight/ViewControllers/StreamFrameViewController.m b/Limelight/ViewControllers/StreamFrameViewController.m index 8d045d45..eff462ee 100644 --- a/Limelight/ViewControllers/StreamFrameViewController.m +++ b/Limelight/ViewControllers/StreamFrameViewController.m @@ -54,6 +54,7 @@ CGSize _keyboardSize; #if !TARGET_OS_TV CustomEdgeSwipeGestureRecognizer *_exitSwipeRecognizer; + CustomEdgeSwipeGestureRecognizer *_keyManagerSwipeRecognizer; CustomTapGestureRecognizer *_oscLayoutTapRecoginizer; LayoutOnScreenControlsViewController *_layoutOnScreenControlsVC; #endif @@ -87,7 +88,12 @@ } } -- (void)configExitGesture{ +- (void)presentKeyManagerViewController{ + KeyManagerViewController* keyManViewController = [[KeyManagerViewController alloc] init]; + [self presentViewController:keyManViewController animated:YES completion:nil]; +} + +- (void)configSwipeGestures{ [self.view removeGestureRecognizer:_exitSwipeRecognizer]; _exitSwipeRecognizer = [[CustomEdgeSwipeGestureRecognizer alloc] initWithTarget:self action:@selector(edgeSwiped)]; _exitSwipeRecognizer.edges = _settings.swipeExitScreenEdge.intValue; @@ -95,6 +101,15 @@ _exitSwipeRecognizer.delaysTouchesBegan = NO; _exitSwipeRecognizer.delaysTouchesEnded = NO; [self.view addGestureRecognizer:_exitSwipeRecognizer]; + + + [self.view removeGestureRecognizer:_keyManagerSwipeRecognizer]; + _keyManagerSwipeRecognizer = [[CustomEdgeSwipeGestureRecognizer alloc] initWithTarget:self action:@selector(presentKeyManagerViewController)]; + _keyManagerSwipeRecognizer.edges = UIRectEdgeRight; + _keyManagerSwipeRecognizer.normalizedThresholdDistance = _settings.swipeToExitDistance.floatValue; + _keyManagerSwipeRecognizer.delaysTouchesBegan = NO; + _keyManagerSwipeRecognizer.delaysTouchesEnded = NO; + [self.view addGestureRecognizer:_keyManagerSwipeRecognizer]; } - (void)configZoomGesture{ @@ -123,7 +138,7 @@ //[self.view removeGestureRecognizer:] _settings = [[[DataManager alloc] init] getSettings]; //StreamFrameViewController retrieve the settings here. [self configOscLayoutTool]; - [self configExitGesture]; + [self configSwipeGestures]; [self configZoomGesture]; [self->_streamView disableOnScreenControls]; //don't know why but this must be called outside the streamview class, just put it here. execute in streamview class cause hang [self.mainFrameViewcontroller reloadStreamConfig]; // reload streamconfig @@ -227,7 +242,7 @@ [self.view addGestureRecognizer:_playPauseTapGestureRecognizer]; #else - [self configExitGesture]; // swipe & exit gesture configured here + [self configSwipeGestures]; // swipe & exit gesture configured here [self configOscLayoutTool]; //_oscLayoutTapRecoginizer will be added or removed to the view here #endif diff --git a/Moonlight.xcodeproj/project.pbxproj b/Moonlight.xcodeproj/project.pbxproj index 15e68108..7217de52 100644 --- a/Moonlight.xcodeproj/project.pbxproj +++ b/Moonlight.xcodeproj/project.pbxproj @@ -23,6 +23,9 @@ 8454AC662C31907F00F0A302 /* iPad.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8454AC682C31907F00F0A302 /* iPad.storyboard */; }; 8454AC6A2C319BDA00F0A302 /* iPhone.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8454AC6C2C319BDA00F0A302 /* iPhone.storyboard */; }; 84591A512BE1625E006AAB10 /* CustomEdgeSwipeGestureRecognizer.m in Sources */ = {isa = PBXBuildFile; fileRef = 84591A502BE1625E006AAB10 /* CustomEdgeSwipeGestureRecognizer.m */; }; + 84E1CDEC2C4F67D300547904 /* KeyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E1CDEB2C4F67D300547904 /* KeyManager.swift */; }; + 84E1CDEF2C4F688E00547904 /* KeyManagerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E1CDEE2C4F688E00547904 /* KeyManagerViewController.swift */; }; + 84E1CDFA2C4FE83400547904 /* SwiftLocalizationHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E1CDF92C4FE83400547904 /* SwiftLocalizationHelper.swift */; }; 84FD0B512C31338D006B5BE5 /* LocalizationHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 84FD0B502C31338D006B5BE5 /* LocalizationHelper.m */; }; 84FD0B542C3134A6006B5BE5 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 84FD0B532C3134A6006B5BE5 /* Localizable.xcstrings */; }; 98181BE92791277400E43572 /* libSDL2.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 98181BE82791275D00E43572 /* libSDL2.a */; }; @@ -150,6 +153,9 @@ 8454AC6D2C319BDA00F0A302 /* mul */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; name = mul; path = mul.lproj/iPhone.xcstrings; sourceTree = ""; }; 84591A4F2BE16240006AAB10 /* CustomEdgeSwipeGestureRecognizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CustomEdgeSwipeGestureRecognizer.h; sourceTree = ""; }; 84591A502BE1625E006AAB10 /* CustomEdgeSwipeGestureRecognizer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CustomEdgeSwipeGestureRecognizer.m; sourceTree = ""; }; + 84E1CDEB2C4F67D300547904 /* KeyManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyManager.swift; sourceTree = ""; }; + 84E1CDEE2C4F688E00547904 /* KeyManagerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyManagerViewController.swift; sourceTree = ""; }; + 84E1CDF92C4FE83400547904 /* SwiftLocalizationHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftLocalizationHelper.swift; sourceTree = ""; }; 84FD0B4F2C313343006B5BE5 /* LocalizationHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LocalizationHelper.h; sourceTree = ""; }; 84FD0B502C31338D006B5BE5 /* LocalizationHelper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LocalizationHelper.m; sourceTree = ""; }; 84FD0B532C3134A6006B5BE5 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = ""; }; @@ -448,6 +454,7 @@ 84FD0B4F2C313343006B5BE5 /* LocalizationHelper.h */, 84FD0B502C31338D006B5BE5 /* LocalizationHelper.m */, 84FD0B532C3134A6006B5BE5 /* Localizable.xcstrings */, + 84E1CDF92C4FE83400547904 /* SwiftLocalizationHelper.swift */, ); path = Localization; sourceTree = ""; @@ -615,6 +622,7 @@ 9819CC21254F180F008A7C8E /* AbsoluteTouchHandler.m */, 988FCD3F293B091B003050E2 /* KeyboardInputField.h */, 988FCD40293B091B003050E2 /* KeyboardInputField.m */, + 84E1CDEB2C4F67D300547904 /* KeyManager.swift */, ); path = Input; sourceTree = ""; @@ -678,6 +686,7 @@ FBD3494F19FF2174002D2A60 /* SettingsViewController.m */, FB4A23B61A9D3637004D2EF2 /* LoadingFrameViewController.h */, FB4A23B71A9D3637004D2EF2 /* LoadingFrameViewController.m */, + 84E1CDEE2C4F688E00547904 /* KeyManagerViewController.swift */, ); path = ViewControllers; sourceTree = ""; @@ -1010,6 +1019,7 @@ FB9AFD321A7D867C00872C98 /* AppAssetRetriever.m in Sources */, 843E93BE2C41528700B1651F /* OrientationHelper.swift in Sources */, FB4678FF1A565DAC00377732 /* WakeOnLanManager.m in Sources */, + 84E1CDEC2C4F67D300547904 /* KeyManager.swift in Sources */, FB53E1431BE5DC4400CD6ECE /* IdManager.m in Sources */, 9897B6A1221260EF00966419 /* Controller.m in Sources */, 84351B112C29ACCB00A77989 /* OSCProfilesManager.m in Sources */, @@ -1035,6 +1045,7 @@ FB89463619F646E200339C8A /* StreamFrameViewController.m in Sources */, 9827E7A32514366900F25707 /* HapticContext.m in Sources */, 9832D1361BBCD5C50036EF48 /* TemporaryApp.m in Sources */, + 84E1CDEF2C4F688E00547904 /* KeyManagerViewController.swift in Sources */, 84351B102C29ACCB00A77989 /* OSCProfile.m in Sources */, 84351B2C2C29AD6100A77989 /* OSCProfilesTableViewController.m in Sources */, 98D585701C0ED0E800F6CC00 /* TemporarySettings.m in Sources */, @@ -1043,6 +1054,7 @@ FB89462E19F646E200339C8A /* PairManager.m in Sources */, FB9AFD371A7E02DB00872C98 /* HttpRequest.m in Sources */, FB4678ED1A50C40900377732 /* OnScreenControls.m in Sources */, + 84E1CDFA2C4FE83400547904 /* SwiftLocalizationHelper.swift in Sources */, FB290D0019B2C406004C83CF /* main.m in Sources */, 9896219723D15C7000211983 /* X1Kit.swift in Sources */, FBD3494319FC9C04002D2A60 /* AppAssetManager.m in Sources */,