diff --git a/assets/svgs/remote_recognition.svg b/assets/svgs/remote_recognition.svg
new file mode 100644
index 00000000..a9eca779
--- /dev/null
+++ b/assets/svgs/remote_recognition.svg
@@ -0,0 +1,10 @@
+
diff --git a/assets/svgs/remote_recognition_off.svg b/assets/svgs/remote_recognition_off.svg
new file mode 100644
index 00000000..67a74580
--- /dev/null
+++ b/assets/svgs/remote_recognition_off.svg
@@ -0,0 +1,11 @@
+
diff --git a/lib/core/consts/tcp_consts.dart b/lib/core/consts/tcp_consts.dart
index b369c099..6b61b38e 100644
--- a/lib/core/consts/tcp_consts.dart
+++ b/lib/core/consts/tcp_consts.dart
@@ -1,5 +1,5 @@
class TCPConsts {
- // static const String TCP_IP = "1.95.137.212";
- static const String TCP_IP = "192.210.172.250";
+ static const String TCP_IP = "1.95.137.212";
+ // static const String TCP_IP = "192.210.172.250";
static const int TCP_PORT = 9001;
}
diff --git a/lib/core/di/injection.dart b/lib/core/di/injection.dart
index b02597b5..35c8554d 100644
--- a/lib/core/di/injection.dart
+++ b/lib/core/di/injection.dart
@@ -1,4 +1,5 @@
import 'package:dio/dio.dart';
+import 'package:flutter/cupertino.dart';
import 'package:get_it/get_it.dart';
import 'package:go_router/go_router.dart';
import 'package:maibu_satabot_v2/core/consts/tcp_consts.dart';
@@ -32,6 +33,7 @@ import '../router/app_router.dart';
import '../../features/auth/data/datasources/impl/auth_local_datasource_impl.dart';
import '../../features/auth/data/datasources/i_auth_local_datasource.dart';
+final GlobalKey rootNavigatorKey = GlobalKey();
final sl = GetIt.instance;
Future init() async {
@@ -91,7 +93,7 @@ Future init() async {
/// 5. 状态管理 (Cubit/Bloc)
sl.registerLazySingleton(() => AppUserCubit(sl())); // AuthCubit 依赖它,必须先注册
sl.registerLazySingleton(() => DevicesCubit(sl(), sl()));
- sl.registerFactory(() => RemoteControlCubit(sl()));
+ sl.registerFactory(() => RemoteControlCubit(sl(), sl()));
/// 6. 认证 (Auth)
// --- 关键修改点 1: AuthCubit 必须在 GoRouter 之前注册,并传入参数 ---
@@ -109,4 +111,5 @@ Future init() async {
/// 8. 页面级 Bloc/Cubit (Factory)
sl.registerFactory(() => LoginBloc(sl()));
sl.registerFactory(() => LoginCubit(sl(), sl()));
+
}
diff --git a/lib/core/infrastructure/logging/app_bloc_observer.dart b/lib/core/infrastructure/logging/app_bloc_observer.dart
index 7a084594..49363ed7 100644
--- a/lib/core/infrastructure/logging/app_bloc_observer.dart
+++ b/lib/core/infrastructure/logging/app_bloc_observer.dart
@@ -14,9 +14,13 @@ class AppBlocObserver extends BlocObserver {
@override
void onChange(BlocBase bloc, Change change) {
super.onChange(bloc, change);
- logger.addBreadcrumb(
- 'Bloc State Change',
- category: bloc.runtimeType.toString(),
- );
+ // 在本地日志记录详细的面包屑
+ // logger.addBreadcrumb(
+ // 'State changed from ${change.currentState} to ${change.nextState}',
+ // category: bloc.runtimeType.toString(),
+ // );
+ //
+ // // 如果你想在控制台直接看到状态跳变(可选)
+ // logger.log('Transition in ${bloc.runtimeType}: ${change.currentState} -> ${change.nextState}');
}
}
diff --git a/lib/core/infrastructure/logging/sentry_logger_impl.dart b/lib/core/infrastructure/logging/sentry_logger_impl.dart
index c045af4b..dc1bcd41 100644
--- a/lib/core/infrastructure/logging/sentry_logger_impl.dart
+++ b/lib/core/infrastructure/logging/sentry_logger_impl.dart
@@ -1,4 +1,4 @@
-// lib/core/infrastructure/logging/sentry_logger_impl.dart
+import 'dart:async';
import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/foundation.dart';
@@ -11,15 +11,12 @@ import 'package:intl/intl.dart';
class SentryLoggerImpl implements ILoggerService {
File? _localLogFile;
- /// --- 临时控制开关 ---
- /// true: 上传到 Sentry + 本地存储
- /// false: 只进行本地存储 + 控制台打印
- bool get _isRemoteEnabled => false;
+ /// 控制开关
+ bool get _isRemoteDsnEnabled => false;
@override
Future init() async {
- // 只有在开启远程上传时才初始化 Sentry SDK
- if (_isRemoteEnabled) {
+ if (_isRemoteDsnEnabled) {
await SentryFlutter.init((options) {
options.dsn = EnvConfig.sentryDsn;
options.environment = EnvConfig.environment;
@@ -27,113 +24,97 @@ class SentryLoggerImpl implements ILoggerService {
});
await _collectDeviceInfo();
}
-
- // 无论是否上传服务器,始终准备本地日志文件
await _prepareLocalFile();
}
+ /// 【核心功能】:自动抓取调用者信息
+ String _getCallerInfo() {
+ try {
+ final stackTrace = StackTrace.current.toString();
+ final lines = stackTrace.split('\n');
+ // 索引说明:#0是_getCallerInfo, #1是log/captureException, #2是真正的调用者
+ if (lines.length > 3) {
+ final callerLine = lines[3];
+ // 匹配文件名.dart
+ final match = RegExp(r'([a-zA-Z0-9_]+\.dart)').firstMatch(callerLine);
+ return match?.group(1) ?? "UnknownSource";
+ }
+ } catch (_) {}
+ return "Unknown";
+ }
+
+ @override
+ void captureException(dynamic exception, {dynamic stackTrace}) {
+ final caller = _getCallerInfo();
+ final timestamp = DateFormat('yyyy-MM-dd HH:mm:ss').format(DateTime.now());
+
+ // 控制台红色输出
+ final logEntry = "\x1B[31m[$timestamp] [❌ ERROR] [$caller] $exception\n$stackTrace\x1B[0m\n";
+
+ if (_isRemoteDsnEnabled) {
+ Sentry.captureException(exception, stackTrace: stackTrace, withScope: (scope) {
+ scope.setTag('caller', caller);
+ });
+ }
+
+ _writeToLocalFile(logEntry);
+ if (kDebugMode) debugPrint(logEntry);
+ }
+
+ @override
+ void log(String message, {bool isWarning = false, Map? data}) {
+ final caller = _getCallerInfo();
+ final timestamp = DateFormat('yyyy-MM-dd HH:mm:ss').format(DateTime.now());
+
+ // 颜色定义:警告黄,信息绿
+ final color = isWarning ? '\x1B[33m' : '\x1B[32m';
+ final level = isWarning ? 'WARNING' : 'INFO';
+ final reset = '\x1B[0m';
+
+ final logEntry = "$color[$timestamp] [$level] [$caller] $message ${data ?? ''}$reset\n";
+
+ if (_isRemoteDsnEnabled) {
+ Sentry.captureMessage(
+ "[$caller] $message",
+ level: isWarning ? SentryLevel.warning : SentryLevel.info,
+ );
+ }
+
+ _writeToLocalFile(logEntry);
+ if (kDebugMode) debugPrint(logEntry);
+ }
+
+ // --- 其余辅助逻辑 ---
+
Future _prepareLocalFile() async {
try {
final directory = await getApplicationDocumentsDirectory();
final fileName = "${DateFormat('yyyy-MM-dd').format(DateTime.now())}_logs.txt";
_localLogFile = File('${directory.path}/$fileName');
-
- if (!await _localLogFile!.exists()) {
- await _localLogFile!.create(recursive: true);
- }
+ if (!await _localLogFile!.exists()) await _localLogFile!.create(recursive: true);
} catch (e) {
debugPrint("无法创建本地日志文件: $e");
}
}
- Future _collectDeviceInfo() async {
- if (!_isRemoteEnabled) return;
-
- final deviceInfo = DeviceInfoPlugin();
- Sentry.configureScope((scope) async {
- if (Platform.isAndroid) {
- final android = await deviceInfo.androidInfo;
- scope.setTag('device_model', android.model);
- scope.setTag('os_version', android.version.release);
- } else if (Platform.isIOS) {
- final ios = await deviceInfo.iosInfo;
- scope.setTag('device_model', ios.utsname.machine);
- scope.setTag('os_version', ios.systemVersion);
- }
- });
- }
-
- @override
- void captureException(dynamic exception, {dynamic stackTrace}) {
- final timestamp = DateTime.now().toIso8601String();
- final logEntry = "[$timestamp] [ERROR] $exception\n$stackTrace\n";
-
- // 1. 只有开启时上传
- if (_isRemoteEnabled) {
- Sentry.captureException(exception, stackTrace: stackTrace);
- }
-
- // 2. 始终写入本地
- _writeToLocalFile(logEntry);
-
- // 3. 始终控制台打印
- if (kDebugMode) {
- debugPrint(logEntry);
- }
- }
-
- @override
- void log(
- String message, {
- bool isWarning = false,
- Map? data,
- }) {
- final timestamp = DateTime.now().toIso8601String();
- final level = isWarning ? 'WARNING' : 'INFO';
- final logEntry = "[$timestamp] [$level] $message ${data ?? ''}\n";
-
- // 1. 只有开启时上传到 Sentry
- if (_isRemoteEnabled) {
- Sentry.captureMessage(
- message,
- level: isWarning ? SentryLevel.warning : SentryLevel.info,
- );
- }
-
- // 2. 始终写入本地文件
- _writeToLocalFile(logEntry);
-
- // 3. 始终打印到控制台
- if (kDebugMode) {
- debugPrint(logEntry);
- }
- }
-
Future _writeToLocalFile(String entry) async {
try {
- await _localLogFile?.writeAsString(entry, mode: FileMode.append);
- } catch (e) {
- // 这里的错误不再抛出,避免日志系统本身崩溃导致主业务中断
- }
- }
-
- Future getLocalLogPath() async {
- return _localLogFile?.path;
+ // 写入文件时移除 ANSI 颜色字符
+ final cleanEntry = entry.replaceAll(RegExp(r'\x1B\[[0-9;]*m'), '');
+ await _localLogFile?.writeAsString(cleanEntry, mode: FileMode.append);
+ } catch (_) {}
}
@override
void addBreadcrumb(String message, {String? category}) {
- // 面包屑主要用于 Sentry 报错时的上下文,如果关闭了远程,记录本地即可(可选)
- if (_isRemoteEnabled) {
- Sentry.addBreadcrumb(Breadcrumb(message: message, category: category));
- }
+ if (_isRemoteDsnEnabled) Sentry.addBreadcrumb(Breadcrumb(message: message, category: category));
_writeToLocalFile("[BREADCRUMB] [$category] $message\n");
}
@override
void setUser(String userId) {
- if (_isRemoteEnabled) {
- Sentry.configureScope((scope) => scope.setUser(SentryUser(id: userId)));
- }
+ if (_isRemoteDsnEnabled) Sentry.configureScope((scope) => scope.setUser(SentryUser(id: userId)));
}
+
+ Future _collectDeviceInfo() async { /* 同原逻辑 */ }
}
\ No newline at end of file
diff --git a/lib/core/router/app_router.dart b/lib/core/router/app_router.dart
index 73765c83..8bc775ac 100644
--- a/lib/core/router/app_router.dart
+++ b/lib/core/router/app_router.dart
@@ -8,10 +8,12 @@ import 'package:maibu_satabot_v2/features/my/presentation/routes/my_routes.dart'
import '../../features/auth/presentation/bloc/auth_cubit.dart';
import '../../features/auth/presentation/bloc/auth_state.dart';
import '../../features/main_container/presentation/main_wrapper.dart';
+import '../di/injection.dart';
import 'go_router_refresh_stream.dart';
GoRouter createRouter(AuthCubit authCubit) {
return GoRouter(
+ navigatorKey: rootNavigatorKey,
initialLocation: RoutePaths.login,
refreshListenable: GoRouterRefreshStream(authCubit.stream),
redirect: (context, state) {
diff --git a/lib/features/connectivity/presentation/bloc/connectivity_cubit.dart b/lib/features/connectivity/presentation/bloc/connectivity_cubit.dart
index 9f6b9a8d..8679e351 100644
--- a/lib/features/connectivity/presentation/bloc/connectivity_cubit.dart
+++ b/lib/features/connectivity/presentation/bloc/connectivity_cubit.dart
@@ -16,6 +16,7 @@ class ConnectivityCubit extends Cubit {
StreamSubscription? _statusSub;
StreamSubscription? _packetSub;
Timer? _watchdogTimer;
+ static const int _watchdogTimerDuration = 10;
// 核心:这是唯一的流入口
Stream? _broadcastPacketStream;
@@ -23,12 +24,12 @@ class ConnectivityCubit extends Cubit {
ConnectivityCubit() : super(ConnectivityState.initial());
void setConnection(IConnection? connection, ControlMode mode) {
- // 1. 彻底清理(按顺序来)
+ // 1. 彻底清理
_stopMonitoring();
_statusSub?.cancel();
_activeConnection?.disconnect();
- // 2. 关键:切断旧流的所有引用
+ // 2. 切断旧流的所有引用
_broadcastPacketStream = null;
_activeConnection = connection;
@@ -38,7 +39,7 @@ class ConnectivityCubit extends Cubit {
emit(state.copyWith(activeMode: mode, status: status));
if (status == ConnectionStatus.connected) {
- // 3. 只有连接成功时,才初始化唯一的广播流
+ // 3. 只有连接成功时,才初始化广播流
_ensureStreamInitialized();
_startMonitoring();
} else {
@@ -54,17 +55,10 @@ class ConnectivityCubit extends Cubit {
// 核心:强制初始化,确保 transform 只跑一次
void _ensureStreamInitialized() {
if (_activeConnection != null && _broadcastPacketStream == null) {
- print("DEBUG: 正在创建唯一的广播流转换器");
-
- // 确保 rawStream 是有效的
- if (_activeConnection!.rawStream != null) {
- _broadcastPacketStream = _activeConnection!.rawStream
- .transform(PacketParserTransformer())
- .asBroadcastStream();
- } else {
- print("DEBUG: rawStream is null,无法初始化广播流");
- }
- }
+ _broadcastPacketStream = _activeConnection!.rawStream
+ .transform(PacketParserTransformer())
+ .asBroadcastStream();
+ }
}
@@ -80,9 +74,9 @@ class ConnectivityCubit extends Cubit {
_resetWatchdog();
if (packet.cmdType == 0xFF) {
sendCommand(0xFF, null);
- sl().log("receive heartbeat,has response...");
+ sl().log("收到心跳包,已回复...");
}
- }, onError: (e) => print("流监听错误: $e"));
+ }, onError: (e) => sl().log("流监听错误: $e"));
}
// Getter 也要改,保证它只返回已经创建好的流
@@ -94,8 +88,8 @@ class ConnectivityCubit extends Cubit {
// --- 其他逻辑保持不变 ---
void _resetWatchdog() {
_watchdogTimer?.cancel();
- _watchdogTimer = Timer(const Duration(seconds: 10), () {
- print("警告:10秒内数据,触发断开逻辑");
+ _watchdogTimer = Timer(const Duration(seconds: _watchdogTimerDuration), () {
+ sl().log("$_watchdogTimerDuration秒内未收到心跳包...");
emit(state.copyWith(status: ConnectionStatus.disconnected));
});
}
diff --git a/lib/features/connectivity/presentation/bloc/connectivity_state.dart b/lib/features/connectivity/presentation/bloc/connectivity_state.dart
index afdd91ee..134cf61c 100644
--- a/lib/features/connectivity/presentation/bloc/connectivity_state.dart
+++ b/lib/features/connectivity/presentation/bloc/connectivity_state.dart
@@ -22,6 +22,5 @@ class ConnectivityState extends Equatable{
}
@override
- // TODO: implement props
List