refactor: simplify remote desktop UI and add maximize toggle
This commit is contained in:
parent
81ecfba85a
commit
842227a4d5
@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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);
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user