首页的权限交接的弹窗(关闭了释放权限)+添加日志过滤功能,支持通过 shouldLog参数控制日志输出
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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, []);
|
||||
|
||||
@@ -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')),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -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(),
|
||||
),
|
||||
)],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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]);
|
||||
|
||||
Reference in New Issue
Block a user