diff --git a/webrtc_controller_flutter/lib/main.dart b/webrtc_controller_flutter/lib/main.dart index 8a81765..984b840 100644 --- a/webrtc_controller_flutter/lib/main.dart +++ b/webrtc_controller_flutter/lib/main.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter_webrtc/flutter_webrtc.dart'; import 'config/ice_servers.dart'; @@ -16,12 +17,9 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { - return MaterialApp( + return CupertinoApp( title: 'WebRTC 控制端', - theme: ThemeData( - colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue), - useMaterial3: true, - ), + theme: const CupertinoThemeData(primaryColor: CupertinoColors.activeBlue), home: const ControllerHome(), ); } @@ -35,17 +33,15 @@ class ControllerHome extends StatefulWidget { } class _ControllerHomeState extends State { - final _serverUrlController = - TextEditingController(text: kDefaultSignalServer); + final _serverUrlController = TextEditingController( + text: kDefaultSignalServer, + ); final _deviceIdController = TextEditingController(); final _targetController = TextEditingController(text: '981964879'); RemoteController? _controller; RTCVideoRenderer? _renderer; - /// 用于在任意位置(含回调中)弹出提示。 - final GlobalKey _scaffoldKey = GlobalKey(); - bool _connected = false; String _status = '状态: 已停止'; String _stats = ''; @@ -76,6 +72,23 @@ class _ControllerHomeState extends State { void _setStatus(String status) => setState(() => _status = status); + /// 以 iOS 风格弹窗提示用户(替代原 Material 的 SnackBar)。 + void _showAlert(String message) { + if (!mounted) return; + showCupertinoDialog( + context: context, + builder: (ctx) => CupertinoAlertDialog( + content: Text(message), + actions: [ + CupertinoDialogAction( + child: const Text('确定'), + onPressed: () => Navigator.of(ctx).pop(), + ), + ], + ), + ); + } + Future _connect() async { final serverUrl = _serverUrlController.text.trim(); final deviceId = _deviceIdController.text.trim(); @@ -132,43 +145,19 @@ class _ControllerHomeState extends State { /// ICE 断开:提示用户并自动返回连接设置面板。 Future _onIceDisconnected(String message) async { - final scaffoldCtx = _scaffoldKey.currentState?.context; - if (mounted && scaffoldCtx != null) { - ScaffoldMessenger.of(scaffoldCtx).showSnackBar( - SnackBar( - content: Text(message), - duration: const Duration(seconds: 3), - ), - ); - } + _showAlert(message); await _disconnect(); } /// 目标被控端不在线:提示用户并复位到连接设置面板。 Future _onTargetOffline(String message) async { - final scaffoldCtx = _scaffoldKey.currentState?.context; - if (mounted && scaffoldCtx != null) { - ScaffoldMessenger.of(scaffoldCtx).showSnackBar( - SnackBar( - content: Text(message), - duration: const Duration(seconds: 3), - ), - ); - } + _showAlert(message); setState(() => _connected = false); } /// 被控端拒绝连接请求:提示用户并复位到连接设置面板。 Future _onConnectionRejected(String message) async { - final scaffoldCtx = _scaffoldKey.currentState?.context; - if (mounted && scaffoldCtx != null) { - ScaffoldMessenger.of(scaffoldCtx).showSnackBar( - SnackBar( - content: Text(message), - duration: const Duration(seconds: 3), - ), - ); - } + _showAlert(message); setState(() => _connected = false); } @@ -186,63 +175,81 @@ class _ControllerHomeState extends State { @override Widget build(BuildContext context) { - return Scaffold( - key: _scaffoldKey, - appBar: AppBar(title: const Text('WebRTC 控制端')), - body: _connected ? _buildControlPanel() : _buildSetupPanel(), + return CupertinoPageScaffold( + // 控制模式(已连接)下隐藏导航栏,避免其半透明浮层遮挡顶部的 + // 状态/统计信息浮层;连接设置面板再显示标题栏。 + navigationBar: _connected + ? null + : const CupertinoNavigationBar( + middle: Text('WebRTC 控制端'), + ), + child: _connected ? _buildControlPanel() : _buildSetupPanel(), ); } Widget _buildSetupPanel() { - return SingleChildScrollView( - padding: const EdgeInsets.all(24), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - 'WebRTC 控制端', - style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), - ), - const SizedBox(height: 24), - const Text('信令服务器地址:', style: TextStyle(fontSize: 14)), - TextField( - controller: _serverUrlController, - decoration: const InputDecoration( - hintText: 'ws://175.178.213.60:8088/ws/signal', + return SafeArea( + child: SingleChildScrollView( + padding: const EdgeInsets.all(24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'WebRTC 控制端', + style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), ), - ), - const SizedBox(height: 16), - const Text('本机设备ID:', style: TextStyle(fontSize: 14)), - TextField( - controller: _deviceIdController, - decoration: const InputDecoration(hintText: '设备ID'), - ), - const SizedBox(height: 16), - const Text('目标被控设备ID:', style: TextStyle(fontSize: 14)), - TextField( - controller: _targetController, - decoration: const InputDecoration(hintText: '被控端设备ID'), - ), - const SizedBox(height: 24), - Text( - _status, - style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), - ), - const SizedBox(height: 24), - SizedBox( - width: double.infinity, - child: ElevatedButton( - onPressed: _connect, - child: const Text('连接被控设备'), + const SizedBox(height: 24), + const Text('信令服务器地址:', style: TextStyle(fontSize: 14)), + const SizedBox(height: 8), + CupertinoTextField( + controller: _serverUrlController, + placeholder: 'ws://175.178.213.60:8088/ws/signal', + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 12), ), - ), - ], + const SizedBox(height: 16), + const Text('本机设备ID:', style: TextStyle(fontSize: 14)), + const SizedBox(height: 8), + CupertinoTextField( + controller: _deviceIdController, + placeholder: '设备ID', + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 12), + ), + const SizedBox(height: 16), + const Text('目标被控设备ID:', style: TextStyle(fontSize: 14)), + const SizedBox(height: 8), + CupertinoTextField( + controller: _targetController, + placeholder: '被控端设备ID', + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 12), + ), + const SizedBox(height: 24), + Text( + _status, + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 24), + SizedBox( + width: double.infinity, + child: CupertinoButton.filled( + onPressed: _connect, + child: const Text('连接被控设备'), + ), + ), + ], + ), ), ); } Widget _buildControlPanel() { - return Stack( + return SafeArea( + // 仅保留顶部安全区(避开系统状态栏),底部/左右保持全屏, + // 以便右下角的断开按钮贴近屏幕边缘。 + top: true, + bottom: false, + left: false, + right: false, + child: Stack( children: [ Container(color: Colors.black), if (_renderer != null) @@ -263,10 +270,12 @@ class _ControllerHomeState extends State { onTouch: (x, y) {}, onSwipe: (x1, y1, x2, y2, d) {}, onLongPress: (x, y) {}, - onKey: (k, a) => _controller - ?.sendControlCommand(ControlCommands.key(k, a)), - onMotionEvent: (a, x, y) => _controller - ?.sendControlCommand(ControlCommands.motionEvent(a, x, y)), + onKey: (k, a) => _controller?.sendControlCommand( + ControlCommands.key(k, a), + ), + onMotionEvent: (a, x, y) => _controller?.sendControlCommand( + ControlCommands.motionEvent(a, x, y), + ), ), ], ), @@ -281,14 +290,10 @@ class _ControllerHomeState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - _status, - style: const TextStyle(color: Colors.white), - ), + Text(_status, style: const TextStyle(color: Colors.white)), Text( _stats, - style: - const TextStyle(color: Colors.white, fontSize: 12), + style: const TextStyle(color: Colors.white, fontSize: 12), ), ], ), @@ -297,13 +302,26 @@ class _ControllerHomeState extends State { Positioned( bottom: 16, right: 16, - child: FloatingActionButton( + child: CupertinoButton( + padding: EdgeInsets.zero, onPressed: _disconnect, - tooltip: '断开连接', - child: const Icon(Icons.close), + child: Container( + width: 48, + height: 48, + decoration: BoxDecoration( + color: CupertinoColors.destructiveRed, + borderRadius: BorderRadius.circular(24), + ), + alignment: Alignment.center, + child: const Icon( + CupertinoIcons.clear, + color: CupertinoColors.white, + ), + ), ), ), ], + ), ); } }