Commit cf1809a5 authored by 张宏's avatar 张宏

usb摄像头对接完成

parent 026b37a8
...@@ -20,6 +20,9 @@ class TcpClientCubit extends Cubit<TcpClientState> { ...@@ -20,6 +20,9 @@ class TcpClientCubit extends Cubit<TcpClientState> {
bool _isManualDisconnect = false; bool _isManualDisconnect = false;
bool _isConnecting = false; bool _isConnecting = false;
/// Socket 数据监听的订阅,需要在 close 时显式取消
StreamSubscription<dynamic>? _socketSub;
final StreamController<Uint8List> _dataController = final StreamController<Uint8List> _dataController =
StreamController.broadcast(); StreamController.broadcast();
...@@ -65,6 +68,12 @@ class TcpClientCubit extends Cubit<TcpClientState> { ...@@ -65,6 +68,12 @@ class TcpClientCubit extends Cubit<TcpClientState> {
timeout: Duration(milliseconds: _timeout), timeout: Duration(milliseconds: _timeout),
); );
if (isClosed) {
_socket?.close();
_socket = null;
return false;
}
_socket!.setOption(SocketOption.tcpNoDelay, true); _socket!.setOption(SocketOption.tcpNoDelay, true);
_listenSocket(); _listenSocket();
...@@ -73,6 +82,7 @@ class TcpClientCubit extends Cubit<TcpClientState> { ...@@ -73,6 +82,7 @@ class TcpClientCubit extends Cubit<TcpClientState> {
_startHeartbeat(); _startHeartbeat();
return true; return true;
} catch (e) { } catch (e) {
if (isClosed) return false;
emit(state.copyWith( emit(state.copyWith(
connectionStatus: TcpConnectionStatus.error, connectionStatus: TcpConnectionStatus.error,
error: e.toString(), error: e.toString(),
...@@ -86,7 +96,8 @@ class TcpClientCubit extends Cubit<TcpClientState> { ...@@ -86,7 +96,8 @@ class TcpClientCubit extends Cubit<TcpClientState> {
/// 监听 Socket 数据 /// 监听 Socket 数据
void _listenSocket() { void _listenSocket() {
_socket!.listen( _socketSub?.cancel();
_socketSub = _socket!.listen(
(data) { (data) {
final uint8List = Uint8List.fromList(data); final uint8List = Uint8List.fromList(data);
if (!_dataController.isClosed) { if (!_dataController.isClosed) {
...@@ -94,6 +105,7 @@ class TcpClientCubit extends Cubit<TcpClientState> { ...@@ -94,6 +105,7 @@ class TcpClientCubit extends Cubit<TcpClientState> {
} }
}, },
onError: (e) { onError: (e) {
if (isClosed) return;
emit(state.copyWith( emit(state.copyWith(
connectionStatus: TcpConnectionStatus.error, connectionStatus: TcpConnectionStatus.error,
error: e.toString(), error: e.toString(),
...@@ -101,7 +113,7 @@ class TcpClientCubit extends Cubit<TcpClientState> { ...@@ -101,7 +113,7 @@ class TcpClientCubit extends Cubit<TcpClientState> {
_tryReconnect(); _tryReconnect();
}, },
onDone: () { onDone: () {
if (!_isManualDisconnect) { if (!_isManualDisconnect && !isClosed) {
_tryReconnect(); _tryReconnect();
} }
}, },
...@@ -110,6 +122,7 @@ class TcpClientCubit extends Cubit<TcpClientState> { ...@@ -110,6 +122,7 @@ class TcpClientCubit extends Cubit<TcpClientState> {
/// 自动重连 /// 自动重连
void _tryReconnect() { void _tryReconnect() {
if (isClosed) return;
if (_isManualDisconnect) return; if (_isManualDisconnect) return;
if (_currentRetry >= _maxRetryCount) { if (_currentRetry >= _maxRetryCount) {
emit(state.copyWith(connectionStatus: TcpConnectionStatus.disconnected)); emit(state.copyWith(connectionStatus: TcpConnectionStatus.disconnected));
...@@ -121,7 +134,7 @@ class TcpClientCubit extends Cubit<TcpClientState> { ...@@ -121,7 +134,7 @@ class TcpClientCubit extends Cubit<TcpClientState> {
_reconnectTimer?.cancel(); _reconnectTimer?.cancel();
_reconnectTimer = Timer(Duration(milliseconds: _retryInterval), () { _reconnectTimer = Timer(Duration(milliseconds: _retryInterval), () {
connect(); if (!isClosed) connect();
}); });
} }
...@@ -173,14 +186,24 @@ class TcpClientCubit extends Cubit<TcpClientState> { ...@@ -173,14 +186,24 @@ class TcpClientCubit extends Cubit<TcpClientState> {
_isManualDisconnect = true; _isManualDisconnect = true;
_reconnectTimer?.cancel(); _reconnectTimer?.cancel();
_heartbeatTimer?.cancel(); _heartbeatTimer?.cancel();
_socketSub?.cancel();
_socketSub = null;
_socket?.close(); _socket?.close();
_socket = null; _socket = null;
if (!isClosed) {
emit(state.copyWith(connectionStatus: TcpConnectionStatus.disconnected)); emit(state.copyWith(connectionStatus: TcpConnectionStatus.disconnected));
} }
}
@override @override
Future<void> close() { Future<void> close() {
disconnect(); _isManualDisconnect = true;
_reconnectTimer?.cancel();
_heartbeatTimer?.cancel();
_socketSub?.cancel();
_socketSub = null;
_socket?.close();
_socket = null;
_dataController.close(); _dataController.close();
return super.close(); return super.close();
} }
......
...@@ -8,6 +8,12 @@ class TcpServerCubit extends Cubit<TcpServerState> { ...@@ -8,6 +8,12 @@ class TcpServerCubit extends Cubit<TcpServerState> {
ServerSocket? _serverSocket; ServerSocket? _serverSocket;
final List<Socket> _clients = []; final List<Socket> _clients = [];
/// 服务端 Socket 监听的订阅
StreamSubscription<Socket>? _serverSub;
/// 每个客户端 Socket 的订阅
final List<StreamSubscription<dynamic>> _clientSubs = [];
final StreamController<Map<String, dynamic>> _dataController = final StreamController<Map<String, dynamic>> _dataController =
StreamController.broadcast(); StreamController.broadcast();
...@@ -26,9 +32,15 @@ class TcpServerCubit extends Cubit<TcpServerState> { ...@@ -26,9 +32,15 @@ class TcpServerCubit extends Cubit<TcpServerState> {
try { try {
_serverSocket = _serverSocket =
await ServerSocket.bind(InternetAddress.anyIPv4, state.port); await ServerSocket.bind(InternetAddress.anyIPv4, state.port);
if (isClosed) {
_serverSocket?.close();
_serverSocket = null;
return;
}
emit(state.copyWith(isRunning: true, port: _serverSocket!.port)); emit(state.copyWith(isRunning: true, port: _serverSocket!.port));
_listenClients(); _listenClients();
} catch (e) { } catch (e) {
if (isClosed) return;
if (e.toString().contains('already in use')) { if (e.toString().contains('already in use')) {
emit(state.copyWith(port: state.port + 1)); emit(state.copyWith(port: state.port + 1));
start(); start();
...@@ -40,7 +52,12 @@ class TcpServerCubit extends Cubit<TcpServerState> { ...@@ -40,7 +52,12 @@ class TcpServerCubit extends Cubit<TcpServerState> {
/// 监听客户端连接 /// 监听客户端连接
void _listenClients() { void _listenClients() {
_serverSocket!.listen((client) { _serverSub?.cancel();
_serverSub = _serverSocket!.listen((client) {
if (isClosed) {
client.close();
return;
}
_clients.add(client); _clients.add(client);
emit(state.copyWith(connectedClients: _clients.length)); emit(state.copyWith(connectedClients: _clients.length));
_handleClient(client); _handleClient(client);
...@@ -49,8 +66,9 @@ class TcpServerCubit extends Cubit<TcpServerState> { ...@@ -49,8 +66,9 @@ class TcpServerCubit extends Cubit<TcpServerState> {
/// 处理单个客户端数据 /// 处理单个客户端数据
void _handleClient(Socket client) { void _handleClient(Socket client) {
client.listen( final sub = client.listen(
(data) { (data) {
if (_dataController.isClosed) return;
_dataController.add({ _dataController.add({
'client': client, 'client': client,
'address': client.remoteAddress.address, 'address': client.remoteAddress.address,
...@@ -61,6 +79,7 @@ class TcpServerCubit extends Cubit<TcpServerState> { ...@@ -61,6 +79,7 @@ class TcpServerCubit extends Cubit<TcpServerState> {
onDone: () => _removeClient(client), onDone: () => _removeClient(client),
onError: (e) => _removeClient(client), onError: (e) => _removeClient(client),
); );
_clientSubs.add(sub);
} }
/// 群发字节数据给所有客户端 /// 群发字节数据给所有客户端
...@@ -85,24 +104,35 @@ class TcpServerCubit extends Cubit<TcpServerState> { ...@@ -85,24 +104,35 @@ class TcpServerCubit extends Cubit<TcpServerState> {
/// 移除客户端 /// 移除客户端
void _removeClient(Socket client) { void _removeClient(Socket client) {
_clients.remove(client); _clients.remove(client);
emit(state.copyWith(connectedClients: _clients.length));
client.destroy(); client.destroy();
if (!isClosed) {
emit(state.copyWith(connectedClients: _clients.length));
}
} }
/// 停止服务端 /// 停止服务端
Future<void> stop() async { Future<void> stop() async {
for (var sub in _clientSubs) {
await sub.cancel();
}
_clientSubs.clear();
for (var c in _clients) { for (var c in _clients) {
await c.close(); await c.close();
} }
_clients.clear(); _clients.clear();
await _serverSocket?.close(); await _serverSocket?.close();
_serverSocket = null;
if (!isClosed) {
emit(state.copyWith(isRunning: false, connectedClients: 0)); emit(state.copyWith(isRunning: false, connectedClients: 0));
} }
}
@override @override
Future<void> close() async { Future<void> close() async {
await stop(); await stop();
_dataController.close(); _serverSub?.cancel();
_serverSub = null;
await _dataController.close();
return super.close(); return super.close();
} }
} }
...@@ -16,9 +16,13 @@ class HomeIndexCubit extends Cubit<HomeIndexState> { ...@@ -16,9 +16,13 @@ class HomeIndexCubit extends Cubit<HomeIndexState> {
emit(state.copyWith(isLoading: true, error: null)); emit(state.copyWith(isLoading: true, error: null));
try { try {
final metrics = await _monitoringService.getMetrics(); final metrics = await _monitoringService.getMetrics();
if (isClosed) return;
final patientInfo = await _monitoringService.getPatientInfo(); final patientInfo = await _monitoringService.getPatientInfo();
if (isClosed) return;
final alerts = await _monitoringService.getAlerts(); final alerts = await _monitoringService.getAlerts();
if (isClosed) return;
final menuItems = await _monitoringService.getMenuItems(); final menuItems = await _monitoringService.getMenuItems();
if (isClosed) return;
emit(state.copyWith( emit(state.copyWith(
isLoading: false, isLoading: false,
...@@ -30,6 +34,7 @@ class HomeIndexCubit extends Cubit<HomeIndexState> { ...@@ -30,6 +34,7 @@ class HomeIndexCubit extends Cubit<HomeIndexState> {
error: null, error: null,
)); ));
} catch (e) { } catch (e) {
if (isClosed) return;
emit(state.copyWith( emit(state.copyWith(
isLoading: false, isLoading: false,
status: HomeIndexStatus.failure, status: HomeIndexStatus.failure,
...@@ -41,9 +46,13 @@ class HomeIndexCubit extends Cubit<HomeIndexState> { ...@@ -41,9 +46,13 @@ class HomeIndexCubit extends Cubit<HomeIndexState> {
Future<void> refreshData() async { Future<void> refreshData() async {
try { try {
final metrics = await _monitoringService.getMetrics(); final metrics = await _monitoringService.getMetrics();
if (isClosed) return;
final patientInfo = await _monitoringService.getPatientInfo(); final patientInfo = await _monitoringService.getPatientInfo();
if (isClosed) return;
final alerts = await _monitoringService.getAlerts(); final alerts = await _monitoringService.getAlerts();
if (isClosed) return;
final menuItems = await _monitoringService.getMenuItems(); final menuItems = await _monitoringService.getMenuItems();
if (isClosed) return;
emit(state.copyWith( emit(state.copyWith(
status: HomeIndexStatus.success, status: HomeIndexStatus.success,
...@@ -54,6 +63,7 @@ class HomeIndexCubit extends Cubit<HomeIndexState> { ...@@ -54,6 +63,7 @@ class HomeIndexCubit extends Cubit<HomeIndexState> {
error: null, error: null,
)); ));
} catch (e) { } catch (e) {
if (isClosed) return;
emit(state.copyWith( emit(state.copyWith(
status: HomeIndexStatus.failure, status: HomeIndexStatus.failure,
error: e.toString(), error: e.toString(),
......
...@@ -17,6 +17,9 @@ class CameraControls extends StatelessWidget { ...@@ -17,6 +17,9 @@ class CameraControls extends StatelessWidget {
/// 摄像头是否已就绪(已打开) /// 摄像头是否已就绪(已打开)
final bool cameraReady; final bool cameraReady;
/// 是否已全屏
final bool isFullscreen;
/// 拍照回调 /// 拍照回调
final VoidCallback? onTakePhoto; final VoidCallback? onTakePhoto;
...@@ -26,13 +29,18 @@ class CameraControls extends StatelessWidget { ...@@ -26,13 +29,18 @@ class CameraControls extends StatelessWidget {
/// 刷新回调 /// 刷新回调
final VoidCallback? onRefresh; final VoidCallback? onRefresh;
/// 切换全屏回调
final VoidCallback? onToggleFullscreen;
const CameraControls({ const CameraControls({
super.key, super.key,
this.isRecording = false, this.isRecording = false,
this.cameraReady = false, this.cameraReady = false,
this.isFullscreen = false,
this.onTakePhoto, this.onTakePhoto,
this.onToggleRecord, this.onToggleRecord,
this.onRefresh, this.onRefresh,
this.onToggleFullscreen,
}); });
@override @override
...@@ -47,7 +55,12 @@ class CameraControls extends StatelessWidget { ...@@ -47,7 +55,12 @@ class CameraControls extends StatelessWidget {
enabled: true, // 刷新按钮始终可用 enabled: true, // 刷新按钮始终可用
), ),
SizedBox(width: 16.w), SizedBox(width: 16.w),
// todo 放大,全屏 _buildControlButton(
icon: isFullscreen ? Icons.fullscreen_exit : Icons.fullscreen,
onPressed: onToggleFullscreen,
tooltip: isFullscreen ? '退出全屏' : '全屏放大',
enabled: cameraReady,
),
// _buildControlButton( // _buildControlButton(
// icon: Icons.camera_alt, // icon: Icons.camera_alt,
......
...@@ -12,7 +12,7 @@ import 'camera_controls.dart'; ...@@ -12,7 +12,7 @@ import 'camera_controls.dart';
/// 职责: /// 职责:
/// - 通过 [UsbCameraPreview] 嵌入 USB 摄像头实时预览画面 /// - 通过 [UsbCameraPreview] 嵌入 USB 摄像头实时预览画面
/// - 监听 [UsbCameraService.eventStream] 处理设备热插拔和状态变化 /// - 监听 [UsbCameraService.eventStream] 处理设备热插拔和状态变化
/// - 底部控制栏:拍照、录像、刷新按钮 /// - 底部控制栏:刷新、全屏按钮
/// ///
/// 涉及页面:首页监护舱页面 /// 涉及页面:首页监护舱页面
class MonitoringScreen extends StatefulWidget { class MonitoringScreen extends StatefulWidget {
...@@ -38,6 +38,15 @@ class _MonitoringScreenState extends State<MonitoringScreen> { ...@@ -38,6 +38,15 @@ class _MonitoringScreenState extends State<MonitoringScreen> {
bool _cameraReady = false; bool _cameraReady = false;
bool _isRecording = false; bool _isRecording = false;
bool _isFullscreen = false;
/// 用于强制重建 PlatformView 的计数器
/// flutter_usbcamera 底层使用单例 [mCameraView] 绑定唯一 TextureView,
/// 退出全屏时 generation+1 使原始 UsbCameraPreview 以新 Key 重建,
/// 触发原生 onCameraViewCreated → mCameraView 重新指向此处的 TextureView
int _cameraViewGeneration = 0;
OverlayEntry? _fullscreenEntry;
@override @override
void initState() { void initState() {
...@@ -128,21 +137,28 @@ class _MonitoringScreenState extends State<MonitoringScreen> { ...@@ -128,21 +137,28 @@ class _MonitoringScreenState extends State<MonitoringScreen> {
@override @override
void dispose() { void dispose() {
_eventSub?.cancel(); _eventSub?.cancel();
_fullscreenEntry?.remove();
_fullscreenEntry = null;
_cameraService.dispose(); _cameraService.dispose();
super.dispose(); super.dispose();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( // AspectRatio 放在最外层,让整个 Container 宽度贴合 16:9 视频比例,
// 避免父级 Expanded 撑宽后出现右侧空白
return ClipRRect(
borderRadius: BorderRadius.circular(20.r),
child: AspectRatio(
aspectRatio: 16 / 9,
child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: const LinearGradient( gradient: const LinearGradient(
begin: Alignment.topCenter, begin: Alignment.topCenter,
end: Alignment.bottomCenter, end: Alignment.bottomCenter,
colors: [Color(0xCC63A0FF), Color(0xCC0024C4)], colors: [Color(0xCC63A0FF), Color(0xCC0024C4)],
), ),
borderRadius: BorderRadius.circular(20.r), border: Border.all(color: Colors.white24, width: 4),
border: Border.all(color: Colors.white24, width: 1),
), ),
child: Stack( child: Stack(
children: [ children: [
...@@ -157,24 +173,28 @@ class _MonitoringScreenState extends State<MonitoringScreen> { ...@@ -157,24 +173,28 @@ class _MonitoringScreenState extends State<MonitoringScreen> {
child: CameraControls( child: CameraControls(
isRecording: _isRecording, isRecording: _isRecording,
cameraReady: _cameraReady, cameraReady: _cameraReady,
isFullscreen: _isFullscreen,
onTakePhoto: _onTakePhoto, onTakePhoto: _onTakePhoto,
onToggleRecord: _onToggleRecord, onToggleRecord: _onToggleRecord,
onRefresh: _onRefresh, onRefresh: _onRefresh,
onToggleFullscreen: _onToggleFullscreen,
), ),
), ),
], ],
), ),
),
),
); );
} }
/// 构建 USB 摄像头预览画面(通过 flutter_usbcamera 插件) /// 构建 USB 摄像头预览画面(通过 flutter_usbcamera 插件)
///
/// 使用 [_cameraViewGeneration] 作为 Key 的一部分:
/// 退出全屏时 generation+1 使 Widget 以新 Key 重建,
/// 对应的 PlatformView 也随之重建并触发 onCameraViewCreated
Widget _buildCameraPreview() { Widget _buildCameraPreview() {
return ClipRRect( return UsbCameraPreview(
borderRadius: BorderRadius.circular(20.r), key: ValueKey('camera_$_cameraViewGeneration'),
child: const AspectRatio(
aspectRatio: 16 / 9,
child: UsbCameraPreview(),
),
); );
} }
...@@ -256,6 +276,86 @@ class _MonitoringScreenState extends State<MonitoringScreen> { ...@@ -256,6 +276,86 @@ class _MonitoringScreenState extends State<MonitoringScreen> {
widget.onRefresh?.call(); widget.onRefresh?.call();
} }
// ==================== 全屏逻辑 ====================
/// 切换全屏模式
///
/// flutter_usbcamera 底层将 [mCameraView] 作为单例绑定唯一 TextureView,
/// 不能同时在两个 Widget 中渲染 [UsbCameraPreview](第二个会黑屏)。
///
/// 解决思路:关闭摄像头 → 切换 View 容器 → 重新打开摄像头。
/// - 进入全屏:关摄像头 → 插 Overlay(含 UsbCameraPreview)→ 开摄像头
/// - 退出全屏:关摄像头 → 移 Overlay → 增 generation 强制重建原始
/// PlatformView → 开摄像头
Future<void> _onToggleFullscreen() async {
if (!_cameraReady) {
_showSnackBar('摄像头未就绪');
return;
}
if (_isFullscreen) {
await _exitFullscreen();
} else {
await _enterFullscreen();
}
}
/// 进入全屏
Future<void> _enterFullscreen() async {
final deviceId = _cameraService.controller.currentDeviceId;
if (deviceId == null) return;
// 在 await 之前捕获 overlay,避免 use_build_context_synchronously
final overlay = Overlay.of(context);
// 1. 关闭摄像头,释放当前 PlatformView 的绑定
await _cameraService.closeCamera();
// 2. 插入全屏 Overlay —— 其 UsbCameraPreview 会创建新 PlatformView
// 并触发原生 onCameraViewCreated → mCameraView 指向新 TextureView
setState(() => _isFullscreen = true);
_fullscreenEntry = OverlayEntry(
builder: (_) => _FullscreenOverlay(onExit: _exitFullscreen),
);
overlay.insert(_fullscreenEntry!);
// 3. 等待 PlatformView 初始化完成
await Future.delayed(const Duration(milliseconds: 150));
// 4. 重新打开摄像头,渲染到 Overlay 中的 TextureView
await _cameraService.openCamera(deviceId);
}
/// 退出全屏
Future<void> _exitFullscreen() async {
final deviceId = _cameraService.controller.currentDeviceId;
// 1. 关闭摄像头
if (deviceId != null) {
await _cameraService.closeCamera();
}
// 2. 移除全屏 Overlay(其 PlatformView 被销毁)
_fullscreenEntry?.remove();
_fullscreenEntry = null;
// 3. 增加 generation → rebuild 时 UsbCameraPreview 的 Key 变化
// → 旧 PlatformView 被销毁,新 PlatformView 被创建
// → onCameraViewCreated → mCameraView 指向此处的 TextureView
setState(() {
_isFullscreen = false;
_cameraViewGeneration++;
});
// 4. 等待新 PlatformView 初始化完成
await Future.delayed(const Duration(milliseconds: 150));
// 5. 重新打开摄像头,渲染到原始位置的 TextureView
if (deviceId != null) {
await _cameraService.openCamera(deviceId);
}
}
void _showSnackBar(String message) { void _showSnackBar(String message) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
...@@ -270,3 +370,52 @@ class _MonitoringScreenState extends State<MonitoringScreen> { ...@@ -270,3 +370,52 @@ class _MonitoringScreenState extends State<MonitoringScreen> {
); );
} }
} }
/// 全屏摄像头预览浮层
///
/// 通过 [OverlayEntry] 插入到当前页面,而非通过 Navigator.push 创建新路由,
/// 避免 flutter_usbcamera 的 PlatformView 冲突。
class _FullscreenOverlay extends StatelessWidget {
final VoidCallback onExit;
const _FullscreenOverlay({required this.onExit});
@override
Widget build(BuildContext context) {
return Material(
color: Colors.black,
child: Stack(
children: [
// 全屏摄像头画面(16:9 居中)
const Center(
child: AspectRatio(
aspectRatio: 16 / 9,
child: UsbCameraPreview(),
),
),
// 顶部退出按钮
Positioned(
top: MediaQuery.of(context).padding.top + 16.h,
left: 16.w,
child: GestureDetector(
onTap: onExit,
child: Container(
width: 48.w,
height: 48.w,
decoration: BoxDecoration(
color: Colors.black38,
borderRadius: BorderRadius.circular(24.r),
),
child: Icon(
Icons.fullscreen_exit,
color: Colors.white,
size: 28.w,
),
),
),
),
],
),
);
}
}
import 'dart:async';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:laki_icu_app/blocs/auth/auth_bloc.dart'; import 'package:laki_icu_app/blocs/auth/auth_bloc.dart';
import 'package:laki_icu_app/blocs/auth/auth_event.dart'; import 'package:laki_icu_app/blocs/auth/auth_event.dart';
...@@ -9,6 +11,8 @@ class LoginCubit extends Cubit<LoginState> { ...@@ -9,6 +11,8 @@ class LoginCubit extends Cubit<LoginState> {
final AuthBloc authBloc; final AuthBloc authBloc;
final StorageService storageService; final StorageService storageService;
StreamSubscription<AuthState>? _authSub;
LoginCubit({ LoginCubit({
required this.authBloc, required this.authBloc,
required this.storageService, required this.storageService,
...@@ -17,7 +21,8 @@ class LoginCubit extends Cubit<LoginState> { ...@@ -17,7 +21,8 @@ class LoginCubit extends Cubit<LoginState> {
} }
void _listenAuthState() { void _listenAuthState() {
authBloc.stream.listen((authState) { _authSub = authBloc.stream.listen((authState) {
if (isClosed) return;
if (authState is AuthFailure) { if (authState is AuthFailure) {
emit(state.copyWith(isLoading: false, error: authState.error)); emit(state.copyWith(isLoading: false, error: authState.error));
} else if (authState is AuthLoading) { } else if (authState is AuthLoading) {
...@@ -54,6 +59,7 @@ class LoginCubit extends Cubit<LoginState> { ...@@ -54,6 +59,7 @@ class LoginCubit extends Cubit<LoginState> {
Future<void> loadRememberCredentials() async { Future<void> loadRememberCredentials() async {
final credentials = await storageService.getRememberCredentials(); final credentials = await storageService.getRememberCredentials();
if (isClosed) return;
if (credentials['username'] != null) { if (credentials['username'] != null) {
emit(state.copyWith( emit(state.copyWith(
rememberPassword: true, rememberPassword: true,
...@@ -66,4 +72,10 @@ class LoginCubit extends Cubit<LoginState> { ...@@ -66,4 +72,10 @@ class LoginCubit extends Cubit<LoginState> {
void forgotPwd() { void forgotPwd() {
// TODO: 跳转到忘记密码页面 // TODO: 跳转到忘记密码页面
} }
@override
Future<void> close() {
_authSub?.cancel();
return super.close();
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment