diff --git a/lib/features/desktop/desktop_view.dart b/lib/features/desktop/desktop_view.dart index 1cfa201d..79f41038 100644 --- a/lib/features/desktop/desktop_view.dart +++ b/lib/features/desktop/desktop_view.dart @@ -8,9 +8,16 @@ import '../../app/app_controller.dart'; import '../../widgets/surface_card.dart'; class DesktopView extends StatefulWidget { - const DesktopView({super.key, required this.controller}); + const DesktopView({ + super.key, + required this.controller, + this.isMaximized = false, + this.onToggleMaximize, + }); final AppController controller; + final bool isMaximized; + final VoidCallback? onToggleMaximize; @override State createState() => _DesktopViewState(); @@ -29,6 +36,7 @@ class _DesktopViewState extends State { final TextEditingController _bitrateController = TextEditingController(text: '2000'); bool _useGpu = false; + bool _showAdvancedOptions = false; String _connectionState = 'disconnected'; bool _hasStream = false; bool _isFocused = false; @@ -145,158 +153,181 @@ class _DesktopViewState extends State { SurfaceCard( child: Padding( padding: const EdgeInsets.all(16.0), - child: Wrap( - spacing: 16, - runSpacing: 16, - crossAxisAlignment: WrapCrossAlignment.center, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - // Connection Button - ElevatedButton.icon( - onPressed: _toggleConnection, - style: ElevatedButton.styleFrom( - backgroundColor: _connectionState == 'connected' - ? Colors.redAccent - : (_connectionState == 'connecting' - ? Colors.orangeAccent - : theme.colorScheme.primary), - foregroundColor: Colors.white, - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), - ), - icon: Icon( - _connectionState == 'connected' - ? Icons.portable_wifi_off_rounded - : Icons.settings_remote_rounded, - ), - label: Text( - _connectionState == 'connected' - ? '断开连接' - : (_connectionState == 'connecting' ? '正在连接...' : '连接桌面'), - style: const TextStyle(fontWeight: FontWeight.bold), - ), - ), - - // Display Selector - SizedBox( - width: 100, - child: TextField( - controller: _displayController, - enabled: _connectionState == 'disconnected', - decoration: const InputDecoration( - labelText: 'Display', - prefixIcon: Icon(Icons.monitor_rounded, size: 16), - ), - ), - ), - - // Resolution settings - SizedBox( - width: 90, - child: TextField( - controller: _widthController, - enabled: _connectionState == 'disconnected', - keyboardType: TextInputType.number, - decoration: const InputDecoration(labelText: '宽度'), - ), - ), - SizedBox( - width: 90, - child: TextField( - controller: _heightController, - enabled: _connectionState == 'disconnected', - keyboardType: TextInputType.number, - decoration: const InputDecoration(labelText: '高度'), - ), - ), - - // FPS / Bitrate - SizedBox( - width: 70, - child: TextField( - controller: _fpsController, - enabled: _connectionState == 'disconnected', - keyboardType: TextInputType.number, - decoration: const InputDecoration(labelText: '帧率'), - ), - ), - SizedBox( - width: 90, - child: TextField( - controller: _bitrateController, - enabled: _connectionState == 'disconnected', - keyboardType: TextInputType.number, - decoration: const InputDecoration( - labelText: '码率 (kbps)', - ), - ), - ), - - // GPU accelerator toggle - Row( - mainAxisSize: MainAxisSize.min, + Wrap( + spacing: 16, + runSpacing: 16, + crossAxisAlignment: WrapCrossAlignment.center, children: [ - const Text('GPU 加速'), - Switch( - value: _useGpu, - onChanged: _connectionState == 'disconnected' - ? (val) => setState(() => _useGpu = val) - : null, + // Connection Button + ElevatedButton.icon( + onPressed: _toggleConnection, + style: ElevatedButton.styleFrom( + backgroundColor: _connectionState == 'connected' + ? Colors.redAccent + : (_connectionState == 'connecting' + ? Colors.orangeAccent + : theme.colorScheme.primary), + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), + ), + icon: Icon( + _connectionState == 'connected' + ? Icons.portable_wifi_off_rounded + : Icons.settings_remote_rounded, + ), + label: Text( + _connectionState == 'connected' + ? '断开连接' + : (_connectionState == 'connecting' ? '正在连接...' : '连接桌面'), + style: const TextStyle(fontWeight: FontWeight.bold), + ), ), - ], - ), - - // Status Indicator - Container( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), - decoration: BoxDecoration( - color: _connectionState == 'connected' - ? Colors.green.withValues(alpha: 0.15) - : (_connectionState == 'connecting' - ? Colors.orange.withValues(alpha: 0.15) - : Colors.grey.withValues(alpha: 0.15)), - borderRadius: BorderRadius.circular(16), - border: Border.all( - color: _connectionState == 'connected' - ? Colors.green - : (_connectionState == 'connecting' - ? Colors.orange - : Colors.grey), - width: 1, - ), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - width: 8, - height: 8, - decoration: BoxDecoration( + // Status Indicator + Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: _connectionState == 'connected' + ? Colors.green.withValues(alpha: 0.15) + : (_connectionState == 'connecting' + ? Colors.orange.withValues(alpha: 0.15) + : Colors.grey.withValues(alpha: 0.15)), + borderRadius: BorderRadius.circular(16), + border: Border.all( color: _connectionState == 'connected' ? Colors.green : (_connectionState == 'connecting' ? Colors.orange : Colors.grey), - shape: BoxShape.circle, + width: 1, ), ), - const SizedBox(width: 8), - Text( - _connectionState == 'connected' - ? '已连接' - : (_connectionState == 'connecting' ? '连接中' : '未连接'), - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, - color: _connectionState == 'connected' - ? Colors.green - : (_connectionState == 'connecting' - ? Colors.orange - : (isDark ? Colors.white70 : Colors.black87)), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 8, + height: 8, + decoration: BoxDecoration( + color: _connectionState == 'connected' + ? Colors.green + : (_connectionState == 'connecting' + ? Colors.orange + : Colors.grey), + shape: BoxShape.circle, + ), + ), + const SizedBox(width: 8), + Text( + _connectionState == 'connected' + ? '已连接' + : (_connectionState == 'connecting' ? '连接中' : '未连接'), + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: _connectionState == 'connected' + ? Colors.green + : (_connectionState == 'connecting' + ? Colors.orange + : (isDark ? Colors.white70 : Colors.black87)), + ), + ), + ], + ), + ), + // Advanced Options Toggle + TextButton.icon( + onPressed: () => setState(() => _showAdvancedOptions = !_showAdvancedOptions), + icon: Icon(_showAdvancedOptions ? Icons.expand_less : Icons.expand_more), + label: const Text('高级选项'), + ), + // Maximize Toggle + if (widget.onToggleMaximize != null) + IconButton( + onPressed: widget.onToggleMaximize, + icon: Icon(widget.isMaximized ? Icons.fullscreen_exit_rounded : Icons.fullscreen_rounded), + tooltip: widget.isMaximized ? '恢复默认大小' : '最大化', + ), + ], + ), + if (_showAdvancedOptions) ...[ + const SizedBox(height: 16), + Wrap( + spacing: 16, + runSpacing: 16, + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + // Display Selector + SizedBox( + width: 100, + child: TextField( + controller: _displayController, + enabled: _connectionState == 'disconnected', + decoration: const InputDecoration( + labelText: 'Display', + prefixIcon: Icon(Icons.monitor_rounded, size: 16), + ), ), ), + // Resolution settings + SizedBox( + width: 90, + child: TextField( + controller: _widthController, + enabled: _connectionState == 'disconnected', + keyboardType: TextInputType.number, + decoration: const InputDecoration(labelText: '宽度'), + ), + ), + SizedBox( + width: 90, + child: TextField( + controller: _heightController, + enabled: _connectionState == 'disconnected', + keyboardType: TextInputType.number, + decoration: const InputDecoration(labelText: '高度'), + ), + ), + // FPS / Bitrate + SizedBox( + width: 70, + child: TextField( + controller: _fpsController, + enabled: _connectionState == 'disconnected', + keyboardType: TextInputType.number, + decoration: const InputDecoration(labelText: '帧率'), + ), + ), + SizedBox( + width: 90, + child: TextField( + controller: _bitrateController, + enabled: _connectionState == 'disconnected', + keyboardType: TextInputType.number, + decoration: const InputDecoration( + labelText: '码率 (kbps)', + ), + ), + ), + // GPU accelerator toggle + Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Text('GPU 加速'), + Switch( + value: _useGpu, + onChanged: _connectionState == 'disconnected' + ? (val) => setState(() => _useGpu = val) + : null, + ), + ], + ), ], ), - ), + ], ], ), ), diff --git a/lib/features/settings/settings_remote_desktop_panel.dart b/lib/features/settings/settings_remote_desktop_panel.dart index 899d9825..5da69c8b 100644 --- a/lib/features/settings/settings_remote_desktop_panel.dart +++ b/lib/features/settings/settings_remote_desktop_panel.dart @@ -15,6 +15,38 @@ class SettingsRemoteDesktopPanel extends StatefulWidget { } class _SettingsRemoteDesktopPanelState extends State { + final GlobalKey _desktopViewKey = GlobalKey(); + bool _isMaximized = false; + + void _toggleMaximize() { + if (_isMaximized) { + Navigator.of(context).pop(); + } else { + setState(() => _isMaximized = true); + showDialog( + context: context, + useSafeArea: false, + barrierDismissible: false, + builder: (context) { + return Dialog.fullscreen( + child: DesktopView( + key: _desktopViewKey, + controller: widget.controller, + isMaximized: true, + onToggleMaximize: () { + Navigator.of(context).pop(); + }, + ), + ); + }, + ).then((_) { + if (mounted) { + setState(() => _isMaximized = false); + } + }); + } + } + @override Widget build(BuildContext context) { final palette = context.palette; @@ -39,10 +71,16 @@ class _SettingsRemoteDesktopPanelState extends State ], ), const SizedBox(height: 12), - SizedBox( - height: 640, - child: DesktopView(controller: widget.controller), - ), + if (!_isMaximized) + SizedBox( + height: 640, + child: DesktopView( + key: _desktopViewKey, + controller: widget.controller, + isMaximized: false, + onToggleMaximize: _toggleMaximize, + ), + ), ], ); } diff --git a/macos/Podfile.lock b/macos/Podfile.lock index cd2e4f29..732b6b0b 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -4,6 +4,9 @@ PODS: - FlutterMacOS - file_selector_macos (0.0.1): - FlutterMacOS + - flutter_webrtc (0.12.6): + - FlutterMacOS + - WebRTC-SDK (= 125.6422.06) - FlutterMacOS (1.0.0) - irondash_engine_context (0.0.1): - FlutterMacOS @@ -18,10 +21,12 @@ PODS: - FlutterMacOS - super_native_extensions (0.0.1): - FlutterMacOS + - WebRTC-SDK (125.6422.06) DEPENDENCIES: - device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`) - file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`) + - flutter_webrtc (from `Flutter/ephemeral/.symlinks/plugins/flutter_webrtc/macos`) - FlutterMacOS (from `Flutter/ephemeral`) - irondash_engine_context (from `Flutter/ephemeral/.symlinks/plugins/irondash_engine_context/macos`) - package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`) @@ -32,12 +37,15 @@ DEPENDENCIES: SPEC REPOS: trunk: - CocoaAsyncSocket + - WebRTC-SDK EXTERNAL SOURCES: device_info_plus: :path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos file_selector_macos: :path: Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos + flutter_webrtc: + :path: Flutter/ephemeral/.symlinks/plugins/flutter_webrtc/macos FlutterMacOS: :path: Flutter/ephemeral irondash_engine_context: @@ -55,12 +63,14 @@ SPEC CHECKSUMS: CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76 file_selector_macos: 9e9e068e90ebee155097d00e89ae91edb2374db7 + flutter_webrtc: 377dbcebdde6fed0fc40de87bcaaa2bffcec9a88 FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 irondash_engine_context: 893c7d96d20ce361d7e996f39d360c4c2f9869ba package_info_plus: f0052d280d17aa382b932f399edf32507174e870 patrol: cea8074f183a2a4232d0ebd10569ae05149ada42 shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb super_native_extensions: c2795d6d9aedf4a79fae25cb6160b71b50549189 + WebRTC-SDK: 79942c006ea64f6fb48d7da8a4786dfc820bc1db PODFILE CHECKSUM: ef2282d07ab509932defa9bc41c2af9516037afc diff --git a/test/features/settings/settings_remote_desktop_panel_test.dart b/test/features/settings/settings_remote_desktop_panel_test.dart index 1c1ef4c0..6298498f 100644 --- a/test/features/settings/settings_remote_desktop_panel_test.dart +++ b/test/features/settings/settings_remote_desktop_panel_test.dart @@ -31,9 +31,16 @@ void main() { // Verify the panel headers and titles expect(find.text('远程桌面'), findsOneWidget); expect(find.text('连接桌面'), findsOneWidget); - expect(find.text('GPU 加速'), findsOneWidget); - // Verify inputs + // Verify advanced options are hidden initially + expect(find.text('GPU 加速'), findsNothing); + + // Tap to expand advanced options + await tester.tap(find.text('高级选项')); + await tester.pumpAndSettle(); + + // Verify advanced options appear + expect(find.text('GPU 加速'), findsOneWidget); expect(find.widgetWithText(TextField, 'Display'), findsOneWidget); expect(find.text('Display'), findsOneWidget); });