首页的权限交接的弹窗(关闭了释放权限)+添加日志过滤功能,支持通过 shouldLog参数控制日志输出

This commit is contained in:
2026-04-29 14:25:46 +08:00
parent c3c9db5447
commit 19add5ad51
8 changed files with 124 additions and 32 deletions

View File

@@ -57,6 +57,7 @@ import '../../features/remote_control/domain/usecase/remote_control_usecase.dart
import '../../features/remote_control/presentation/bloc/remote_control_cubit.dart';
import '../app/app_user_cubit.dart';
import '../localization/locale_cubit.dart';
import '../../features/home/presentation/bloc/permission_request_bloc.dart';
import '../network/dio_client.dart';
import '../network/net_message_dispatcher.dart';
import '../network/tcp/tcp_client.dart';
@@ -200,6 +201,12 @@ Future<void> init() async {
// 🔥 RemoteControlCubit 注入 DeviceStatusBloc(工厂模式,每次新建)
sl.registerFactory(() => RemoteControlCubit(sl(), sl(), sl(), sl()));
// 🔥 PermissionRequestBloc 用于首页权限弹窗(单例,通过 NetMessageDispatcher 监听)
sl.registerLazySingleton(() => PermissionRequestBloc(
sl<NetMessageDispatcher>(),
sl<RemoteControlRepository>(),
));
/// . 路径生成
sl.registerLazySingleton<PathHttpDatasource>(
() => PathHttpDatasourceImpl(

View File

@@ -107,7 +107,13 @@ class SentryLoggerImpl implements ILoggerService {
String message, {
String level = 'INFO', // 'DEBUG', 'INFO', 'WARNING', 'ERROR'
Map<String, dynamic>? data,
bool shouldLog = false, // 默认 false 不打印,传 true 才打印
}) {
// 🔥 关键过滤:shouldLog 为 false 时直接返回,不打印
if (!shouldLog) {
return;
}
final timestamp = DateTime.now().toIso8601String();
final logEntry = "[$timestamp] [$level] $message ${data ?? ''}\n";

View File

@@ -10,6 +10,7 @@ abstract class ILoggerService {
String message, {
String level = 'INFO', // 'DEBUG', 'INFO', 'WARNING', 'ERROR'
Map<String, dynamic>? data,
bool shouldLog = false, // 默认 false 不打印,传 true 才打印
});
void addBreadcrumb(String message, {String? category});
void setUser(String userId);

View File

@@ -48,7 +48,7 @@ class TcpClient {
if (forSwitch) {
_isSwitching = true;
//debugPrint('🚫 [TCP] 标记为切换断开,将禁止自动重连');
_logger.logWithLevel('🚫 [TCP] 标记为切换断开,将禁止自动重连');
_logger.logWithLevel('🚫 [TCP] 标记为切换断开,将禁止自动重连', shouldLog: true);
}
// 🔥 关键:立即取消可能已经存在或即将触发的重连定时器
@@ -57,7 +57,7 @@ class TcpClient {
if (_socket != null) {
//debugPrint('🔌 [TCP] 物理断开 Socket...');
_logger.logWithLevel('🔌 [TCP] 物理断开 Socket...');
_logger.logWithLevel('🔌 [TCP] 物理断开 Socket...', shouldLog: true);
_socket!.destroy(); // 或者 .close()
_socket = null;
}
@@ -65,7 +65,7 @@ class TcpClient {
// 通过参数配置,不硬编码
Future<void> connect({required String host, required int port}) async {
//debugPrint('🔌 [TCP] 开始连接:$host:$port'); // ✅ 必须看到这条
_logger.logWithLevel('🔌 [TCP] 开始连接:$host:$port');
_logger.logWithLevel('🔌 [TCP] 开始连接:$host:$port', shouldLog: true);
_lastHost = host;
_lastPort = port;
try {
@@ -76,8 +76,8 @@ class TcpClient {
);
// 🔥 关键修复:禁用Nagle算法,确保小包立即发送
_socket!.setOption(SocketOption.tcpNoDelay, true);
debugPrint('✅ [TCP] 连接成功!'); // ✅ 必须看到这条
_logger.logWithLevel('✅ [TCP] 连接成功!');
// debugPrint('✅ [TCP] 连接成功!'); // ✅ 必须看到这条
_logger.logWithLevel('✅ [TCP] 连接成功!', shouldLog: true);
// 开始认证tcp
await _sendAuthPacket();
_socket!.listen((data) {
@@ -97,28 +97,32 @@ class TcpClient {
try {
var packets = _decoder.decode(data);
//debugPrint('📦 [TCP] 解码成功,包数量:${packets.length}');
_logger.logWithLevel('✅ [TCP] 解码成功,包数量:${packets.length}');
_logger.logWithLevel('✅ [TCP] 解码成功,包数量:${packets.length}',shouldLog: true);
for (var packet in packets) {
// 🔥 最根部日志:收到任何推送都打印
// debugPrint('📥 [TCP-ROOT] 收到推送 CMD: 0x${packet.command.toRadixString(16).toUpperCase()}, Payload长度: ${packet.payload.length}');
_logger.logWithLevel('📥 [TCP-ROOT] 收到推送 CMD: 0x${packet.command.toRadixString(16).toUpperCase()}, Payload长度: ${packet.payload.length}', shouldLog: true);
if (!_controller.isClosed) {
_controller.add(packet);
//debugPrint('➡️ [TCP] 已分发 CMD: 0x${packet.command.toRadixString(16)}');
_logger.logWithLevel('✅ [TCP] 已分发 CMD: 0x${packet.command.toRadixString(16)}');
_logger.logWithLevel('✅ [TCP] 已分发 CMD: 0x${packet.command.toRadixString(16)}' ,shouldLog: true);
}
if (packet.command == 0xFF) {
//debugPrint('收到服务端心跳,自动回复...');
_logger.logWithLevel('✅ [TCP] 收到服务端心跳,自动回复...');
_logger.logWithLevel('✅ [TCP] 收到服务端心跳,自动回复...',shouldLog: true);
sendHeartbeat(); // 回复 AB AA FF AA AB
}
if (packet.command == 0x03) {
// debugPrint('⚠️ 收到认证响应:${packet.payload}');
_logger.logWithLevel('⚠️ 收到认证响应:${packet.payload}');
_logger.logWithLevel('⚠️ 收到认证响应:${packet.payload}',shouldLog: true);
// 解析 payload 看是否有错误信息
}
}
} catch (e, stackTrace) {
debugPrint('❌ [TCP] 解码数据时发生异常:$e\n$stackTrace'); // 🔥 捕获解码异常
_logger.logWithLevel('❌ [TCP] 解码数据时发生异常:$e\n$stackTrace');
// debugPrint('❌ [TCP] 解码数据时发生异常:$e\n$stackTrace'); // 🔥 捕获解码异常
_logger.logWithLevel('❌ [TCP] 解码数据时发生异常:$e\n$stackTrace', shouldLog: true);
}
},
onDone: (){
@@ -126,7 +130,7 @@ class TcpClient {
// debugPrint('来到断线重连!');
if (!_isSwitching) {
//debugPrint('onDone❌ [TCP] 连接已断开!');
_logger.logWithLevel('❌ [TCP] 连接已断开!');
_logger.logWithLevel('❌ [TCP] 连接已断开!', shouldLog: true);
_handleDisconnect();
}
_isSwitching = false; // 重置标志,以免影响下次
@@ -134,10 +138,10 @@ class TcpClient {
},
onError: (e) {
//debugPrint('来到断线重连!error');
_logger.logWithLevel('❌ [TCP] 发生错误:$e');
_logger.logWithLevel('❌ [TCP] 发生错误:$e', shouldLog: true);
if (!_isSwitching) {
//debugPrint('❌ [TCP] 发生错误:$e');
_logger.logWithLevel('❌ [TCP] 发生错误:$e');
_logger.logWithLevel('❌ [TCP] 发生错误:$e', shouldLog: true);
_handleDisconnect();
} // 统一走重连逻辑,保护 Controller 不被关闭
_isSwitching = false; // 重置标志,以免影响下次
@@ -167,14 +171,14 @@ class TcpClient {
_logger.logWithLevel('⏳ 调度重连:Host=${_lastHost}, Port=${_lastPort}');
if (_lastHost == null || _lastPort == null) {
//debugPrint('❌ 无法重连:Host 或 Port 为空!');
_logger.logWithLevel('❌ 无法重连:Host 或 Port 为空!');
_logger.logWithLevel('❌ 无法重连:Host 或 Port 为空!', shouldLog: true);
return;
}
_reconnectTimer = Timer(const Duration(seconds: 5), () {
_reconnectTimer = null;
//debugPrint('⏰ 定时器触发,开始执行重连...');
_logger.logWithLevel('⏰ 定时器触发,开始执行重连...');
_logger.logWithLevel('⏰ 定时器触发,开始执行重连...', shouldLog: true);
connect(host: _lastHost!, port: _lastPort!);
});
await _sendAuthPacket();
@@ -185,17 +189,17 @@ class TcpClient {
Future<void> _scheduleReconnectBySwitch(devname) async {
debugPrint('⏳ 被动调度重连:Host=${_lastHost}, Port=${_lastPort}'); // ✅ 检查 Host/Port 是否为空
//debugPrint('⏳ 被动调度重连:Host=${_lastHost}, Port=${_lastPort}'); // ✅ 检查 Host/Port 是否为空
if (_lastHost == null || _lastPort == null) {
// debugPrint('❌ 无法重连:Host 或 Port 为空!');
_logger.logWithLevel('❌ 无法重连:Host 或 Port 为空!');
_logger.logWithLevel('❌ 无法重连:Host 或 Port 为空!', shouldLog: true);
return;
}
_reconnectTimer = Timer(const Duration(seconds: 5), () {
_reconnectTimer = null;
//debugPrint('⏰ 被动-定时器触发,开始执行重连...');
_logger.logWithLevel('⏰ 被动-定时器触发,开始执行重连...');
_logger.logWithLevel('⏰ 被动-定时器触发,开始执行重连...', shouldLog: true);
connectBySwitch(host: _lastHost!, port: _lastPort!, deviceName: devname);
});
await _sendAuthPacketBySwitch(devname);
@@ -246,7 +250,7 @@ class TcpClient {
void startHeartbeat({Duration interval = const Duration(seconds: 4)}) {
if (_heartbeatTimer != null) return; // 防止重复启动
//debugPrint('收到服务端心跳,自动回复...');
_logger.logWithLevel('✅ [TCP] 收到服务端心跳,自动回复...');
_logger.logWithLevel('✅ [TCP] 收到服务端心跳,自动回复...', shouldLog: true);
_heartbeatTimer = Timer.periodic(interval, (_) {
// 发送心跳帧:AB AA FF 00 00 AA AB(与 sendRaw 一致)
//sendRaw(0xFF, []);

View File

@@ -8,8 +8,12 @@ import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
import 'package:maibu_satabot_v2/core/network/tcp/tcp_client.dart';
import 'package:maibu_satabot_v2/features/devices/presentation/bloc/devices_cubit.dart';
import 'package:maibu_satabot_v2/features/devices/presentation/bloc/devices_state.dart'; // 👈 必须加
import 'package:maibu_satabot_v2/features/remote_control/domain/repositories/remote_control_repository.dart';
import '../../../devices/domain/entities/device_entity.dart';
import '../bloc/permission_request_bloc.dart';
import '../bloc/permission_request_state.dart';
import '../bloc/permission_request_event.dart';
import '../widgets/ImmersionHeader.dart';
import '../widgets/quick_actions_grid.dart';
import '../widgets/work_params_card.dart';
@@ -63,7 +67,17 @@ class _HomePageState extends State<HomePage> {
tcpClient.connectBySwitch(host: TCPConsts.TCP_IP, port: TCPConsts.TCP_PORT, deviceName: firstDevice.deviceName).then((_) {
debugPrint('✅ [HomePage] TCP 连接成功');
tcpClient.startHeartbeat(interval: const Duration(seconds: 4));
// 🔥 关键:TCP 连接成功后,主动请求权限以激活服务器的推送机制
final remoteRepo = GetIt.instance<RemoteControlRepository>();
remoteRepo.requestControlPermission(firstDevice.deviceName, "app");
debugPrint('✅ [HomePage] 已发送权限请求,激活服务器推送');
});
} else {
// 🔥 TCP 已连接,也要发送权限请求
final remoteRepo = GetIt.instance<RemoteControlRepository>();
remoteRepo.requestControlPermission(firstDevice.deviceName, "app");
debugPrint('✅ [HomePage] TCP 已连接,已发送权限请求');
}
}
} catch (e) {
@@ -103,15 +117,68 @@ class _HomePageState extends State<HomePage> {
tenantName: '',
);
return Scaffold(
backgroundColor: const Color(0xFFF7F7F7),
body: CustomScrollView(
physics: const BouncingScrollPhysics(),
slivers: [
SliverToBoxAdapter(child: ImmersionHeader(device: currentDevice ?? defaultDevice)),
const SliverToBoxAdapter(child: QuickActionsGrid()),
SliverToBoxAdapter(child: WorkParamsCard()),
const SliverToBoxAdapter(child: SizedBox(height: 120)),
return BlocBuilder<PermissionRequestBloc, PermissionRequestState>(
buildWhen: (p, c) {
final shouldRebuild = p.runtimeType != c.runtimeType;
debugPrint('🔍 [HomePage-BlocBuilder] buildWhen 检查: previous=${p.runtimeType}, current=${c.runtimeType}, shouldRebuild=$shouldRebuild');
return shouldRebuild;
},
builder: (context, permissionState) {
debugPrint('🔍 [HomePage-BlocBuilder] builder 被调用,当前状态: ${permissionState.runtimeType}');
return Stack(
children: [
Scaffold(
backgroundColor: const Color(0xFFF7F7F7),
body: CustomScrollView(
physics: const BouncingScrollPhysics(),
slivers: [
SliverToBoxAdapter(child: ImmersionHeader(device: currentDevice ?? defaultDevice)),
const SliverToBoxAdapter(child: QuickActionsGrid()),
SliverToBoxAdapter(child: WorkParamsCard()),
const SliverToBoxAdapter(child: SizedBox(height: 120)),
],
),
),
if (permissionState is PermissionRequestDialogVisible)
_buildPermissionDialog(context, permissionState),
],
);
},
);
}
Widget _buildPermissionDialog(BuildContext context, PermissionRequestDialogVisible state) {
debugPrint('🔍 [HomePage] 🚨 弹窗正在显示!platform=${state.platform}');
final deviceId = context.read<DevicesCubit>().state.selectedDevice?.deviceName ?? "";
final permissionBloc = context.read<PermissionRequestBloc>();
return Container(
color: Colors.black54,
alignment: Alignment.center,
child: AlertDialog(
title: Text(AppLocalizations.of(context).translate('remote_control.permission_request_title')),
content: Text(AppLocalizations.of(context).translate('remote_control.permission_request_content')),
actions: [
TextButton(
onPressed: () {
permissionBloc.add(PermissionDialogDismissed(
agree: false,
deviceId: deviceId,
));
},
child: Text(AppLocalizations.of(context).translate('remote_control.refuse')),
),
TextButton(
onPressed: () {
permissionBloc.add(PermissionDialogDismissed(
agree: true,
deviceId: deviceId,
));
},
child: Text(AppLocalizations.of(context).translate('remote_control.agree')),
),
],
),
);

View File

@@ -8,6 +8,7 @@ import 'package:maibu_satabot_v2/features/home/presentation/pages/productDesc.da
import '../../../../core/di/injection.dart';
import '../../../../core/router/route_paths.dart';
import '../../../devices/presentation/bloc/device_status_bloc.dart';
import '../../presentation/bloc/permission_request_bloc.dart';
import '../../../remote_control/presentation/bloc/remote_control_cubit.dart';
import '../../../remote_control/presentation/pages/remote_control_page.dart';
import '../pages/route_plan_page.dart';
@@ -41,6 +42,12 @@ class HomeRoutes {
/// 2. 首页 Tab 分支(带导航栏)
/// 仅保留真正的首页入口
static StatefulShellBranch get branch => StatefulShellBranch(
routes: [GoRoute(path: RoutePaths.home, builder: (context, state) => const HomePage())],
routes: [GoRoute(
path: RoutePaths.home,
builder: (context, state) => BlocProvider.value(
value: sl<PermissionRequestBloc>(), // 使用全局单例,持久监听 0x12
child: const HomePage(),
),
)],
);
}

View File

@@ -455,7 +455,7 @@ class RemoteControlCubit extends Cubit<RemoteControlState> {
(success) {
// 更新状态
//print('✅ [RemoteControl] 请求控制权限成功:$success');
_logger.logWithLevel('✅ [RemoteControl] 请求控制权限成功:$success');
_logger.logWithLevel('✅ [RemoteControl] 请求控制权限成功:$success',shouldLog: true);
///处理result
if(success){
emit(state.copyWith(hasPermission: true));

View File

@@ -61,7 +61,7 @@ class _RemoteControlPageState extends State<RemoteControlPage> {
//print("远程控制要推出啦");
final deviceState = _devicesCubit?.state;
if (deviceState?.selectedDevice != null) {
_cubit.releasePermission("app");
// _cubit.releasePermission("app");
}
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);