refactor: simplify remote desktop UI and add maximize toggle

This commit is contained in:
Haitao Pan 2026-06-03 11:47:04 +08:00
parent 81ecfba85a
commit 842227a4d5
4 changed files with 231 additions and 145 deletions

View File

@ -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<DesktopView> createState() => _DesktopViewState();
@ -29,6 +36,7 @@ class _DesktopViewState extends State<DesktopView> {
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<DesktopView> {
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,
),
],
),
],
),
),
],
],
),
),

View File

@ -15,6 +15,38 @@ class SettingsRemoteDesktopPanel extends StatefulWidget {
}
class _SettingsRemoteDesktopPanelState extends State<SettingsRemoteDesktopPanel> {
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<SettingsRemoteDesktopPanel>
],
),
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,
),
),
],
);
}

View File

@ -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

View File

@ -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);
});