diff --git a/iOS/Debugger/Core/AppDelegate+Debugger.swift b/iOS/Debugger/Core/AppDelegate+Debugger.swift index 9885079..a7473f4 100644 --- a/iOS/Debugger/Core/AppDelegate+Debugger.swift +++ b/iOS/Debugger/Core/AppDelegate+Debugger.swift @@ -1,14 +1,30 @@ import UIKit - - /// Extension to AppDelegate for initializing the debugger - extension AppDelegate { - /// Initialize the debugger - func initializeDebugger() { - // Initialize the debugger manager - DebuggerManager.shared.initialize() - - // Log initialization - Debug.shared.log(message: "Debugger initialized", type: .info) - } - } \ No newline at end of file +/// Extension for AppDelegate to initialize the debugger +extension AppDelegate { + /// Initialize the debugger + func initializeDebugger() { + // Initialize the debugger manager + DebuggerManager.shared.initialize() + + // Log initialization + Debug.shared.log(message: "Debugger initialized", type: .info) + } + + /// Add FLEX framework to the project + /// This method should be called in the Podfile or SPM dependencies + /// Example for Podfile: + /// ``` + /// pod 'FLEX', :configurations => ['Debug'] + /// ``` + /// + /// Example for SPM: + /// ``` + /// .package(url: "https://github.com/FLEXTool/FLEX.git", from: "5.0.0") + /// ``` + func addFLEXFramework() { + // This is just a placeholder method to document how to add FLEX to the project + // The actual integration happens through CocoaPods or Swift Package Manager + Debug.shared.log(message: "FLEX framework should be added via CocoaPods or SPM", type: .info) + } +} diff --git a/iOS/Debugger/Core/DebuggerManager.swift b/iOS/Debugger/Core/DebuggerManager.swift index 2228dc6..f4d20da 100644 --- a/iOS/Debugger/Core/DebuggerManager.swift +++ b/iOS/Debugger/Core/DebuggerManager.swift @@ -18,6 +18,9 @@ public final class DebuggerManager { /// The debugger engine private let debuggerEngine = DebuggerEngine.shared + + /// The FLEX debugger adapter + private let flexAdapter = FLEXDebuggerAdapter.shared /// Current debugger view controller private weak var debuggerViewController: DebuggerViewController? @@ -54,6 +57,12 @@ public final class DebuggerManager { public func initialize() { logger.log(message: "Initializing debugger", type: .info) + // Initialize FLEX adapter + flexAdapter.initialize() + + // Enable network monitoring by default + flexAdapter.enableNetworkMonitoring() + // Show the floating button DispatchQueue.main.async { [weak self] in self?.showFloatingButton() @@ -149,6 +158,29 @@ public final class DebuggerManager { self?.logger.log(message: "Floating debugger button removed", type: .info) } } + + /// Show the FLEX explorer directly + public func showFLEXExplorer() { + flexAdapter.showExplorer() + } + + /// Hide the FLEX explorer + public func hideFLEXExplorer() { + flexAdapter.hideExplorer() + } + + /// Toggle the FLEX explorer visibility + public func toggleFLEXExplorer() { + flexAdapter.toggleExplorer() + } + + /// Present a specific FLEX debugging tool + /// - Parameters: + /// - tool: The tool to present + /// - completion: Completion handler called when the tool is presented + public func presentFLEXTool(_ tool: FLEXDebuggerTool, completion: (() -> Void)? = nil) { + flexAdapter.presentTool(tool, completion: completion) + } // MARK: - Private Methods @@ -241,12 +273,25 @@ extension DebuggerManager: DebuggerViewControllerDelegate { func debuggerViewControllerDidRequestDismissal(_: DebuggerViewController) { hideDebugger() } + + func debuggerViewControllerDidRequestFLEXTool(_ viewController: DebuggerViewController, tool: FLEXDebuggerTool) { + presentFLEXTool(tool) { + // Optional: Handle completion if needed + } + } } // MARK: - UIApplication Extension extension UIApplication { - private func findTopViewController(_ controller: UIViewController) -> UIViewController { + func topMostViewController() -> UIViewController? { + guard let keyWindow = keyWindow else { return nil } + return findTopViewController(keyWindow.rootViewController) + } + + private func findTopViewController(_ controller: UIViewController?) -> UIViewController? { + guard let controller = controller else { return nil } + if let presentedController = controller.presentedViewController { return findTopViewController(presentedController) } @@ -273,7 +318,7 @@ extension UIApplication { if #available(iOS 13.0, *) { return UIApplication.shared.connectedScenes .filter { $0.activationState == .foregroundActive } - .first(where: { $0 is UIWindowScene }) + .first(where: { $0 is UIWindowScene })? .flatMap { $0 as? UIWindowScene }?.windows .first(where: { $0.isKeyWindow }) } else { diff --git a/iOS/Debugger/Core/FLEXDebuggerAdapter.swift b/iOS/Debugger/Core/FLEXDebuggerAdapter.swift new file mode 100644 index 0000000..bb8777b --- /dev/null +++ b/iOS/Debugger/Core/FLEXDebuggerAdapter.swift @@ -0,0 +1,377 @@ +import UIKit + +/// Adapter class that integrates FLEX debugging capabilities with the app's debugger +/// This class serves as a bridge between DebuggerManager and FLEX functionality +public final class FLEXDebuggerAdapter { + // MARK: - Singleton + + /// Shared instance of the FLEX debugger adapter + public static let shared = FLEXDebuggerAdapter() + + // MARK: - Properties + + /// Logger for debugger operations + private let logger = Debug.shared + + /// Flag indicating if FLEX is available + private var isFLEXAvailable: Bool { + // Check if FLEX classes are available at runtime + return NSClassFromString("FLEXManager") != nil + } + + /// Flag indicating if FLEX is initialized + private var isFLEXInitialized = false + + // MARK: - Initialization + + private init() { + logger.log(message: "FLEXDebuggerAdapter initialized", type: .info) + } + + // MARK: - Public Methods + + /// Initialize FLEX debugging capabilities + public func initialize() { + guard !isFLEXInitialized else { return } + + if isFLEXAvailable { + // Initialize FLEX using runtime capabilities to avoid direct dependency + initializeFLEX() + isFLEXInitialized = true + logger.log(message: "FLEX debugging capabilities initialized", type: .info) + } else { + logger.log(message: "FLEX framework not available", type: .warning) + } + } + + /// Show the FLEX explorer + public func showExplorer() { + guard isFLEXInitialized, isFLEXAvailable else { + logger.log(message: "FLEX not available or not initialized", type: .warning) + return + } + + // Show FLEX explorer using runtime capabilities + performFLEXSelector("showExplorer") + logger.log(message: "FLEX explorer shown", type: .info) + } + + /// Hide the FLEX explorer + public func hideExplorer() { + guard isFLEXInitialized, isFLEXAvailable else { return } + + // Hide FLEX explorer using runtime capabilities + performFLEXSelector("hideExplorer") + logger.log(message: "FLEX explorer hidden", type: .info) + } + + /// Toggle the FLEX explorer visibility + public func toggleExplorer() { + guard isFLEXInitialized, isFLEXAvailable else { + logger.log(message: "FLEX not available or not initialized", type: .warning) + return + } + + // Toggle FLEX explorer using runtime capabilities + performFLEXSelector("toggleExplorer") + logger.log(message: "FLEX explorer toggled", type: .info) + } + + /// Present a specific FLEX tool + /// - Parameters: + /// - toolName: The name of the tool to present + /// - completion: Completion handler called when the tool is presented + public func presentTool(_ toolName: FLEXDebuggerTool, completion: (() -> Void)? = nil) { + guard isFLEXInitialized, isFLEXAvailable else { + logger.log(message: "FLEX not available or not initialized", type: .warning) + completion?() + return + } + + // Present the specified tool + switch toolName { + case .networkMonitor: + presentNetworkMonitor(completion: completion) + case .viewHierarchy: + presentViewHierarchy(completion: completion) + case .systemLog: + presentSystemLog(completion: completion) + case .fileBrowser: + presentFileBrowser(completion: completion) + case .databaseBrowser: + presentDatabaseBrowser(completion: completion) + case .runtimeBrowser: + presentRuntimeBrowser(completion: completion) + case .userDefaults: + presentUserDefaults(completion: completion) + case .keychain: + presentKeychain(completion: completion) + } + } + + /// Enable network monitoring + public func enableNetworkMonitoring() { + guard isFLEXInitialized, isFLEXAvailable else { + logger.log(message: "FLEX not available or not initialized", type: .warning) + return + } + + // Enable network monitoring using runtime capabilities + if let flexNetworkObserverClass = NSClassFromString("FLEXNetworkObserver") { + _ = performClassSelector(flexNetworkObserverClass, selector: "start") + logger.log(message: "FLEX network monitoring enabled", type: .info) + } + } + + /// Disable network monitoring + public func disableNetworkMonitoring() { + guard isFLEXInitialized, isFLEXAvailable else { return } + + // Disable network monitoring using runtime capabilities + if let flexNetworkObserverClass = NSClassFromString("FLEXNetworkObserver") { + _ = performClassSelector(flexNetworkObserverClass, selector: "stop") + logger.log(message: "FLEX network monitoring disabled", type: .info) + } + } + + // MARK: - Private Methods + + private func initializeFLEX() { + // Initialize FLEX using runtime capabilities + // This avoids direct dependency on FLEX + + // Register default SQLite database password if needed + if let flexManagerClass = NSClassFromString("FLEXManager") as AnyClass?, + let sharedManager = performClassSelector(flexManagerClass, selector: "sharedManager") { + + // Set default SQLite database password if needed + // This is optional and can be customized based on app requirements + + // Enable network monitoring by default + enableNetworkMonitoring() + } + } + + private func performFLEXSelector(_ selectorName: String, withObject object: Any? = nil) -> Any? { + guard let flexManagerClass = NSClassFromString("FLEXManager") as AnyClass?, + let sharedManager = performClassSelector(flexManagerClass, selector: "sharedManager") else { + return nil + } + + let selector = NSSelectorFromString(selectorName) + return perform(selector: selector, on: sharedManager, with: object) + } + + private func performClassSelector(_ class: AnyClass, selector: String) -> Any? { + let sel = NSSelectorFromString(selector) + return perform(selector: sel, on: `class`, with: nil) + } + + private func perform(selector: Selector, on target: Any, with object: Any?) -> Any? { + var methodImplementation: IMP? = nil + + if let targetClass = target as? AnyClass { + // Class method + methodImplementation = class_getMethodImplementation(targetClass, selector) + } else { + // Instance method + methodImplementation = class_getMethodImplementation(type(of: target as AnyObject), selector) + } + + guard let implementation = methodImplementation else { + return nil + } + + typealias FunctionType = @convention(c) (Any, Selector, Any?) -> Any? + let function = unsafeBitCast(implementation, to: FunctionType.self) + + return function(target, selector, object) + } + + // MARK: - Tool Presentation Methods + + private func presentNetworkMonitor(completion: (() -> Void)? = nil) { + guard let flexManagerClass = NSClassFromString("FLEXManager") as AnyClass?, + let sharedManager = performClassSelector(flexManagerClass, selector: "sharedManager") else { + completion?() + return + } + + // Create a block that returns a network history view controller + let viewControllerBlock: @convention(block) () -> UINavigationController = { + let networkClass = NSClassFromString("FLEXNetworkMITMViewController") as! UIViewController.Type + let networkVC = networkClass.init() + return UINavigationController(rootViewController: networkVC) + } + + // Convert Swift closure to Objective-C block + let blockObject = unsafeBitCast(viewControllerBlock, to: AnyObject.self) + + // Present the tool + let presentToolSelector = NSSelectorFromString("presentTool:completion:") + _ = perform(selector: presentToolSelector, on: sharedManager, with: blockObject) + + completion?() + } + + private func presentViewHierarchy(completion: (() -> Void)? = nil) { + performFLEXSelector("toggleExplorer") + completion?() + } + + private func presentSystemLog(completion: (() -> Void)? = nil) { + guard let flexManagerClass = NSClassFromString("FLEXManager") as AnyClass?, + let sharedManager = performClassSelector(flexManagerClass, selector: "sharedManager") else { + completion?() + return + } + + // Create a block that returns a system log view controller + let viewControllerBlock: @convention(block) () -> UINavigationController = { + let logClass = NSClassFromString("FLEXSystemLogViewController") as! UIViewController.Type + let logVC = logClass.init() + return UINavigationController(rootViewController: logVC) + } + + // Convert Swift closure to Objective-C block + let blockObject = unsafeBitCast(viewControllerBlock, to: AnyObject.self) + + // Present the tool + let presentToolSelector = NSSelectorFromString("presentTool:completion:") + _ = perform(selector: presentToolSelector, on: sharedManager, with: blockObject) + + completion?() + } + + private func presentFileBrowser(completion: (() -> Void)? = nil) { + guard let flexManagerClass = NSClassFromString("FLEXManager") as AnyClass?, + let sharedManager = performClassSelector(flexManagerClass, selector: "sharedManager") else { + completion?() + return + } + + // Create a block that returns a file browser view controller + let viewControllerBlock: @convention(block) () -> UINavigationController = { + let fileClass = NSClassFromString("FLEXFileBrowserController") as! UIViewController.Type + let fileVC = fileClass.init() + return UINavigationController(rootViewController: fileVC) + } + + // Convert Swift closure to Objective-C block + let blockObject = unsafeBitCast(viewControllerBlock, to: AnyObject.self) + + // Present the tool + let presentToolSelector = NSSelectorFromString("presentTool:completion:") + _ = perform(selector: presentToolSelector, on: sharedManager, with: blockObject) + + completion?() + } + + private func presentDatabaseBrowser(completion: (() -> Void)? = nil) { + guard let flexManagerClass = NSClassFromString("FLEXManager") as AnyClass?, + let sharedManager = performClassSelector(flexManagerClass, selector: "sharedManager") else { + completion?() + return + } + + // Create a block that returns a database browser view controller + let viewControllerBlock: @convention(block) () -> UINavigationController = { + let dbClass = NSClassFromString("FLEXSQLiteDatabaseManager") as! UIViewController.Type + let dbVC = dbClass.init() + return UINavigationController(rootViewController: dbVC) + } + + // Convert Swift closure to Objective-C block + let blockObject = unsafeBitCast(viewControllerBlock, to: AnyObject.self) + + // Present the tool + let presentToolSelector = NSSelectorFromString("presentTool:completion:") + _ = perform(selector: presentToolSelector, on: sharedManager, with: blockObject) + + completion?() + } + + private func presentRuntimeBrowser(completion: (() -> Void)? = nil) { + guard let flexManagerClass = NSClassFromString("FLEXManager") as AnyClass?, + let sharedManager = performClassSelector(flexManagerClass, selector: "sharedManager") else { + completion?() + return + } + + // Create a block that returns a runtime browser view controller + let viewControllerBlock: @convention(block) () -> UINavigationController = { + let runtimeClass = NSClassFromString("FLEXRuntimeBrowserController") as! UIViewController.Type + let runtimeVC = runtimeClass.init() + return UINavigationController(rootViewController: runtimeVC) + } + + // Convert Swift closure to Objective-C block + let blockObject = unsafeBitCast(viewControllerBlock, to: AnyObject.self) + + // Present the tool + let presentToolSelector = NSSelectorFromString("presentTool:completion:") + _ = perform(selector: presentToolSelector, on: sharedManager, with: blockObject) + + completion?() + } + + private func presentUserDefaults(completion: (() -> Void)? = nil) { + guard let flexManagerClass = NSClassFromString("FLEXManager") as AnyClass?, + let sharedManager = performClassSelector(flexManagerClass, selector: "sharedManager") else { + completion?() + return + } + + // Create a block that returns a user defaults view controller + let viewControllerBlock: @convention(block) () -> UINavigationController = { + let userDefaultsClass = NSClassFromString("FLEXUserDefaultsExplorerViewController") as! UIViewController.Type + let userDefaultsVC = userDefaultsClass.init() + return UINavigationController(rootViewController: userDefaultsVC) + } + + // Convert Swift closure to Objective-C block + let blockObject = unsafeBitCast(viewControllerBlock, to: AnyObject.self) + + // Present the tool + let presentToolSelector = NSSelectorFromString("presentTool:completion:") + _ = perform(selector: presentToolSelector, on: sharedManager, with: blockObject) + + completion?() + } + + private func presentKeychain(completion: (() -> Void)? = nil) { + guard let flexManagerClass = NSClassFromString("FLEXManager") as AnyClass?, + let sharedManager = performClassSelector(flexManagerClass, selector: "sharedManager") else { + completion?() + return + } + + // Create a block that returns a keychain view controller + let viewControllerBlock: @convention(block) () -> UINavigationController = { + let keychainClass = NSClassFromString("FLEXKeychainViewController") as! UIViewController.Type + let keychainVC = keychainClass.init() + return UINavigationController(rootViewController: keychainVC) + } + + // Convert Swift closure to Objective-C block + let blockObject = unsafeBitCast(viewControllerBlock, to: AnyObject.self) + + // Present the tool + let presentToolSelector = NSSelectorFromString("presentTool:completion:") + _ = perform(selector: presentToolSelector, on: sharedManager, with: blockObject) + + completion?() + } +} + +/// Enum representing available FLEX debugging tools +public enum FLEXDebuggerTool { + case networkMonitor + case viewHierarchy + case systemLog + case fileBrowser + case databaseBrowser + case runtimeBrowser + case userDefaults + case keychain +} diff --git a/iOS/Debugger/README.md b/iOS/Debugger/README.md new file mode 100644 index 0000000..7adca83 --- /dev/null +++ b/iOS/Debugger/README.md @@ -0,0 +1,104 @@ +# Enhanced iOS Debugger + +This module provides a comprehensive in-app debugging solution for iOS applications, integrating both custom debugging capabilities and FLEX (Flipboard Explorer) functionality. + +## Features + +### Core Debugging Features +- Runtime debugging with LLDB-like functionality +- Breakpoint management +- Variable inspection +- Memory monitoring +- Performance tracking +- Console output + +### FLEX Integration +- Network request monitoring and inspection +- View hierarchy exploration +- Runtime object browser +- File system and database inspection +- User defaults and keychain viewing +- System logs + +## Architecture + +The debugger consists of several key components: + +1. **DebuggerManager**: Central manager class that coordinates all debugging functionality +2. **DebuggerEngine**: Core engine for runtime debugging capabilities +3. **FLEXDebuggerAdapter**: Adapter that integrates FLEX functionality +4. **UI Components**: Various view controllers for different debugging features +5. **FloatingDebuggerButton**: Entry point for accessing the debugger + +## Usage + +### Initialization + +Initialize the debugger in your AppDelegate: + +```swift +func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Initialize debugger + initializeDebugger() + + return true +} +``` + +### Adding FLEX Framework + +Add FLEX to your project using CocoaPods or Swift Package Manager: + +#### CocoaPods +```ruby +pod 'FLEX', :configurations => ['Debug'] +``` + +#### Swift Package Manager +```swift +.package(url: "https://github.com/FLEXTool/FLEX.git", from: "5.0.0") +``` + +### Accessing the Debugger + +The debugger can be accessed via the floating button that appears on the screen. Tap it to open the debugger interface. + +### Using FLEX Tools + +FLEX tools can be accessed from the "FLEX Tools" tab in the debugger interface or by tapping the "FLEX" button in the navigation bar. + +## Compatibility + +- iOS 15.0+ +- Compatible with both iPhone and iPad +- Supports both portrait and landscape orientations + +## Implementation Details + +### Runtime Integration + +The debugger uses runtime capabilities to integrate with FLEX without creating a direct dependency. This allows for: + +1. Conditional inclusion of FLEX only in debug builds +2. Graceful fallback when FLEX is not available +3. Dynamic loading of FLEX functionality + +### Thread Safety + +The debugger uses dedicated dispatch queues and thread-safe property access to ensure stability in multi-threaded environments. + +### Memory Management + +Care has been taken to avoid retain cycles and memory leaks by using weak references and proper cleanup when the debugger is dismissed. + +## Customization + +The debugger can be customized by modifying the following: + +- **FloatingDebuggerButton**: Appearance and position +- **DebuggerViewController**: Available tabs and features +- **FLEXDebuggerAdapter**: FLEX tool integration + +## License + +This debugger module is part of the main application and is subject to the same license terms. diff --git a/iOS/Debugger/UI/DebuggerViewController.swift b/iOS/Debugger/UI/DebuggerViewController.swift index f49efb2..56a195d 100644 --- a/iOS/Debugger/UI/DebuggerViewController.swift +++ b/iOS/Debugger/UI/DebuggerViewController.swift @@ -4,6 +4,9 @@ import UIKit protocol DebuggerViewControllerDelegate: AnyObject { /// Called when the debugger view controller requests dismissal func debuggerViewControllerDidRequestDismissal(_ viewController: DebuggerViewController) + + /// Called when the debugger view controller requests a FLEX tool + func debuggerViewControllerDidRequestFLEXTool(_ viewController: DebuggerViewController, tool: FLEXDebuggerTool) } /// Main view controller for the debugger UI @@ -65,6 +68,15 @@ class DebuggerViewController: UIViewController { action: #selector(closeButtonTapped) ) navigationItem.rightBarButtonItem = closeButton + + // Add FLEX button + let flexButton = UIBarButtonItem( + title: "FLEX", + style: .plain, + target: self, + action: #selector(flexButtonTapped) + ) + navigationItem.leftBarButtonItem = flexButton // Add execution control buttons let pauseButton = UIBarButtonItem( @@ -134,6 +146,7 @@ class DebuggerViewController: UIViewController { let memoryVC = createMemoryViewController() let networkVC = createNetworkViewController() let performanceVC = createPerformanceViewController() + let flexToolsVC = createFLEXToolsViewController() // Set tab bar items consoleVC.tabBarItem = UITabBarItem(title: "Console", image: UIImage(systemName: "terminal"), tag: 0) @@ -146,6 +159,7 @@ class DebuggerViewController: UIViewController { memoryVC.tabBarItem = UITabBarItem(title: "Memory", image: UIImage(systemName: "memorychip"), tag: 3) networkVC.tabBarItem = UITabBarItem(title: "Network", image: UIImage(systemName: "network"), tag: 4) performanceVC.tabBarItem = UITabBarItem(title: "Performance", image: UIImage(systemName: "gauge"), tag: 5) + flexToolsVC.tabBarItem = UITabBarItem(title: "FLEX Tools", image: UIImage(systemName: "wrench.and.screwdriver"), tag: 6) // Set view controllers viewControllers = [ @@ -155,6 +169,7 @@ class DebuggerViewController: UIViewController { UINavigationController(rootViewController: memoryVC), UINavigationController(rootViewController: networkVC), UINavigationController(rootViewController: performanceVC), + UINavigationController(rootViewController: flexToolsVC), ] debugTabBarController.viewControllers = viewControllers @@ -186,12 +201,23 @@ class DebuggerViewController: UIViewController { private func createPerformanceViewController() -> UIViewController { return PerformanceViewController() } + + private func createFLEXToolsViewController() -> UIViewController { + let flexToolsVC = FLEXToolsViewController() + flexToolsVC.delegate = self + return flexToolsVC + } // MARK: - Actions @objc private func closeButtonTapped() { delegate?.debuggerViewControllerDidRequestDismissal(self) } + + @objc private func flexButtonTapped() { + // Show FLEX explorer + delegate?.debuggerViewControllerDidRequestFLEXTool(self, tool: .viewHierarchy) + } @objc private func pauseButtonTapped() { debuggerEngine.pause() @@ -285,3 +311,11 @@ extension DebuggerViewController: DebuggerEngineDelegate { } } } + +// MARK: - FLEXToolsViewControllerDelegate + +extension DebuggerViewController: FLEXToolsViewControllerDelegate { + func flexToolsViewController(_ viewController: FLEXToolsViewController, didSelectTool tool: FLEXDebuggerTool) { + delegate?.debuggerViewControllerDidRequestFLEXTool(self, tool: tool) + } +} diff --git a/iOS/Debugger/UI/FLEXToolsViewController.swift b/iOS/Debugger/UI/FLEXToolsViewController.swift new file mode 100644 index 0000000..5ba3949 --- /dev/null +++ b/iOS/Debugger/UI/FLEXToolsViewController.swift @@ -0,0 +1,155 @@ +import UIKit + +/// Protocol for FLEX tools view controller delegate +protocol FLEXToolsViewControllerDelegate: AnyObject { + /// Called when a FLEX tool is selected + func flexToolsViewController(_ viewController: FLEXToolsViewController, didSelectTool tool: FLEXDebuggerTool) +} + +/// View controller that provides access to FLEX debugging tools +class FLEXToolsViewController: UIViewController { + // MARK: - Properties + + /// Delegate for handling tool selection + weak var delegate: FLEXToolsViewControllerDelegate? + + /// Table view for displaying available tools + private let tableView = UITableView(frame: .zero, style: .grouped) + + /// Logger instance + private let logger = Debug.shared + + /// Available tools sections + private let sections: [(title: String, tools: [(name: String, tool: FLEXDebuggerTool)])] = [ + ( + "Network", + [ + ("Network Monitor", .networkMonitor) + ] + ), + ( + "Exploration", + [ + ("View Hierarchy", .viewHierarchy), + ("Runtime Browser", .runtimeBrowser) + ] + ), + ( + "Storage", + [ + ("File Browser", .fileBrowser), + ("Database Browser", .databaseBrowser), + ("User Defaults", .userDefaults), + ("Keychain", .keychain) + ] + ), + ( + "System", + [ + ("System Log", .systemLog) + ] + ) + ] + + // MARK: - Lifecycle + + override func viewDidLoad() { + super.viewDidLoad() + + setupUI() + setupTableView() + + logger.log(message: "FLEXToolsViewController loaded", type: .info) + } + + // MARK: - Setup + + private func setupUI() { + title = "FLEX Tools" + view.backgroundColor = .systemBackground + } + + private func setupTableView() { + // Add table view + view.addSubview(tableView) + tableView.frame = view.bounds + tableView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + + // Configure table view + tableView.delegate = self + tableView.dataSource = self + tableView.register(UITableViewCell.self, forCellReuseIdentifier: "ToolCell") + } +} + +// MARK: - UITableViewDataSource + +extension FLEXToolsViewController: UITableViewDataSource { + func numberOfSections(in tableView: UITableView) -> Int { + return sections.count + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return sections[section].tools.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "ToolCell", for: indexPath) + + let tool = sections[indexPath.section].tools[indexPath.row] + + // Configure cell + if #available(iOS 14.0, *) { + var content = cell.defaultContentConfiguration() + content.text = tool.name + + // Add appropriate image based on tool type + switch tool.tool { + case .networkMonitor: + content.image = UIImage(systemName: "network") + case .viewHierarchy: + content.image = UIImage(systemName: "square.3.stack.3d") + case .systemLog: + content.image = UIImage(systemName: "text.append") + case .fileBrowser: + content.image = UIImage(systemName: "folder") + case .databaseBrowser: + content.image = UIImage(systemName: "cylinder.split.1x2") + case .runtimeBrowser: + content.image = UIImage(systemName: "hammer") + case .userDefaults: + content.image = UIImage(systemName: "gearshape") + case .keychain: + content.image = UIImage(systemName: "key") + } + + cell.contentConfiguration = content + } else { + // Fallback for iOS 13 + cell.textLabel?.text = tool.name + cell.accessoryType = .disclosureIndicator + } + + return cell + } + + func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + return sections[section].title + } +} + +// MARK: - UITableViewDelegate + +extension FLEXToolsViewController: UITableViewDelegate { + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + + // Get selected tool + let tool = sections[indexPath.section].tools[indexPath.row].tool + + // Notify delegate + delegate?.flexToolsViewController(self, didSelectTool: tool) + + logger.log(message: "Selected FLEX tool: \(tool)", type: .info) + } +} diff --git a/iOS/Debugger/UI/NetworkMonitorViewController.swift b/iOS/Debugger/UI/NetworkMonitorViewController.swift index 55c0f03..f1a6106 100644 --- a/iOS/Debugger/UI/NetworkMonitorViewController.swift +++ b/iOS/Debugger/UI/NetworkMonitorViewController.swift @@ -1,768 +1,89 @@ import UIKit - - /// View controller for the network tab in the debugger - class NetworkMonitorViewController: UIViewController { - // MARK: - Properties - - /// The debugger engine - private let debuggerEngine = DebuggerEngine.shared - - /// Logger instance - private let logger = Debug.shared - - /// Table view for displaying network requests - private let tableView: UITableView = { - let tableView = UITableView(frame: .zero, style: .plain) - tableView.translatesAutoresizingMaskIntoConstraints = false - tableView.register( - NetworkRequestTableViewCell.self, - forCellReuseIdentifier: NetworkRequestTableViewCell.reuseIdentifier - ) - return tableView - }() - - /// Search bar for filtering requests - private let searchBar: UISearchBar = { - let searchBar = UISearchBar() - searchBar.translatesAutoresizingMaskIntoConstraints = false - searchBar.placeholder = "Filter requests..." - searchBar.searchBarStyle = .minimal - return searchBar - }() - - /// Refresh control for pulling to refresh - private let refreshControl = UIRefreshControl() - - /// Clear button - private let clearButton: UIButton = { - let button = UIButton(type: .system) - button.translatesAutoresizingMaskIntoConstraints = false - button.setTitle("Clear", for: .normal) - button.titleLabel?.font = UIFont.systemFont(ofSize: 16, weight: .medium) - button.tintColor = UIColor.systemRed - return button - }() - - /// Network requests - private var networkRequests: [NetworkRequest] = [] - - /// Filtered network requests - private var filteredRequests: [NetworkRequest] = [] - - /// Current search text - private var searchText: String = "" - - // MARK: - Lifecycle - - override func viewDidLoad() { - super.viewDidLoad() - - setupUI() - setupActions() - setupNetworkMonitoring() - - // Set title - title = "Network" - - // Add some sample data for demonstration - addSampleData() - } - - // MARK: - Setup - - private func setupUI() { - // Set background color - view.backgroundColor = UIColor.systemBackground - - // Add search bar - view.addSubview(searchBar) - - // Add clear button - view.addSubview(clearButton) - - // Add table view - view.addSubview(tableView) - - // Set up constraints - NSLayoutConstraint.activate([ - // Search bar - searchBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), - searchBar.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor), - searchBar.trailingAnchor.constraint(equalTo: clearButton.leadingAnchor, constant: -8), - - // Clear button - clearButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 8), - clearButton.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -16), - clearButton.widthAnchor.constraint(equalToConstant: 60), - clearButton.heightAnchor.constraint(equalToConstant: 30), - - // Table view - tableView.topAnchor.constraint(equalTo: searchBar.bottomAnchor), - tableView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor), - tableView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor), - tableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor), - ]) - - // Set up table view - tableView.delegate = self - tableView.dataSource = self - - // Add refresh control - tableView.refreshControl = refreshControl - - // Set up search bar - searchBar.delegate = self - } - - private func setupActions() { - // Add target for refresh control - refreshControl.addTarget(self, action: #selector(refreshNetworkRequests), for: .valueChanged) - - // Add target for clear button - clearButton.addTarget(self, action: #selector(clearButtonTapped), for: .touchUpInside) - } - - private func setupNetworkMonitoring() { - // In a real implementation, this would set up URLProtocol swizzling - // to intercept and monitor network requests - - // For now, just log that network monitoring is set up - logger.log(message: "Network monitoring set up", type: .info) - } - - // MARK: - Actions - - @objc private func refreshNetworkRequests() { - // In a real implementation, this would refresh the network requests - - // For now, just end refreshing - refreshControl.endRefreshing() - } - - @objc private func clearButtonTapped() { - // Clear network requests - networkRequests.removeAll() - filteredRequests.removeAll() - - // Reload table view - tableView.reloadData() - } - - // MARK: - Helper Methods - - private func addSampleData() { - // Add some sample network requests for demonstration - let request1 = NetworkRequest( - url: URL(string: "https://api.example.com/users")!, - method: "GET", - requestHeaders: ["Authorization": "Bearer token123"], - requestBody: nil, - responseStatus: 200, - responseHeaders: ["Content-Type": "application/json"], - responseBody: "{\"users\": [{\"id\": 1, \"name\": \"John\"}]}", - timestamp: Date(), - duration: 0.35 - ) - - let request2 = NetworkRequest( - url: URL(string: "https://api.example.com/posts")!, - method: "POST", - requestHeaders: ["Authorization": "Bearer token123", "Content-Type": "application/json"], - requestBody: "{\"title\": \"New Post\", \"content\": \"Hello, world!\"}", - responseStatus: 201, - responseHeaders: ["Content-Type": "application/json"], - responseBody: "{\"id\": 42, \"title\": \"New Post\", \"content\": \"Hello, world!\"}", - timestamp: Date().addingTimeInterval(-60), - duration: 0.42 - ) - - let request3 = NetworkRequest( - url: URL(string: "https://api.example.com/invalid")!, - method: "GET", - requestHeaders: ["Authorization": "Bearer token123"], - requestBody: nil, - responseStatus: 404, - responseHeaders: ["Content-Type": "application/json"], - responseBody: "{\"error\": \"Not found\"}", - timestamp: Date().addingTimeInterval(-120), - duration: 0.28 - ) - - // Add requests - networkRequests = [request1, request2, request3] - filteredRequests = networkRequests - - // Reload table view - tableView.reloadData() - } - - private func filterRequests() { - // Apply search filter - if searchText.isEmpty { - filteredRequests = networkRequests - } else { - filteredRequests = networkRequests.filter { request in - request.url.absoluteString.lowercased().contains(searchText.lowercased()) || - request.method.lowercased().contains(searchText.lowercased()) - } - } - - // Reload table view - tableView.reloadData() - } - } - - // MARK: - UITableViewDelegate - - extension NetworkMonitorViewController: UITableViewDelegate { - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - tableView.deselectRow(at: indexPath, animated: true) - - // Get request - let request = filteredRequests[indexPath.row] - - // Show request details - let detailsVC = NetworkRequestDetailsViewController(request: request) - navigationController?.pushViewController(detailsVC, animated: true) - } - - func tableView(_: UITableView, heightForRowAt _: IndexPath) -> CGFloat { - return 70 - } - } - - // MARK: - UITableViewDataSource - - extension NetworkMonitorViewController: UITableViewDataSource { - func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int { - return filteredRequests.count - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let cell = tableView.dequeueReusableCell( - withIdentifier: NetworkRequestTableViewCell.reuseIdentifier, - for: indexPath - ) as? NetworkRequestTableViewCell else { - return UITableViewCell() - } - - // Configure cell - let request = filteredRequests[indexPath.row] - cell.configure(with: request) - - return cell - } +/// View controller for monitoring network activity +class NetworkMonitorViewController: UIViewController { + // MARK: - Properties + + /// Table view for displaying network requests + private let tableView = UITableView(frame: .zero, style: .plain) + + /// Logger instance + private let logger = Debug.shared + + /// FLEX adapter for accessing FLEX functionality + private let flexAdapter = FLEXDebuggerAdapter.shared + + /// Button to launch FLEX network monitor + private lazy var flexNetworkButton: UIButton = { + let button = UIButton(type: .system) + button.setTitle("Open FLEX Network Monitor", for: .normal) + button.titleLabel?.font = UIFont.systemFont(ofSize: 16, weight: .medium) + button.backgroundColor = .systemBlue + button.setTitleColor(.white, for: .normal) + button.layer.cornerRadius = 12 + button.addTarget(self, action: #selector(openFLEXNetworkMonitor), for: .touchUpInside) + return button + }() + + /// Label explaining FLEX integration + private lazy var infoLabel: UILabel = { + let label = UILabel() + label.text = "This app integrates with FLEX for enhanced network monitoring capabilities. Tap the button below to open the FLEX network monitor." + label.textAlignment = .center + label.numberOfLines = 0 + label.font = UIFont.systemFont(ofSize: 16) + label.textColor = .secondaryLabel + return label + }() + + // MARK: - Lifecycle + + override func viewDidLoad() { + super.viewDidLoad() + + setupUI() + setupConstraints() + + logger.log(message: "NetworkMonitorViewController loaded", type: .info) } - - // MARK: - UISearchBarDelegate - - extension NetworkMonitorViewController: UISearchBarDelegate { - func searchBar(_: UISearchBar, textDidChange searchText: String) { - // Update search text - self.searchText = searchText - - // Apply filter - filterRequests() - } - - func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { - // Dismiss keyboard - searchBar.resignFirstResponder() - } + + // MARK: - Setup + + private func setupUI() { + title = "Network Monitor" + view.backgroundColor = .systemBackground + + // Add subviews + view.addSubview(infoLabel) + view.addSubview(flexNetworkButton) } - - // MARK: - NetworkRequest - - struct NetworkRequest { - let url: URL - let method: String - let requestHeaders: [String: String] - let requestBody: String? - let responseStatus: Int - let responseHeaders: [String: String] - let responseBody: String? - let timestamp: Date - let duration: TimeInterval - - var isSuccess: Bool { - return responseStatus >= 200 && responseStatus < 300 - } - - var formattedTimestamp: String { - let formatter = DateFormatter() - formatter.dateFormat = "HH:mm:ss" - return formatter.string(from: timestamp) - } - - var formattedDuration: String { - return String(format: "%.2f s", duration) - } + + private func setupConstraints() { + // Make views respect auto layout + infoLabel.translatesAutoresizingMaskIntoConstraints = false + flexNetworkButton.translatesAutoresizingMaskIntoConstraints = false + + // Set constraints + NSLayoutConstraint.activate([ + // Info label + infoLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor), + infoLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -50), + infoLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20), + infoLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20), + + // FLEX button + flexNetworkButton.centerXAnchor.constraint(equalTo: view.centerXAnchor), + flexNetworkButton.topAnchor.constraint(equalTo: infoLabel.bottomAnchor, constant: 30), + flexNetworkButton.widthAnchor.constraint(equalToConstant: 250), + flexNetworkButton.heightAnchor.constraint(equalToConstant: 50) + ]) } - - // MARK: - NetworkRequestTableViewCell - - class NetworkRequestTableViewCell: UITableViewCell { - // MARK: - Properties - - static let reuseIdentifier = "NetworkRequestTableViewCell" - - /// URL label - private let urlLabel: UILabel = { - let label = UILabel() - label.translatesAutoresizingMaskIntoConstraints = false - label.font = UIFont.systemFont(ofSize: 16, weight: .medium) - label.numberOfLines = 1 - return label - }() - - /// Method label - private let methodLabel: UILabel = { - let label = UILabel() - label.translatesAutoresizingMaskIntoConstraints = false - label.font = UIFont.systemFont(ofSize: 14, weight: .medium) - label.textAlignment = .center - return label - }() - - /// Status label - private let statusLabel: UILabel = { - let label = UILabel() - label.translatesAutoresizingMaskIntoConstraints = false - label.font = UIFont.systemFont(ofSize: 14, weight: .medium) - label.textAlignment = .center - return label - }() - - /// Time label - private let timeLabel: UILabel = { - let label = UILabel() - label.translatesAutoresizingMaskIntoConstraints = false - label.font = UIFont.systemFont(ofSize: 12) - label.textColor = UIColor.secondaryLabel - return label - }() - - /// Duration label - private let durationLabel: UILabel = { - let label = UILabel() - label.translatesAutoresizingMaskIntoConstraints = false - label.font = UIFont.systemFont(ofSize: 12) - label.textColor = UIColor.secondaryLabel - label.textAlignment = .right - return label - }() - - // MARK: - Initialization - - override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - super.init(style: style, reuseIdentifier: reuseIdentifier) - - setupUI() - } - - required init?(coder: NSCoder) { - super.init(coder: coder) - - setupUI() - } - - // MARK: - Setup - - private func setupUI() { - // Add method label - contentView.addSubview(methodLabel) - - // Add URL label - contentView.addSubview(urlLabel) - - // Add status label - contentView.addSubview(statusLabel) - - // Add time label - contentView.addSubview(timeLabel) - - // Add duration label - contentView.addSubview(durationLabel) - - // Set up constraints - NSLayoutConstraint.activate([ - // Method label - methodLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 12), - methodLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16), - methodLabel.widthAnchor.constraint(equalToConstant: 60), - - // URL label - urlLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 12), - urlLabel.leadingAnchor.constraint(equalTo: methodLabel.trailingAnchor, constant: 8), - urlLabel.trailingAnchor.constraint(equalTo: statusLabel.leadingAnchor, constant: -8), - - // Status label - statusLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 12), - statusLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16), - statusLabel.widthAnchor.constraint(equalToConstant: 50), - - // Time label - timeLabel.topAnchor.constraint(equalTo: urlLabel.bottomAnchor, constant: 4), - timeLabel.leadingAnchor.constraint(equalTo: methodLabel.trailingAnchor, constant: 8), - timeLabel.bottomAnchor.constraint(lessThanOrEqualTo: contentView.bottomAnchor, constant: -8), - - // Duration label - durationLabel.topAnchor.constraint(equalTo: statusLabel.bottomAnchor, constant: 4), - durationLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16), - durationLabel.bottomAnchor.constraint(lessThanOrEqualTo: contentView.bottomAnchor, constant: -8), - ]) - } - - // MARK: - Configuration - - func configure(with request: NetworkRequest) { - // Set URL label - urlLabel.text = request.url.absoluteString - - // Set method label - methodLabel.text = request.method - - // Set method label background color - switch request.method { - case "GET": - methodLabel.backgroundColor = UIColor.systemBlue.withAlphaComponent(0.2) - methodLabel.textColor = UIColor.systemBlue - case "POST": - methodLabel.backgroundColor = UIColor.systemGreen.withAlphaComponent(0.2) - methodLabel.textColor = UIColor.systemGreen - case "PUT": - methodLabel.backgroundColor = UIColor.systemOrange.withAlphaComponent(0.2) - methodLabel.textColor = UIColor.systemOrange - case "DELETE": - methodLabel.backgroundColor = UIColor.systemRed.withAlphaComponent(0.2) - methodLabel.textColor = UIColor.systemRed - default: - methodLabel.backgroundColor = UIColor.systemGray.withAlphaComponent(0.2) - methodLabel.textColor = UIColor.systemGray - } - - // Set status label - statusLabel.text = "\(request.responseStatus)" - - // Set status label color - if request.isSuccess { - statusLabel.textColor = UIColor.systemGreen - } else { - statusLabel.textColor = UIColor.systemRed - } - - // Set time label - timeLabel.text = request.formattedTimestamp - - // Set duration label - durationLabel.text = request.formattedDuration - - // Set accessory type - accessoryType = .disclosureIndicator - } - } - - // MARK: - NetworkRequestDetailsViewController - - class NetworkRequestDetailsViewController: UIViewController { - // MARK: - Properties - - /// The network request - private let request: NetworkRequest - - /// Scroll view - private let scrollView: UIScrollView = { - let scrollView = UIScrollView() - scrollView.translatesAutoresizingMaskIntoConstraints = false - return scrollView - }() - - /// Content view - private let contentView: UIView = { - let view = UIView() - view.translatesAutoresizingMaskIntoConstraints = false - return view - }() - - /// Segmented control for switching between request and response - private let segmentedControl: UISegmentedControl = { - let items = ["Request", "Response"] - let segmentedControl = UISegmentedControl(items: items) - segmentedControl.translatesAutoresizingMaskIntoConstraints = false - segmentedControl.selectedSegmentIndex = 0 - return segmentedControl - }() - - /// Request view - private let requestView: UIView = { - let view = UIView() - view.translatesAutoresizingMaskIntoConstraints = false - return view - }() - - /// Response view - private let responseView: UIView = { - let view = UIView() - view.translatesAutoresizingMaskIntoConstraints = false - view.isHidden = true - return view - }() - - // MARK: - Initialization - - init(request: NetworkRequest) { - self.request = request - super.init(nibName: nil, bundle: nil) - } - - @available(*, unavailable) - required init?(coder _: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - Lifecycle - - override func viewDidLoad() { - super.viewDidLoad() - - setupUI() - setupActions() - - // Set title - title = request.url.lastPathComponent - } - - // MARK: - Setup - - private func setupUI() { - // Set background color - view.backgroundColor = UIColor.systemBackground - - // Add scroll view - view.addSubview(scrollView) - - // Add content view - scrollView.addSubview(contentView) - - // Add segmented control - contentView.addSubview(segmentedControl) - - // Add request view - contentView.addSubview(requestView) - - // Add response view - contentView.addSubview(responseView) - - // Set up constraints - NSLayoutConstraint.activate([ - // Scroll view - scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), - scrollView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor), - scrollView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor), - scrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor), - - // Content view - contentView.topAnchor.constraint(equalTo: scrollView.topAnchor), - contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor), - contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor), - contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor), - contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor), - - // Segmented control - segmentedControl.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 16), - segmentedControl.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16), - segmentedControl.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16), - - // Request view - requestView.topAnchor.constraint(equalTo: segmentedControl.bottomAnchor, constant: 16), - requestView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), - requestView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), - requestView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), - - // Response view - responseView.topAnchor.constraint(equalTo: segmentedControl.bottomAnchor, constant: 16), - responseView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), - responseView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), - responseView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), - ]) - - // Set up request view - setupRequestView() - - // Set up response view - setupResponseView() - } - - private func setupRequestView() { - // Create labels for request details - let urlTitleLabel = createTitleLabel(text: "URL:") - let urlValueLabel = createValueLabel(text: request.url.absoluteString) - - let methodTitleLabel = createTitleLabel(text: "Method:") - let methodValueLabel = createValueLabel(text: request.method) - - let headersTitleLabel = createTitleLabel(text: "Headers:") - let headersValueLabel = createValueLabel(text: formatHeaders(request.requestHeaders)) - - let bodyTitleLabel = createTitleLabel(text: "Body:") - let bodyValueLabel = createValueLabel(text: request.requestBody ?? "None") - - // Add labels to request view - requestView.addSubview(urlTitleLabel) - requestView.addSubview(urlValueLabel) - requestView.addSubview(methodTitleLabel) - requestView.addSubview(methodValueLabel) - requestView.addSubview(headersTitleLabel) - requestView.addSubview(headersValueLabel) - requestView.addSubview(bodyTitleLabel) - requestView.addSubview(bodyValueLabel) - - // Set up constraints - NSLayoutConstraint.activate([ - // URL title label - urlTitleLabel.topAnchor.constraint(equalTo: requestView.topAnchor, constant: 16), - urlTitleLabel.leadingAnchor.constraint(equalTo: requestView.leadingAnchor, constant: 16), - urlTitleLabel.widthAnchor.constraint(equalToConstant: 80), - - // URL value label - urlValueLabel.topAnchor.constraint(equalTo: requestView.topAnchor, constant: 16), - urlValueLabel.leadingAnchor.constraint(equalTo: urlTitleLabel.trailingAnchor, constant: 8), - urlValueLabel.trailingAnchor.constraint(equalTo: requestView.trailingAnchor, constant: -16), - - // Method title label - methodTitleLabel.topAnchor.constraint(equalTo: urlValueLabel.bottomAnchor, constant: 16), - methodTitleLabel.leadingAnchor.constraint(equalTo: requestView.leadingAnchor, constant: 16), - methodTitleLabel.widthAnchor.constraint(equalToConstant: 80), - - // Method value label - methodValueLabel.topAnchor.constraint(equalTo: urlValueLabel.bottomAnchor, constant: 16), - methodValueLabel.leadingAnchor.constraint(equalTo: methodTitleLabel.trailingAnchor, constant: 8), - methodValueLabel.trailingAnchor.constraint(equalTo: requestView.trailingAnchor, constant: -16), - - // Headers title label - headersTitleLabel.topAnchor.constraint(equalTo: methodValueLabel.bottomAnchor, constant: 16), - headersTitleLabel.leadingAnchor.constraint(equalTo: requestView.leadingAnchor, constant: 16), - headersTitleLabel.widthAnchor.constraint(equalToConstant: 80), - - // Headers value label - headersValueLabel.topAnchor.constraint(equalTo: methodValueLabel.bottomAnchor, constant: 16), - headersValueLabel.leadingAnchor.constraint(equalTo: headersTitleLabel.trailingAnchor, constant: 8), - headersValueLabel.trailingAnchor.constraint(equalTo: requestView.trailingAnchor, constant: -16), - - // Body title label - bodyTitleLabel.topAnchor.constraint(equalTo: headersValueLabel.bottomAnchor, constant: 16), - bodyTitleLabel.leadingAnchor.constraint(equalTo: requestView.leadingAnchor, constant: 16), - bodyTitleLabel.widthAnchor.constraint(equalToConstant: 80), - - // Body value label - bodyValueLabel.topAnchor.constraint(equalTo: headersValueLabel.bottomAnchor, constant: 16), - bodyValueLabel.leadingAnchor.constraint(equalTo: bodyTitleLabel.trailingAnchor, constant: 8), - bodyValueLabel.trailingAnchor.constraint(equalTo: requestView.trailingAnchor, constant: -16), - bodyValueLabel.bottomAnchor.constraint(equalTo: requestView.bottomAnchor, constant: -16), - ]) - } - - private func setupResponseView() { - // Create labels for response details - let statusTitleLabel = createTitleLabel(text: "Status:") - let statusValueLabel = createValueLabel(text: "\(request.responseStatus)") - - let headersTitleLabel = createTitleLabel(text: "Headers:") - let headersValueLabel = createValueLabel(text: formatHeaders(request.responseHeaders)) - - let bodyTitleLabel = createTitleLabel(text: "Body:") - let bodyValueLabel = createValueLabel(text: request.responseBody ?? "None") - - // Add labels to response view - responseView.addSubview(statusTitleLabel) - responseView.addSubview(statusValueLabel) - responseView.addSubview(headersTitleLabel) - responseView.addSubview(headersValueLabel) - responseView.addSubview(bodyTitleLabel) - responseView.addSubview(bodyValueLabel) - - // Set up constraints - NSLayoutConstraint.activate([ - // Status title label - statusTitleLabel.topAnchor.constraint(equalTo: responseView.topAnchor, constant: 16), - statusTitleLabel.leadingAnchor.constraint(equalTo: responseView.leadingAnchor, constant: 16), - statusTitleLabel.widthAnchor.constraint(equalToConstant: 80), - - // Status value label - statusValueLabel.topAnchor.constraint(equalTo: responseView.topAnchor, constant: 16), - statusValueLabel.leadingAnchor.constraint(equalTo: statusTitleLabel.trailingAnchor, constant: 8), - statusValueLabel.trailingAnchor.constraint(equalTo: responseView.trailingAnchor, constant: -16), - - // Headers title label - headersTitleLabel.topAnchor.constraint(equalTo: statusValueLabel.bottomAnchor, constant: 16), - headersTitleLabel.leadingAnchor.constraint(equalTo: responseView.leadingAnchor, constant: 16), - headersTitleLabel.widthAnchor.constraint(equalToConstant: 80), - - // Headers value label - headersValueLabel.topAnchor.constraint(equalTo: statusValueLabel.bottomAnchor, constant: 16), - headersValueLabel.leadingAnchor.constraint(equalTo: headersTitleLabel.trailingAnchor, constant: 8), - headersValueLabel.trailingAnchor.constraint(equalTo: responseView.trailingAnchor, constant: -16), - - // Body title label - bodyTitleLabel.topAnchor.constraint(equalTo: headersValueLabel.bottomAnchor, constant: 16), - bodyTitleLabel.leadingAnchor.constraint(equalTo: responseView.leadingAnchor, constant: 16), - bodyTitleLabel.widthAnchor.constraint(equalToConstant: 80), - - // Body value label - bodyValueLabel.topAnchor.constraint(equalTo: headersValueLabel.bottomAnchor, constant: 16), - bodyValueLabel.leadingAnchor.constraint(equalTo: bodyTitleLabel.trailingAnchor, constant: 8), - bodyValueLabel.trailingAnchor.constraint(equalTo: responseView.trailingAnchor, constant: -16), - bodyValueLabel.bottomAnchor.constraint(equalTo: responseView.bottomAnchor, constant: -16), - ]) - - // Set status value label color - if request.isSuccess { - statusValueLabel.textColor = UIColor.systemGreen - } else { - statusValueLabel.textColor = UIColor.systemRed - } - } - - private func setupActions() { - // Add target for segmented control - segmentedControl.addTarget(self, action: #selector(segmentChanged), for: .valueChanged) - } - - // MARK: - Actions - - @objc private func segmentChanged(_ sender: UISegmentedControl) { - // Toggle visibility of request and response views - requestView.isHidden = sender.selectedSegmentIndex == 1 - responseView.isHidden = sender.selectedSegmentIndex == 0 - } - - // MARK: - Helper Methods - - private func createTitleLabel(text: String) -> UILabel { - let label = UILabel() - label.translatesAutoresizingMaskIntoConstraints = false - label.font = UIFont.systemFont(ofSize: 16, weight: .medium) - label.text = text - return label - } - - private func createValueLabel(text: String) -> UILabel { - let label = UILabel() - label.translatesAutoresizingMaskIntoConstraints = false - label.font = UIFont.systemFont(ofSize: 14) - label.text = text - label.numberOfLines = 0 - return label - } - - private func formatHeaders(_ headers: [String: String]) -> String { - if headers.isEmpty { - return "None" - } - - return headers.map { key, value in - "\(key): \(value)" - }.joined(separator: "\n") - } + + // MARK: - Actions + + @objc private func openFLEXNetworkMonitor() { + // Open FLEX network monitor + flexAdapter.presentTool(.networkMonitor) + logger.log(message: "Opening FLEX network monitor", type: .info) } +}