chore: release v1.0.4
This commit is contained in:
parent
3782db6957
commit
095e9cc464
|
|
@ -0,0 +1,13 @@
|
|||
include: package:very_good_analysis/analysis_options.yaml
|
||||
|
||||
linter:
|
||||
rules:
|
||||
public_member_api_docs: false
|
||||
sort_pub_dependencies: false
|
||||
|
||||
analyzer:
|
||||
errors:
|
||||
cascade_invocations: ignore
|
||||
inference_failure_on_collection_literal: ignore
|
||||
lines_longer_than_80_chars: ignore # Too strict for existing code
|
||||
|
||||
|
|
@ -14,19 +14,8 @@ class MyDebugApp extends StatelessWidget {
|
|||
title: 'YX 网络检查器调试',
|
||||
home: YxNetInspector(
|
||||
config: const YxNetInspectorConfig(
|
||||
showFloatingBall: true,
|
||||
ballSize: 60,
|
||||
ballColor: Colors.blue,
|
||||
),
|
||||
theme: const YxNetInspectorTheme(
|
||||
primaryColor: Colors.blue,
|
||||
backgroundColor: Colors.white,
|
||||
textColor: Colors.black87,
|
||||
successColor: Colors.green,
|
||||
errorColor: Colors.red,
|
||||
warningColor: Colors.orange,
|
||||
cardColor: Colors.white,
|
||||
),
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('调试测试'),
|
||||
|
|
@ -55,7 +44,7 @@ class MyDebugApp extends StatelessWidget {
|
|||
statusCode: 200,
|
||||
responseData: const {
|
||||
'title': 'Test Post',
|
||||
'body': 'Test content'
|
||||
'body': 'Test content',
|
||||
},
|
||||
duration: const Duration(seconds: 1),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -87,26 +87,26 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
|
||||
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "10.0.9"
|
||||
version: "11.0.2"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
|
||||
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.0.9"
|
||||
version: "3.0.10"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_testing
|
||||
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
|
||||
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
version: "3.0.2"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -196,10 +196,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
|
||||
sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.7.4"
|
||||
version: "0.7.6"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -212,10 +212,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: vector_math
|
||||
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
|
||||
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
version: "2.2.0"
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -238,7 +238,7 @@ packages:
|
|||
path: ".."
|
||||
relative: true
|
||||
source: path
|
||||
version: "1.0.2"
|
||||
version: "1.0.3"
|
||||
sdks:
|
||||
dart: ">=3.7.0-0 <4.0.0"
|
||||
dart: ">=3.8.0-0 <4.0.0"
|
||||
flutter: ">=3.18.0-18.0.pre.54"
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
// ignore_for_file: prefer_constructors_over_static_methods // Singleton pattern requires static access
|
||||
import 'package:flutter/foundation.dart';
|
||||
import '../models/network_log_entry.dart';
|
||||
import '../models/inspector_config.dart';
|
||||
import 'package:yx_net_inspector/src/models/inspector_config.dart';
|
||||
import 'package:yx_net_inspector/src/models/network_log_entry.dart';
|
||||
|
||||
/// 网络日志检查器控制器
|
||||
/// 管理网络日志和检查器状态
|
||||
class YxNetInspectorController extends ChangeNotifier {
|
||||
YxNetInspectorController._internal();
|
||||
static YxNetInspectorController? _instance;
|
||||
static YxNetInspectorController get instance {
|
||||
return _instance ??= YxNetInspectorController._internal();
|
||||
}
|
||||
|
||||
YxNetInspectorController._internal();
|
||||
|
||||
/// 配置信息
|
||||
late YxNetInspectorConfig _config;
|
||||
YxNetInspectorConfig get config => _config;
|
||||
|
|
@ -35,12 +35,34 @@ class YxNetInspectorController extends ChangeNotifier {
|
|||
bool _showFloatingBall = true;
|
||||
bool get showFloatingBall => _showFloatingBall && _config.isEnabled;
|
||||
|
||||
/// 面板显示状态
|
||||
bool _isPanelVisible = false;
|
||||
bool get isPanelVisible => _isPanelVisible && _config.isEnabled;
|
||||
|
||||
/// 初始化控制器配置
|
||||
void initialize(YxNetInspectorConfig config) {
|
||||
_config = config;
|
||||
_showFloatingBall = config.showFloatingBall;
|
||||
}
|
||||
|
||||
/// 显示面板
|
||||
void showPanel() {
|
||||
_isPanelVisible = true;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// 隐藏面板
|
||||
void hidePanel() {
|
||||
_isPanelVisible = false;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// 切换面板显示状态
|
||||
void togglePanel() {
|
||||
_isPanelVisible = !_isPanelVisible;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// 记录网络请求
|
||||
void logRequest({
|
||||
required String id,
|
||||
|
|
@ -61,7 +83,6 @@ class YxNetInspectorController extends ChangeNotifier {
|
|||
queryParameters: queryParameters,
|
||||
timestamp: DateTime.now(),
|
||||
isSuccess: false, // 响应到达时会更新
|
||||
status: NetworkRequestStatus.pending, // 初始状态为进行中
|
||||
);
|
||||
|
||||
_logs.insert(0, entry);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,21 @@ import 'package:flutter/foundation.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
/// 网络检查器配置
|
||||
@immutable
|
||||
class YxNetInspectorConfig {
|
||||
const YxNetInspectorConfig({
|
||||
this.showFloatingBall = true,
|
||||
this.ballSize = 60.0,
|
||||
this.ballColor,
|
||||
this.showInDebugMode = true,
|
||||
this.showInReleaseMode = false,
|
||||
this.maxLogs = 1000,
|
||||
this.initialPosition,
|
||||
this.draggable = true,
|
||||
this.showBadge = true,
|
||||
this.autoHide = false,
|
||||
});
|
||||
|
||||
/// 是否显示悬浮球
|
||||
final bool showFloatingBall;
|
||||
|
||||
|
|
@ -33,19 +47,6 @@ class YxNetInspectorConfig {
|
|||
/// 是否自动隐藏悬浮球
|
||||
final bool autoHide;
|
||||
|
||||
const YxNetInspectorConfig({
|
||||
this.showFloatingBall = true,
|
||||
this.ballSize = 60.0,
|
||||
this.ballColor,
|
||||
this.showInDebugMode = true,
|
||||
this.showInReleaseMode = false,
|
||||
this.maxLogs = 1000,
|
||||
this.initialPosition,
|
||||
this.draggable = true,
|
||||
this.showBadge = true,
|
||||
this.autoHide = false,
|
||||
});
|
||||
|
||||
/// 根据当前模式判断检查器是否应该启用
|
||||
bool get isEnabled {
|
||||
if (kDebugMode) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,40 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
/// 网络检查器主题配置
|
||||
@immutable
|
||||
class YxNetInspectorTheme {
|
||||
const YxNetInspectorTheme({
|
||||
this.primaryColor = Colors.blue,
|
||||
this.backgroundColor = Colors.white,
|
||||
this.textColor = Colors.black87,
|
||||
this.secondaryTextColor = Colors.grey,
|
||||
this.errorColor = Colors.red,
|
||||
this.successColor = Colors.green,
|
||||
this.warningColor = Colors.orange,
|
||||
this.borderColor = const Color(0xFFE0E0E0),
|
||||
this.cardColor = Colors.white,
|
||||
this.floatingBallColor,
|
||||
});
|
||||
|
||||
/// 创建暗色主题
|
||||
factory YxNetInspectorTheme.dark() {
|
||||
return const YxNetInspectorTheme(
|
||||
primaryColor: Colors.blueAccent,
|
||||
backgroundColor: Color(0xFF121212),
|
||||
textColor: Colors.white,
|
||||
errorColor: Colors.redAccent,
|
||||
successColor: Colors.greenAccent,
|
||||
warningColor: Colors.orangeAccent,
|
||||
borderColor: Color(0xFF333333),
|
||||
cardColor: Color(0xFF1E1E1E),
|
||||
);
|
||||
}
|
||||
|
||||
/// 创建亮色主题(默认)
|
||||
factory YxNetInspectorTheme.light() {
|
||||
return const YxNetInspectorTheme();
|
||||
}
|
||||
|
||||
/// 检查器 UI 的主色调
|
||||
final Color primaryColor;
|
||||
|
||||
|
|
@ -32,39 +65,6 @@ class YxNetInspectorTheme {
|
|||
/// 悬浮球颜色(如果设置则覆盖配置)
|
||||
final Color? floatingBallColor;
|
||||
|
||||
const YxNetInspectorTheme({
|
||||
this.primaryColor = Colors.blue,
|
||||
this.backgroundColor = Colors.white,
|
||||
this.textColor = Colors.black87,
|
||||
this.secondaryTextColor = Colors.grey,
|
||||
this.errorColor = Colors.red,
|
||||
this.successColor = Colors.green,
|
||||
this.warningColor = Colors.orange,
|
||||
this.borderColor = const Color(0xFFE0E0E0),
|
||||
this.cardColor = Colors.white,
|
||||
this.floatingBallColor,
|
||||
});
|
||||
|
||||
/// 创建暗色主题
|
||||
factory YxNetInspectorTheme.dark() {
|
||||
return const YxNetInspectorTheme(
|
||||
primaryColor: Colors.blueAccent,
|
||||
backgroundColor: Color(0xFF121212),
|
||||
textColor: Colors.white,
|
||||
secondaryTextColor: Colors.grey,
|
||||
errorColor: Colors.redAccent,
|
||||
successColor: Colors.greenAccent,
|
||||
warningColor: Colors.orangeAccent,
|
||||
borderColor: Color(0xFF333333),
|
||||
cardColor: Color(0xFF1E1E1E),
|
||||
);
|
||||
}
|
||||
|
||||
/// 创建亮色主题(默认)
|
||||
factory YxNetInspectorTheme.light() {
|
||||
return const YxNetInspectorTheme();
|
||||
}
|
||||
|
||||
/// 创建一个带有更新值的副本
|
||||
YxNetInspectorTheme copyWith({
|
||||
Color? primaryColor,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,23 @@ enum NetworkRequestStatus {
|
|||
}
|
||||
|
||||
/// 网络请求日志条目
|
||||
@immutable
|
||||
class NetworkLogEntry {
|
||||
const NetworkLogEntry({
|
||||
required this.id,
|
||||
required this.method,
|
||||
required this.url,
|
||||
required this.timestamp,
|
||||
required this.isSuccess,
|
||||
this.headers,
|
||||
this.requestData,
|
||||
this.queryParameters,
|
||||
this.statusCode,
|
||||
this.responseData,
|
||||
this.errorMessage,
|
||||
this.duration,
|
||||
this.status = NetworkRequestStatus.pending,
|
||||
});
|
||||
final String id;
|
||||
final String method;
|
||||
final String url;
|
||||
|
|
@ -28,22 +44,6 @@ class NetworkLogEntry {
|
|||
final bool isSuccess;
|
||||
final NetworkRequestStatus status;
|
||||
|
||||
NetworkLogEntry({
|
||||
required this.id,
|
||||
required this.method,
|
||||
required this.url,
|
||||
this.headers,
|
||||
this.requestData,
|
||||
this.queryParameters,
|
||||
this.statusCode,
|
||||
this.responseData,
|
||||
this.errorMessage,
|
||||
required this.timestamp,
|
||||
this.duration,
|
||||
required this.isSuccess,
|
||||
this.status = NetworkRequestStatus.pending,
|
||||
});
|
||||
|
||||
/// 根据请求状态获取状态颜色
|
||||
Color get statusColor {
|
||||
switch (status) {
|
||||
|
|
@ -70,7 +70,7 @@ class NetworkLogEntry {
|
|||
|
||||
/// 获取预估请求大小
|
||||
int get requestSize {
|
||||
int size = 0;
|
||||
var size = 0;
|
||||
if (requestData != null) {
|
||||
size += requestData.toString().length;
|
||||
}
|
||||
|
|
@ -102,7 +102,7 @@ class NetworkLogEntry {
|
|||
String get formattedDuration {
|
||||
if (duration == null) return '未知';
|
||||
final ms = duration!.inMilliseconds;
|
||||
if (ms < 1000) return '${ms} ms';
|
||||
if (ms < 1000) return '$ms ms';
|
||||
return '${(ms / 1000).toStringAsFixed(1)} s';
|
||||
}
|
||||
|
||||
|
|
@ -111,7 +111,7 @@ class NetworkLogEntry {
|
|||
try {
|
||||
final uri = Uri.parse(url);
|
||||
return '${uri.path}${uri.query.isNotEmpty ? '?${uri.query}' : ''}';
|
||||
} catch (e) {
|
||||
} on Object catch (_) {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
|
@ -119,8 +119,8 @@ class NetworkLogEntry {
|
|||
String get hostUrl {
|
||||
try {
|
||||
final uri = Uri.parse(url);
|
||||
return '${uri.host}';
|
||||
} catch (e) {
|
||||
return uri.host;
|
||||
} on Object catch (_) {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,17 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import '../controller/yx_net_inspector_controller.dart';
|
||||
import '../models/inspector_config.dart';
|
||||
import '../models/inspector_theme.dart';
|
||||
import 'inspector_panel.dart';
|
||||
import 'package:yx_net_inspector/src/controller/yx_net_inspector_controller.dart';
|
||||
import 'package:yx_net_inspector/src/models/inspector_config.dart';
|
||||
import 'package:yx_net_inspector/src/models/inspector_theme.dart';
|
||||
|
||||
/// 悬浮调试球组件
|
||||
class YxFloatingBall extends StatefulWidget {
|
||||
const YxFloatingBall({
|
||||
required this.config, required this.theme, required this.controller, super.key,
|
||||
});
|
||||
final YxNetInspectorConfig config;
|
||||
final YxNetInspectorTheme theme;
|
||||
final YxNetInspectorController controller;
|
||||
|
||||
const YxFloatingBall({
|
||||
super.key,
|
||||
required this.config,
|
||||
required this.theme,
|
||||
required this.controller,
|
||||
});
|
||||
|
||||
@override
|
||||
State<YxFloatingBall> createState() => _YxFloatingBallState();
|
||||
}
|
||||
|
|
@ -28,8 +23,6 @@ class _YxFloatingBallState extends State<YxFloatingBall>
|
|||
late Animation<double> _opacityAnimation;
|
||||
|
||||
Offset _position = const Offset(20, 200);
|
||||
bool _isExpanded = false;
|
||||
OverlayEntry? _currentOverlayEntry;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
|
@ -46,167 +39,23 @@ class _YxFloatingBallState extends State<YxFloatingBall>
|
|||
vsync: this,
|
||||
);
|
||||
|
||||
_scaleAnimation = Tween<double>(begin: 1.0, end: 1.2).animate(
|
||||
_scaleAnimation = Tween<double>(begin: 1, end: 1.2).animate(
|
||||
CurvedAnimation(parent: _animationController, curve: Curves.easeInOut),
|
||||
);
|
||||
|
||||
_opacityAnimation = Tween<double>(begin: 1.0, end: 0.8).animate(
|
||||
_opacityAnimation = Tween<double>(begin: 1, end: 0.8).animate(
|
||||
CurvedAnimation(parent: _animationController, curve: Curves.easeInOut),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
// 清理 overlay
|
||||
if (_currentOverlayEntry != null) {
|
||||
_currentOverlayEntry!.remove();
|
||||
_currentOverlayEntry = null;
|
||||
}
|
||||
_animationController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _onTap() {
|
||||
if (_isExpanded) {
|
||||
_hideInspectorPanel();
|
||||
} else {
|
||||
_showInspectorPanel();
|
||||
}
|
||||
}
|
||||
|
||||
void _showInspectorPanel() {
|
||||
setState(() {
|
||||
_isExpanded = true;
|
||||
});
|
||||
_animationController.forward();
|
||||
|
||||
// 优先使用 Overlay,如果失败则使用 Navigator
|
||||
_showInspectorOverlay();
|
||||
}
|
||||
|
||||
void _showInspectorOverlay() {
|
||||
// 延迟到下一帧执行,确保Widget树已经完全构建
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_tryShowOverlay();
|
||||
});
|
||||
}
|
||||
|
||||
void _tryShowOverlay() {
|
||||
// 尝试找到 Overlay
|
||||
OverlayState? overlay;
|
||||
try {
|
||||
overlay = Overlay.of(context, rootOverlay: true);
|
||||
} catch (e) {
|
||||
debugPrint('YxNetInspector: 根 Overlay 查找失败: $e');
|
||||
// 如果 Overlay.of 失败,尝试手动查找
|
||||
overlay = _findOverlayInContext(context);
|
||||
}
|
||||
|
||||
if (overlay == null) {
|
||||
// 如果仍然找不到 Overlay,使用备选方案
|
||||
_showInspectorDialog();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final overlayEntry = OverlayEntry(
|
||||
builder: (context) => Material(
|
||||
color: Colors.black54,
|
||||
child: Stack(
|
||||
children: [
|
||||
// 背景遮罩
|
||||
GestureDetector(
|
||||
onTap: _hideInspectorPanel,
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
),
|
||||
),
|
||||
// 检查器面板
|
||||
Center(
|
||||
child: YxInspectorPanel(
|
||||
theme: widget.theme,
|
||||
controller: widget.controller,
|
||||
onClose: _hideInspectorPanel,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
overlay.insert(overlayEntry);
|
||||
_currentOverlayEntry = overlayEntry;
|
||||
} catch (e) {
|
||||
debugPrint('YxNetInspector: 插入 OverlayEntry 失败: $e');
|
||||
// 重置状态
|
||||
setState(() {
|
||||
_isExpanded = false;
|
||||
});
|
||||
_animationController.reverse();
|
||||
}
|
||||
}
|
||||
|
||||
OverlayState? _findOverlayInContext(BuildContext context) {
|
||||
OverlayState? overlayState;
|
||||
context.visitAncestorElements((element) {
|
||||
if (element.widget is Overlay) {
|
||||
overlayState = Overlay.of(element);
|
||||
return false; // 停止遍历
|
||||
}
|
||||
return true; // 继续向上查找
|
||||
});
|
||||
return overlayState;
|
||||
}
|
||||
|
||||
void _showInspectorDialog() {
|
||||
// 作为最后的备选方案,创建一个自定义的全屏Overlay
|
||||
// 这里我们不依赖Navigator,而是手动管理显示状态
|
||||
_createCustomOverlay();
|
||||
}
|
||||
|
||||
void _createCustomOverlay() {
|
||||
// 如果所有 Overlay 方法都失败,我们显示一个简单的调试信息
|
||||
// 并重置状态,避免悬浮球卡在展开状态
|
||||
|
||||
// 显示一个简单的 SnackBar 或 print 提示
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) {
|
||||
try {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('网络检查器暂时无法显示,请检查应用结构'),
|
||||
duration: Duration(seconds: 2),
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
// 如果 ScaffoldMessenger 也不可用,只打印日志
|
||||
debugPrint('YxNetInspector: 无法显示检查器面板 - 请确保在 MaterialApp 内使用');
|
||||
}
|
||||
|
||||
// 重置状态
|
||||
setState(() {
|
||||
_isExpanded = false;
|
||||
});
|
||||
_animationController.reverse();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _hideInspectorPanel() {
|
||||
setState(() {
|
||||
_isExpanded = false;
|
||||
});
|
||||
_animationController.reverse();
|
||||
|
||||
// 如果有 overlay,移除它
|
||||
if (_currentOverlayEntry != null) {
|
||||
_currentOverlayEntry!.remove();
|
||||
_currentOverlayEntry = null;
|
||||
}
|
||||
// 注意:如果使用了 Navigator 的 PageRouteBuilder,
|
||||
// 对话框关闭会由 onClose 回调中的 Navigator.pop() 处理
|
||||
widget.controller.togglePanel();
|
||||
}
|
||||
|
||||
void _onPanStart(DragStartDetails details) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,50 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
/// 悬浮调试球配置
|
||||
@immutable
|
||||
class YxFloatingBallConfig {
|
||||
const YxFloatingBallConfig({
|
||||
this.size = 60.0,
|
||||
this.position = const Offset(20, 200),
|
||||
this.draggable = true,
|
||||
this.showBadge = true,
|
||||
this.autoHide = false,
|
||||
this.autoHideDuration = const Duration(seconds: 5),
|
||||
this.color,
|
||||
this.opacity = 1.0,
|
||||
});
|
||||
|
||||
/// 创建小型悬浮球配置
|
||||
factory YxFloatingBallConfig.small({Offset? position, Color? color}) {
|
||||
return YxFloatingBallConfig(
|
||||
size: 40,
|
||||
position: position ?? const Offset(20, 200),
|
||||
color: color,
|
||||
showBadge: false,
|
||||
);
|
||||
}
|
||||
|
||||
/// 创建大型悬浮球配置
|
||||
factory YxFloatingBallConfig.large({Offset? position, Color? color}) {
|
||||
return YxFloatingBallConfig(
|
||||
size: 80,
|
||||
position: position ?? const Offset(20, 200),
|
||||
color: color,
|
||||
);
|
||||
}
|
||||
|
||||
/// 创建最小化悬浮球配置
|
||||
factory YxFloatingBallConfig.minimal({Offset? position, Color? color}) {
|
||||
return YxFloatingBallConfig(
|
||||
size: 50,
|
||||
position: position ?? const Offset(20, 200),
|
||||
color: color,
|
||||
showBadge: false,
|
||||
draggable: false,
|
||||
autoHide: true,
|
||||
);
|
||||
}
|
||||
|
||||
/// 悬浮球大小
|
||||
final double size;
|
||||
|
||||
|
|
@ -26,49 +69,6 @@ class YxFloatingBallConfig {
|
|||
/// 悬浮球透明度
|
||||
final double opacity;
|
||||
|
||||
const YxFloatingBallConfig({
|
||||
this.size = 60.0,
|
||||
this.position = const Offset(20, 200),
|
||||
this.draggable = true,
|
||||
this.showBadge = true,
|
||||
this.autoHide = false,
|
||||
this.autoHideDuration = const Duration(seconds: 5),
|
||||
this.color,
|
||||
this.opacity = 1.0,
|
||||
});
|
||||
|
||||
/// 创建小型悬浮球配置
|
||||
factory YxFloatingBallConfig.small({Offset? position, Color? color}) {
|
||||
return YxFloatingBallConfig(
|
||||
size: 40.0,
|
||||
position: position ?? const Offset(20, 200),
|
||||
color: color,
|
||||
showBadge: false,
|
||||
);
|
||||
}
|
||||
|
||||
/// 创建大型悬浮球配置
|
||||
factory YxFloatingBallConfig.large({Offset? position, Color? color}) {
|
||||
return YxFloatingBallConfig(
|
||||
size: 80.0,
|
||||
position: position ?? const Offset(20, 200),
|
||||
color: color,
|
||||
showBadge: true,
|
||||
);
|
||||
}
|
||||
|
||||
/// 创建最小化悬浮球配置
|
||||
factory YxFloatingBallConfig.minimal({Offset? position, Color? color}) {
|
||||
return YxFloatingBallConfig(
|
||||
size: 50.0,
|
||||
position: position ?? const Offset(20, 200),
|
||||
color: color,
|
||||
showBadge: false,
|
||||
draggable: false,
|
||||
autoHide: true,
|
||||
);
|
||||
}
|
||||
|
||||
/// 创建一个带有更新值的副本
|
||||
YxFloatingBallConfig copyWith({
|
||||
double? size,
|
||||
|
|
|
|||
|
|
@ -1,22 +1,26 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import '../controller/yx_net_inspector_controller.dart';
|
||||
import '../models/inspector_theme.dart';
|
||||
import '../models/network_log_entry.dart';
|
||||
import 'log_detail_page.dart';
|
||||
import 'package:yx_net_inspector/src/controller/yx_net_inspector_controller.dart';
|
||||
import 'package:yx_net_inspector/src/models/inspector_theme.dart';
|
||||
import 'package:yx_net_inspector/src/models/network_log_entry.dart';
|
||||
import 'package:yx_net_inspector/src/widgets/log_detail_page.dart';
|
||||
import 'package:yx_net_inspector/src/widgets/panel/inspector_detail_header.dart';
|
||||
import 'package:yx_net_inspector/src/widgets/panel/inspector_header.dart';
|
||||
import 'package:yx_net_inspector/src/widgets/panel/inspector_log_list.dart';
|
||||
import 'package:yx_net_inspector/src/widgets/panel/inspector_search_bar.dart';
|
||||
import 'package:yx_net_inspector/src/widgets/panel/inspector_statistics.dart';
|
||||
|
||||
/// 网络检查器面板组件
|
||||
class YxInspectorPanel extends StatefulWidget {
|
||||
final YxNetInspectorTheme theme;
|
||||
final YxNetInspectorController controller;
|
||||
final VoidCallback onClose;
|
||||
|
||||
const YxInspectorPanel({
|
||||
super.key,
|
||||
required this.theme,
|
||||
required this.controller,
|
||||
required this.onClose,
|
||||
super.key,
|
||||
});
|
||||
final YxNetInspectorTheme theme;
|
||||
final YxNetInspectorController controller;
|
||||
final VoidCallback onClose;
|
||||
|
||||
@override
|
||||
State<YxInspectorPanel> createState() => _YxInspectorPanelState();
|
||||
|
|
@ -52,8 +56,14 @@ class _YxInspectorPanelState extends State<YxInspectorPanel> {
|
|||
});
|
||||
}
|
||||
|
||||
void _toggleFullScreen() {
|
||||
setState(() {
|
||||
_isFullScreen = !_isFullScreen;
|
||||
});
|
||||
}
|
||||
|
||||
List<NetworkLogEntry> get _filteredLogs {
|
||||
List<NetworkLogEntry> logs = widget.controller.logs;
|
||||
var logs = widget.controller.logs;
|
||||
|
||||
// 搜索过滤
|
||||
if (_searchKeyword.isNotEmpty) {
|
||||
|
|
@ -70,497 +80,8 @@ class _YxInspectorPanelState extends State<YxInspectorPanel> {
|
|||
return logs;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListenableBuilder(
|
||||
listenable: widget.controller,
|
||||
builder: (context, child) {
|
||||
Widget content;
|
||||
|
||||
if (_showDetailPage && _selectedLog != null) {
|
||||
// 显示详情页面
|
||||
content = Column(
|
||||
children: [
|
||||
_buildDetailHeader(),
|
||||
Expanded(
|
||||
child: YxLogDetailPage(
|
||||
log: _selectedLog!,
|
||||
theme: widget.theme,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
// 显示主列表页面
|
||||
content = Column(
|
||||
children: [
|
||||
_buildHeader(),
|
||||
_buildStatistics(),
|
||||
_buildSearchBar(),
|
||||
Expanded(child: _buildLogList()),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
if (_isFullScreen) {
|
||||
// 全屏模式 - 考虑状态栏高度
|
||||
return Material(
|
||||
color: Colors.black.withValues(alpha: 0.5),
|
||||
child: SafeArea(
|
||||
child: Container(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: double.infinity,
|
||||
color: widget.theme.backgroundColor,
|
||||
child: content,
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
// 对话框模式
|
||||
return Dialog(
|
||||
backgroundColor: Colors.transparent,
|
||||
child: Container(
|
||||
width: MediaQuery.of(context).size.width * 0.9,
|
||||
height: MediaQuery.of(context).size.height * 0.8,
|
||||
decoration: BoxDecoration(
|
||||
color: widget.theme.backgroundColor,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: content,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDetailHeader() {
|
||||
final actions = [
|
||||
IconButton(
|
||||
onPressed: () => _copyLogDetails(_selectedLog!),
|
||||
icon: const Icon(
|
||||
Icons.copy,
|
||||
color: Colors.white,
|
||||
),
|
||||
tooltip: '复制详情',
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
_isFullScreen = !_isFullScreen;
|
||||
setState(() {});
|
||||
},
|
||||
icon: Icon(
|
||||
_isFullScreen ? Icons.fullscreen_exit : Icons.fullscreen,
|
||||
color: Colors.white,
|
||||
),
|
||||
tooltip: _isFullScreen ? '退出全屏' : '全屏显示',
|
||||
),
|
||||
IconButton(
|
||||
onPressed: widget.onClose,
|
||||
icon: const Icon(
|
||||
Icons.close,
|
||||
color: Colors.white,
|
||||
),
|
||||
tooltip: '关闭',
|
||||
),
|
||||
];
|
||||
|
||||
return _isFullScreen
|
||||
? AppBar(
|
||||
title: Text('请求详情'),
|
||||
backgroundColor: widget.theme.primaryColor,
|
||||
foregroundColor: Colors.white,
|
||||
// excludeHeaderSemantics: false
|
||||
leading: IconButton(
|
||||
onPressed: _hideLogDetail,
|
||||
icon: const Icon(
|
||||
Icons.arrow_back,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
centerTitle: false,
|
||||
actions: actions,
|
||||
)
|
||||
: Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: widget.theme.primaryColor,
|
||||
borderRadius: _isFullScreen
|
||||
? BorderRadius.zero
|
||||
: const BorderRadius.only(
|
||||
topLeft: Radius.circular(16),
|
||||
topRight: Radius.circular(16),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: _hideLogDetail,
|
||||
icon: const Icon(
|
||||
Icons.arrow_back,
|
||||
color: Colors.white,
|
||||
),
|
||||
tooltip: '返回列表',
|
||||
),
|
||||
const Text(
|
||||
'请求详情',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
...actions
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeader() {
|
||||
final actions = [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_isFullScreen = !_isFullScreen;
|
||||
});
|
||||
},
|
||||
icon: Icon(
|
||||
_isFullScreen ? Icons.fullscreen_exit : Icons.fullscreen,
|
||||
color: Colors.white,
|
||||
),
|
||||
tooltip: _isFullScreen ? '退出全屏' : '全屏',
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
widget.controller.clearLogs();
|
||||
},
|
||||
icon: const Icon(Icons.clear_all, color: Colors.white),
|
||||
tooltip: '清空日志',
|
||||
),
|
||||
IconButton(
|
||||
onPressed: widget.onClose,
|
||||
icon: const Icon(Icons.close, color: Colors.white),
|
||||
),
|
||||
];
|
||||
return _isFullScreen
|
||||
? AppBar(
|
||||
title: Text('网络检查器'),
|
||||
backgroundColor: widget.theme.primaryColor,
|
||||
foregroundColor: Colors.white,
|
||||
centerTitle: false,
|
||||
actions: actions,
|
||||
)
|
||||
: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: widget.theme.primaryColor,
|
||||
borderRadius: _isFullScreen
|
||||
? BorderRadius.zero
|
||||
: const BorderRadius.only(
|
||||
topLeft: Radius.circular(16),
|
||||
topRight: Radius.circular(16),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.network_check, color: Colors.white, size: 24),
|
||||
const SizedBox(width: 8),
|
||||
const Text(
|
||||
'网络检查器',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
...actions
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatistics() {
|
||||
final stats = widget.controller.getStatistics();
|
||||
final totalRequests = stats['totalRequests'] as int;
|
||||
final errorRequests = stats['errorRequests'] as int;
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: widget.theme.backgroundColor,
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: widget.theme.secondaryTextColor.withValues(alpha: 0.2),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
// 简化统计 - 只显示关键信息
|
||||
Text(
|
||||
'总请求: $totalRequests',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: widget.theme.textColor,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
if (errorRequests > 0) ...[
|
||||
Icon(
|
||||
Icons.error_outline,
|
||||
size: 16,
|
||||
color: widget.theme.errorColor,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'错误: $errorRequests',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: widget.theme.errorColor,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
] else ...[
|
||||
Icon(
|
||||
Icons.check_circle_outline,
|
||||
size: 16,
|
||||
color: widget.theme.successColor,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'全部成功',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: widget.theme.successColor,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
const Spacer(),
|
||||
// 快速过滤按钮
|
||||
if (errorRequests > 0)
|
||||
TextButton.icon(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_showOnlyErrors = !_showOnlyErrors;
|
||||
});
|
||||
},
|
||||
icon: Icon(
|
||||
_showOnlyErrors ? Icons.clear : Icons.filter_list,
|
||||
size: 16,
|
||||
),
|
||||
label: Text(_showOnlyErrors ? '显示全部' : '仅错误'),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: widget.theme.errorColor,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
minimumSize: Size.zero,
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSearchBar() {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _searchController,
|
||||
style: TextStyle(color: widget.theme.textColor),
|
||||
decoration: InputDecoration(
|
||||
hintText: '搜索请求...',
|
||||
hintStyle: TextStyle(color: widget.theme.secondaryTextColor),
|
||||
prefixIcon: Icon(
|
||||
Icons.search,
|
||||
color: widget.theme.secondaryTextColor,
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: widget.theme.borderColor),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 8,
|
||||
),
|
||||
),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_searchKeyword = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLogList() {
|
||||
final logs = _filteredLogs;
|
||||
|
||||
if (logs.isEmpty) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.inbox, size: 64, color: widget.theme.secondaryTextColor),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'未找到网络请求',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: widget.theme.secondaryTextColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return ListView.builder(
|
||||
itemCount: logs.length,
|
||||
itemBuilder: (context, index) {
|
||||
final log = logs[index];
|
||||
return _buildLogItem(log);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLogItem(NetworkLogEntry log) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: widget.theme.cardColor,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
border: Border.all(
|
||||
color: log.status == NetworkRequestStatus.failed
|
||||
? widget.theme.errorColor.withValues(alpha: 0.3)
|
||||
: Colors.transparent,
|
||||
width: log.status == NetworkRequestStatus.failed ? 1 : 0,
|
||||
),
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
_showLogDetail(log);
|
||||
},
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
child: Row(
|
||||
children: [
|
||||
// URL和状态
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
log.displayUrl,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: widget.theme.textColor,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Row(
|
||||
children: [
|
||||
// 状态指示器
|
||||
Container(
|
||||
width: 6,
|
||||
height: 6,
|
||||
decoration: BoxDecoration(
|
||||
color: log.statusColor,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
|
||||
// 方法标签
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 6, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: _getMethodColor(log.method)
|
||||
.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Text(
|
||||
log.method,
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: _getMethodColor(log.method),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
|
||||
Text(
|
||||
'${log.statusCode ?? "?"} ${log.formattedDuration}',
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: log.statusColor,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
if (log.errorMessage != null) ...[
|
||||
const SizedBox(width: 8),
|
||||
Icon(
|
||||
Icons.error_outline,
|
||||
size: 12,
|
||||
color: widget.theme.errorColor,
|
||||
),
|
||||
],
|
||||
Spacer(),
|
||||
// 时间
|
||||
Text(
|
||||
log.formattedTime.contains(' ')
|
||||
? log.formattedTime.split(' ')[1]
|
||||
: log.formattedTime, // 只显示时间部分
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: widget.theme.secondaryTextColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Color _getMethodColor(String method) {
|
||||
switch (method.toUpperCase()) {
|
||||
case 'GET':
|
||||
return Colors.blue;
|
||||
case 'POST':
|
||||
return Colors.green;
|
||||
case 'PUT':
|
||||
return Colors.orange;
|
||||
case 'DELETE':
|
||||
return Colors.red;
|
||||
case 'PATCH':
|
||||
return Colors.purple;
|
||||
default:
|
||||
return widget.theme.secondaryTextColor;
|
||||
}
|
||||
}
|
||||
|
||||
void _copyLogDetails(NetworkLogEntry log) {
|
||||
// Note: This logic could be moved to util or DetailPage, but keeping here for now as callback
|
||||
final details = '''
|
||||
网络请求详情
|
||||
=======================
|
||||
|
|
@ -600,7 +121,7 @@ ${log.duration != null ? '- 结束时间: ${_formatDateTime(log.timestamp.add(lo
|
|||
} else if (requestData is Map) {
|
||||
try {
|
||||
return requestData.toString();
|
||||
} catch (e) {
|
||||
} on Object catch (_) {
|
||||
return requestData.toString();
|
||||
}
|
||||
} else {
|
||||
|
|
@ -609,7 +130,112 @@ ${log.duration != null ? '- 结束时间: ${_formatDateTime(log.timestamp.add(lo
|
|||
}
|
||||
|
||||
String _formatDateTime(DateTime dateTime) {
|
||||
return '${dateTime.year}-${dateTime.month.toString().padLeft(2, '0')}-${dateTime.day.toString().padLeft(2, '0')} '
|
||||
'${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}:${dateTime.second.toString().padLeft(2, '0')}';
|
||||
return '${dateTime.year}-${dateTime.month.toString().padLeft(2, '0')}-'
|
||||
'${dateTime.day.toString().padLeft(2, '0')} '
|
||||
'${dateTime.hour.toString().padLeft(2, '0')}:'
|
||||
'${dateTime.minute.toString().padLeft(2, '0')}:'
|
||||
'${dateTime.second.toString().padLeft(2, '0')}';
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListenableBuilder(
|
||||
listenable: widget.controller,
|
||||
builder: (context, child) {
|
||||
Widget content;
|
||||
|
||||
if (_showDetailPage && _selectedLog != null) {
|
||||
// 显示详情页面
|
||||
content = Column(
|
||||
children: [
|
||||
InspectorDetailHeader(
|
||||
theme: widget.theme,
|
||||
isFullScreen: _isFullScreen,
|
||||
onToggleFullScreen: _toggleFullScreen,
|
||||
onClose: widget.onClose,
|
||||
onBack: _hideLogDetail,
|
||||
onCopy: () => _copyLogDetails(_selectedLog!),
|
||||
),
|
||||
Expanded(
|
||||
child: YxLogDetailPage(
|
||||
log: _selectedLog!,
|
||||
theme: widget.theme,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
// 显示主列表页面
|
||||
final stats = widget.controller.getStatistics();
|
||||
content = Column(
|
||||
children: [
|
||||
InspectorHeader(
|
||||
theme: widget.theme,
|
||||
isFullScreen: _isFullScreen,
|
||||
onToggleFullScreen: _toggleFullScreen,
|
||||
onClearLogs: widget.controller.clearLogs,
|
||||
onClose: widget.onClose,
|
||||
),
|
||||
InspectorStatistics(
|
||||
theme: widget.theme,
|
||||
totalRequests: stats['totalRequests'] as int? ?? 0,
|
||||
errorRequests: stats['errorRequests'] as int? ?? 0,
|
||||
showOnlyErrors: _showOnlyErrors,
|
||||
onToggleFilter: () {
|
||||
setState(() {
|
||||
_showOnlyErrors = !_showOnlyErrors;
|
||||
});
|
||||
},
|
||||
),
|
||||
InspectorSearchBar(
|
||||
theme: widget.theme,
|
||||
controller: _searchController,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_searchKeyword = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
Expanded(
|
||||
child: InspectorLogList(
|
||||
logs: _filteredLogs,
|
||||
theme: widget.theme,
|
||||
onLogTap: _showLogDetail,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
if (_isFullScreen) {
|
||||
// 全屏模式 - 考虑状态栏高度
|
||||
return Material(
|
||||
color: Colors.black.withValues(alpha: 0.5),
|
||||
child: SafeArea(
|
||||
child: Container(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: double.infinity,
|
||||
color: widget.theme.backgroundColor,
|
||||
child: content,
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
// 对话框模式
|
||||
return Dialog(
|
||||
backgroundColor: Colors.transparent,
|
||||
child: Container(
|
||||
width: MediaQuery.of(context).size.width * 0.9,
|
||||
height: MediaQuery.of(context).size.height * 0.8,
|
||||
decoration: BoxDecoration(
|
||||
color: widget.theme.backgroundColor,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: content,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import '../models/network_log_entry.dart';
|
||||
import '../models/inspector_theme.dart';
|
||||
import 'package:yx_net_inspector/src/models/inspector_theme.dart';
|
||||
import 'package:yx_net_inspector/src/models/network_log_entry.dart';
|
||||
|
||||
/// 网络日志详情页面
|
||||
class YxLogDetailPage extends StatelessWidget {
|
||||
const YxLogDetailPage({required this.log, required this.theme, super.key});
|
||||
final NetworkLogEntry log;
|
||||
final YxNetInspectorTheme theme;
|
||||
|
||||
const YxLogDetailPage({super.key, required this.log, required this.theme});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
return ColoredBox(
|
||||
color: theme.backgroundColor,
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
|
|
@ -372,7 +372,7 @@ class YxLogDetailPage extends StatelessWidget {
|
|||
} else if (requestData is Map) {
|
||||
try {
|
||||
return const JsonEncoder.withIndent(' ').convert(requestData);
|
||||
} catch (e) {
|
||||
} on Object catch (_) {
|
||||
return requestData.toString();
|
||||
}
|
||||
} else {
|
||||
|
|
@ -381,7 +381,10 @@ class YxLogDetailPage extends StatelessWidget {
|
|||
}
|
||||
|
||||
String _formatDateTime(DateTime dateTime) {
|
||||
return '${dateTime.year}-${dateTime.month.toString().padLeft(2, '0')}-${dateTime.day.toString().padLeft(2, '0')} '
|
||||
'${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}:${dateTime.second.toString().padLeft(2, '0')}';
|
||||
return '${dateTime.year}-${dateTime.month.toString().padLeft(2, '0')}-'
|
||||
'${dateTime.day.toString().padLeft(2, '0')} '
|
||||
'${dateTime.hour.toString().padLeft(2, '0')}:'
|
||||
'${dateTime.minute.toString().padLeft(2, '0')}:'
|
||||
'${dateTime.second.toString().padLeft(2, '0')}';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,102 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:yx_net_inspector/src/models/inspector_theme.dart';
|
||||
|
||||
class InspectorDetailHeader extends StatelessWidget {
|
||||
const InspectorDetailHeader({
|
||||
required this.theme,
|
||||
required this.isFullScreen,
|
||||
required this.onToggleFullScreen,
|
||||
required this.onClose,
|
||||
required this.onBack,
|
||||
required this.onCopy,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final YxNetInspectorTheme theme;
|
||||
final bool isFullScreen;
|
||||
final VoidCallback onToggleFullScreen;
|
||||
final VoidCallback onClose;
|
||||
final VoidCallback onBack;
|
||||
final VoidCallback onCopy;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final actions = [
|
||||
IconButton(
|
||||
onPressed: onCopy,
|
||||
icon: const Icon(
|
||||
Icons.copy,
|
||||
color: Colors.white,
|
||||
),
|
||||
tooltip: '复制详情',
|
||||
),
|
||||
IconButton(
|
||||
onPressed: onToggleFullScreen,
|
||||
icon: Icon(
|
||||
isFullScreen ? Icons.fullscreen_exit : Icons.fullscreen,
|
||||
color: Colors.white,
|
||||
),
|
||||
tooltip: isFullScreen ? '退出全屏' : '全屏显示',
|
||||
),
|
||||
IconButton(
|
||||
onPressed: onClose,
|
||||
icon: const Icon(
|
||||
Icons.close,
|
||||
color: Colors.white,
|
||||
),
|
||||
tooltip: '关闭',
|
||||
),
|
||||
];
|
||||
|
||||
return isFullScreen
|
||||
? AppBar(
|
||||
title: const Text('请求详情'),
|
||||
backgroundColor: theme.primaryColor,
|
||||
foregroundColor: Colors.white,
|
||||
// excludeHeaderSemantics: false
|
||||
leading: IconButton(
|
||||
onPressed: onBack,
|
||||
icon: const Icon(
|
||||
Icons.arrow_back,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
centerTitle: false,
|
||||
actions: actions,
|
||||
)
|
||||
: Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.primaryColor,
|
||||
borderRadius: isFullScreen
|
||||
? BorderRadius.zero
|
||||
: const BorderRadius.only(
|
||||
topLeft: Radius.circular(16),
|
||||
topRight: Radius.circular(16),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: onBack,
|
||||
icon: const Icon(
|
||||
Icons.arrow_back,
|
||||
color: Colors.white,
|
||||
),
|
||||
tooltip: '返回列表',
|
||||
),
|
||||
const Text(
|
||||
'请求详情',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
...actions,
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:yx_net_inspector/src/models/inspector_theme.dart';
|
||||
|
||||
class InspectorHeader extends StatelessWidget {
|
||||
const InspectorHeader({
|
||||
required this.theme,
|
||||
required this.isFullScreen,
|
||||
required this.onToggleFullScreen,
|
||||
required this.onClearLogs,
|
||||
required this.onClose,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final YxNetInspectorTheme theme;
|
||||
final bool isFullScreen;
|
||||
final VoidCallback onToggleFullScreen;
|
||||
final VoidCallback onClearLogs;
|
||||
final VoidCallback onClose;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final actions = [
|
||||
IconButton(
|
||||
onPressed: onToggleFullScreen,
|
||||
icon: Icon(
|
||||
isFullScreen ? Icons.fullscreen_exit : Icons.fullscreen,
|
||||
color: Colors.white,
|
||||
),
|
||||
tooltip: isFullScreen ? '退出全屏' : '全屏',
|
||||
),
|
||||
IconButton(
|
||||
onPressed: onClearLogs,
|
||||
icon: const Icon(Icons.clear_all, color: Colors.white),
|
||||
tooltip: '清空日志',
|
||||
),
|
||||
IconButton(
|
||||
onPressed: onClose,
|
||||
icon: const Icon(Icons.close, color: Colors.white),
|
||||
),
|
||||
];
|
||||
|
||||
return isFullScreen
|
||||
? AppBar(
|
||||
title: const Text('网络检查器'),
|
||||
backgroundColor: theme.primaryColor,
|
||||
foregroundColor: Colors.white,
|
||||
centerTitle: false,
|
||||
actions: actions,
|
||||
)
|
||||
: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.primaryColor,
|
||||
borderRadius: isFullScreen
|
||||
? BorderRadius.zero
|
||||
: const BorderRadius.only(
|
||||
topLeft: Radius.circular(16),
|
||||
topRight: Radius.circular(16),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.network_check, color: Colors.white, size: 24),
|
||||
const SizedBox(width: 8),
|
||||
const Text(
|
||||
'网络检查器',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
...actions,
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,191 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:yx_net_inspector/src/models/inspector_theme.dart';
|
||||
import 'package:yx_net_inspector/src/models/network_log_entry.dart';
|
||||
|
||||
class InspectorLogList extends StatelessWidget {
|
||||
const InspectorLogList({
|
||||
required this.logs,
|
||||
required this.theme,
|
||||
required this.onLogTap,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final List<NetworkLogEntry> logs;
|
||||
final YxNetInspectorTheme theme;
|
||||
final ValueChanged<NetworkLogEntry> onLogTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (logs.isEmpty) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.inbox, size: 64, color: theme.secondaryTextColor),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'未找到网络请求',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: theme.secondaryTextColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return ListView.builder(
|
||||
itemCount: logs.length,
|
||||
itemBuilder: (context, index) {
|
||||
final log = logs[index];
|
||||
return InspectorLogItem(
|
||||
log: log,
|
||||
theme: theme,
|
||||
onTap: () => onLogTap(log),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class InspectorLogItem extends StatelessWidget {
|
||||
const InspectorLogItem({
|
||||
required this.log,
|
||||
required this.theme,
|
||||
required this.onTap,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final NetworkLogEntry log;
|
||||
final YxNetInspectorTheme theme;
|
||||
final VoidCallback onTap;
|
||||
|
||||
Color _getMethodColor(String method) {
|
||||
switch (method.toUpperCase()) {
|
||||
case 'GET':
|
||||
return Colors.blue;
|
||||
case 'POST':
|
||||
return Colors.green;
|
||||
case 'PUT':
|
||||
return Colors.orange;
|
||||
case 'DELETE':
|
||||
return Colors.red;
|
||||
case 'PATCH':
|
||||
return Colors.purple;
|
||||
default:
|
||||
return theme.secondaryTextColor;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.cardColor,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
border: Border.all(
|
||||
color: log.status == NetworkRequestStatus.failed
|
||||
? theme.errorColor.withValues(alpha: 0.3)
|
||||
: Colors.transparent,
|
||||
width: log.status == NetworkRequestStatus.failed ? 1 : 0,
|
||||
),
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
child: Row(
|
||||
children: [
|
||||
// URL和状态
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
log.displayUrl,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: theme.textColor,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Row(
|
||||
children: [
|
||||
// 状态指示器
|
||||
Container(
|
||||
width: 6,
|
||||
height: 6,
|
||||
decoration: BoxDecoration(
|
||||
color: log.statusColor,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
|
||||
// 方法标签
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 6,
|
||||
vertical: 2,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: _getMethodColor(log.method)
|
||||
.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Text(
|
||||
log.method,
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: _getMethodColor(log.method),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
|
||||
Text(
|
||||
'${log.statusCode ?? "?"} ${log.formattedDuration}',
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: log.statusColor,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
if (log.errorMessage != null) ...[
|
||||
const SizedBox(width: 8),
|
||||
Icon(
|
||||
Icons.error_outline,
|
||||
size: 12,
|
||||
color: theme.errorColor,
|
||||
),
|
||||
],
|
||||
const Spacer(),
|
||||
// 时间 - Fix: Use split safely or standard formatting
|
||||
Text(
|
||||
log.formattedTime.contains(' ')
|
||||
? log.formattedTime.split(' ')[1]
|
||||
: log.formattedTime,
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: theme.secondaryTextColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:yx_net_inspector/src/models/inspector_theme.dart';
|
||||
|
||||
class InspectorSearchBar extends StatelessWidget {
|
||||
const InspectorSearchBar({
|
||||
required this.theme,
|
||||
required this.controller,
|
||||
required this.onChanged,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final YxNetInspectorTheme theme;
|
||||
final TextEditingController controller;
|
||||
final ValueChanged<String> onChanged;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: controller,
|
||||
style: TextStyle(color: theme.textColor),
|
||||
decoration: InputDecoration(
|
||||
hintText: '搜索请求...',
|
||||
hintStyle: TextStyle(color: theme.secondaryTextColor),
|
||||
prefixIcon: Icon(
|
||||
Icons.search,
|
||||
color: theme.secondaryTextColor,
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: theme.borderColor),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 8,
|
||||
),
|
||||
),
|
||||
onChanged: onChanged,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:yx_net_inspector/src/models/inspector_theme.dart';
|
||||
|
||||
class InspectorStatistics extends StatelessWidget {
|
||||
const InspectorStatistics({
|
||||
required this.theme,
|
||||
required this.totalRequests,
|
||||
required this.errorRequests,
|
||||
required this.showOnlyErrors,
|
||||
required this.onToggleFilter,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final YxNetInspectorTheme theme;
|
||||
final int totalRequests;
|
||||
final int errorRequests;
|
||||
final bool showOnlyErrors;
|
||||
final VoidCallback onToggleFilter;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.backgroundColor,
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: theme.secondaryTextColor.withValues(alpha: 0.2),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
// 简化统计 - 只显示关键信息
|
||||
Text(
|
||||
'总请求: $totalRequests',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: theme.textColor,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
if (errorRequests > 0) ...[
|
||||
Icon(
|
||||
Icons.error_outline,
|
||||
size: 16,
|
||||
color: theme.errorColor,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'错误: $errorRequests',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: theme.errorColor,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
] else ...[
|
||||
Icon(
|
||||
Icons.check_circle_outline,
|
||||
size: 16,
|
||||
color: theme.successColor,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'全部成功',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: theme.successColor,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
const Spacer(),
|
||||
// 快速过滤按钮
|
||||
if (errorRequests > 0)
|
||||
TextButton.icon(
|
||||
onPressed: onToggleFilter,
|
||||
icon: Icon(
|
||||
showOnlyErrors ? Icons.clear : Icons.filter_list,
|
||||
size: 16,
|
||||
),
|
||||
label: Text(showOnlyErrors ? '显示全部' : '仅错误'),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: theme.errorColor,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
minimumSize: Size.zero,
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +1,15 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'controller/yx_net_inspector_controller.dart';
|
||||
import 'models/inspector_config.dart';
|
||||
import 'models/inspector_theme.dart';
|
||||
import 'widgets/floating_ball.dart';
|
||||
import 'package:yx_net_inspector/src/controller/yx_net_inspector_controller.dart';
|
||||
import 'package:yx_net_inspector/src/models/inspector_config.dart';
|
||||
import 'package:yx_net_inspector/src/models/inspector_theme.dart';
|
||||
import 'package:yx_net_inspector/src/widgets/floating_ball.dart';
|
||||
import 'package:yx_net_inspector/src/widgets/inspector_panel.dart';
|
||||
|
||||
/// 包装你的应用并提供网络检查功能的主要组件
|
||||
class YxNetInspector extends StatefulWidget {
|
||||
/// 你的应用组件
|
||||
final Widget child;
|
||||
|
||||
/// 检查器配置
|
||||
final YxNetInspectorConfig config;
|
||||
|
||||
/// 检查器主题
|
||||
final YxNetInspectorTheme theme;
|
||||
|
||||
const YxNetInspector({
|
||||
super.key,
|
||||
required this.child,
|
||||
required this.child, super.key,
|
||||
this.config = const YxNetInspectorConfig(),
|
||||
this.theme = const YxNetInspectorTheme(),
|
||||
});
|
||||
|
|
@ -51,6 +42,14 @@ class YxNetInspector extends StatefulWidget {
|
|||
child: child,
|
||||
);
|
||||
}
|
||||
/// 你的应用组件
|
||||
final Widget child;
|
||||
|
||||
/// 检查器配置
|
||||
final YxNetInspectorConfig config;
|
||||
|
||||
/// 检查器主题
|
||||
final YxNetInspectorTheme theme;
|
||||
|
||||
@override
|
||||
State<YxNetInspector> createState() => _YxNetInspectorState();
|
||||
|
|
@ -97,6 +96,44 @@ class _YxNetInspectorState extends State<YxNetInspector> {
|
|||
);
|
||||
},
|
||||
),
|
||||
|
||||
// 检查器面板覆盖层
|
||||
ListenableBuilder(
|
||||
listenable: _controller,
|
||||
builder: (context, child) {
|
||||
if (!_controller.isPanelVisible) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return Overlay(
|
||||
initialEntries: [
|
||||
OverlayEntry(
|
||||
builder: (context) => Stack(
|
||||
children: [
|
||||
// 背景遮罩
|
||||
GestureDetector(
|
||||
onTap: _controller.hidePanel,
|
||||
child: Container(
|
||||
color: Colors.black54,
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
),
|
||||
),
|
||||
// 检查器面板
|
||||
Center(
|
||||
child: YxInspectorPanel(
|
||||
theme: widget.theme,
|
||||
controller: _controller,
|
||||
onClose: _controller.hidePanel,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
library yx_net_inspector;
|
||||
|
||||
// 核心导出
|
||||
export 'src/yx_net_inspector_app.dart' show YxNetInspector;
|
||||
export 'src/controller/yx_net_inspector_controller.dart';
|
||||
export 'src/models/network_log_entry.dart';
|
||||
export 'src/models/inspector_config.dart';
|
||||
export 'src/models/inspector_theme.dart';
|
||||
export 'src/models/network_log_entry.dart';
|
||||
export 'src/widgets/floating_ball_config.dart';
|
||||
export 'src/yx_net_inspector_app.dart' show YxNetInspector;
|
||||
|
||||
// Dio 拦截器需要单独导入:
|
||||
// import 'package:yx_net_inspector/src/interceptors/dio_interceptor.dart';
|
||||
|
|
|
|||
30
pubspec.lock
30
pubspec.lock
|
|
@ -63,26 +63,26 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
|
||||
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "10.0.9"
|
||||
version: "11.0.2"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
|
||||
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.0.9"
|
||||
version: "3.0.10"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_testing
|
||||
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
|
||||
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
version: "3.0.2"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -164,18 +164,26 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
|
||||
sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.7.4"
|
||||
version: "0.7.6"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_math
|
||||
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
|
||||
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
version: "2.2.0"
|
||||
very_good_analysis:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: very_good_analysis
|
||||
sha256: "62d2b86d183fb81b2edc22913d9f155d26eb5cf3855173adb1f59fac85035c63"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "7.0.0"
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -185,5 +193,5 @@ packages:
|
|||
source: hosted
|
||||
version: "15.0.0"
|
||||
sdks:
|
||||
dart: ">=3.7.0-0 <4.0.0"
|
||||
dart: ">=3.8.0-0 <4.0.0"
|
||||
flutter: ">=3.18.0-18.0.pre.54"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
name: yx_net_inspector
|
||||
description: A powerful network inspector with floating debug ball for Flutter apps. Monitor HTTP requests, responses, and debug network issues in real-time.
|
||||
version: 1.0.3
|
||||
version: 1.0.4
|
||||
homepage: https://github.com/your-username/yx_net_inspector
|
||||
|
||||
environment:
|
||||
|
|
@ -15,4 +15,6 @@ dev_dependencies:
|
|||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
very_good_analysis: ^7.0.0
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:yx_net_inspector/yx_net_inspector.dart';
|
||||
import 'package:yx_net_inspector/src/widgets/floating_ball.dart';
|
||||
import 'package:yx_net_inspector/yx_net_inspector.dart';
|
||||
|
||||
void main() {
|
||||
group('YX Net Inspector 集成测试', () {
|
||||
|
|
@ -9,12 +9,6 @@ void main() {
|
|||
// 创建测试应用
|
||||
await tester.pumpWidget(
|
||||
YxNetInspector(
|
||||
config: const YxNetInspectorConfig(
|
||||
showFloatingBall: true,
|
||||
ballSize: 60.0,
|
||||
showInDebugMode: true,
|
||||
),
|
||||
theme: const YxNetInspectorTheme(),
|
||||
child: MaterialApp(
|
||||
home: Scaffold(
|
||||
appBar: AppBar(title: const Text('测试应用')),
|
||||
|
|
@ -52,8 +46,8 @@ void main() {
|
|||
statusCode: 200,
|
||||
responseData: {
|
||||
'users': [
|
||||
{'id': 1, 'name': 'John'}
|
||||
]
|
||||
{'id': 1, 'name': 'John'},
|
||||
],
|
||||
},
|
||||
duration: const Duration(milliseconds: 300),
|
||||
);
|
||||
|
|
@ -146,10 +140,6 @@ void main() {
|
|||
testWidgets('悬浮球拖拽和位置测试', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
YxNetInspector(
|
||||
config: const YxNetInspectorConfig(
|
||||
showFloatingBall: true,
|
||||
draggable: true,
|
||||
),
|
||||
child: MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Container(),
|
||||
|
|
@ -179,13 +169,13 @@ void main() {
|
|||
|
||||
testWidgets('配置禁用时不显示悬浮球', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
YxNetInspector(
|
||||
config: const YxNetInspectorConfig(
|
||||
const YxNetInspector(
|
||||
config: YxNetInspectorConfig(
|
||||
showFloatingBall: false,
|
||||
),
|
||||
child: MaterialApp(
|
||||
home: Scaffold(
|
||||
body: const Text('Test App'),
|
||||
body: Text('Test App'),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -203,7 +193,6 @@ void main() {
|
|||
testWidgets('主题配置测试', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
YxNetInspector(
|
||||
config: const YxNetInspectorConfig(),
|
||||
theme: const YxNetInspectorTheme(
|
||||
primaryColor: Colors.purple,
|
||||
backgroundColor: Colors.black,
|
||||
|
|
@ -222,7 +211,7 @@ void main() {
|
|||
// 添加一些日志
|
||||
final controller = YxNetInspectorController.instance;
|
||||
controller.logRequest(
|
||||
id: 'test', method: 'GET', url: 'https://example.com');
|
||||
id: 'test', method: 'GET', url: 'https://example.com',);
|
||||
|
||||
await tester.pump();
|
||||
|
||||
|
|
@ -253,7 +242,7 @@ void main() {
|
|||
controller.clearLogs();
|
||||
|
||||
// 添加大量日志
|
||||
for (int i = 0; i < 150; i++) {
|
||||
for (var i = 0; i < 150; i++) {
|
||||
controller.logRequest(
|
||||
id: 'test-$i',
|
||||
method: 'GET',
|
||||
|
|
@ -302,7 +291,7 @@ void main() {
|
|||
controller.clearLogs();
|
||||
|
||||
// 添加超过限制的日志
|
||||
for (int i = 0; i < 20; i++) {
|
||||
for (var i = 0; i < 20; i++) {
|
||||
controller.logRequest(
|
||||
id: 'memory-test-$i',
|
||||
method: 'GET',
|
||||
|
|
|
|||
|
|
@ -1,18 +1,16 @@
|
|||
// 测试入口文件 - 运行所有测试
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
// 集成测试
|
||||
import 'integration/full_workflow_test.dart' as integration_test;
|
||||
import 'unit/inspector_config_test.dart' as config_test;
|
||||
// 单元测试
|
||||
import 'unit/network_log_entry_test.dart' as network_log_entry_test;
|
||||
import 'unit/yx_net_inspector_controller_test.dart' as controller_test;
|
||||
import 'unit/inspector_config_test.dart' as config_test;
|
||||
|
||||
// Widget测试
|
||||
import 'widget/floating_ball_test.dart' as floating_ball_test;
|
||||
import 'widget/inspector_panel_test.dart' as inspector_panel_test;
|
||||
|
||||
// 集成测试
|
||||
import 'integration/full_workflow_test.dart' as integration_test;
|
||||
|
||||
void main() {
|
||||
group('YX Net Inspector 完整测试套件', () {
|
||||
group('单元测试', () {
|
||||
|
|
@ -26,8 +24,6 @@ void main() {
|
|||
inspector_panel_test.main();
|
||||
});
|
||||
|
||||
group('集成测试', () {
|
||||
integration_test.main();
|
||||
});
|
||||
group('集成测试', integration_test.main);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ void main() {
|
|||
test('应该允许自定义配置', () {
|
||||
const config = YxNetInspectorConfig(
|
||||
showFloatingBall: false,
|
||||
ballSize: 80.0,
|
||||
ballSize: 80,
|
||||
ballColor: Colors.red,
|
||||
showInDebugMode: false,
|
||||
showInReleaseMode: true,
|
||||
|
|
@ -51,8 +51,7 @@ void main() {
|
|||
debugDefaultTargetPlatformOverride = TargetPlatform.android;
|
||||
|
||||
const configEnabledInDebug = YxNetInspectorConfig(
|
||||
showInDebugMode: true,
|
||||
showInReleaseMode: false,
|
||||
|
||||
);
|
||||
|
||||
const configDisabledInDebug = YxNetInspectorConfig(
|
||||
|
|
@ -75,27 +74,24 @@ void main() {
|
|||
|
||||
test('copyWith 应该正确创建副本', () {
|
||||
const originalConfig = YxNetInspectorConfig(
|
||||
showFloatingBall: true,
|
||||
ballSize: 60.0,
|
||||
maxLogs: 1000,
|
||||
|
||||
);
|
||||
|
||||
final copiedConfig = originalConfig.copyWith(
|
||||
showFloatingBall: false,
|
||||
ballSize: 80.0,
|
||||
ballSize: 80,
|
||||
);
|
||||
|
||||
expect(copiedConfig.showFloatingBall, isFalse);
|
||||
expect(copiedConfig.ballSize, equals(80.0));
|
||||
expect(copiedConfig.maxLogs, equals(1000)); // 未更改的值应该保持原样
|
||||
expect(
|
||||
copiedConfig.showInDebugMode, equals(originalConfig.showInDebugMode));
|
||||
copiedConfig.showInDebugMode, equals(originalConfig.showInDebugMode),);
|
||||
});
|
||||
|
||||
test('toString 应该返回有用的字符串表示', () {
|
||||
const config = YxNetInspectorConfig(
|
||||
showFloatingBall: true,
|
||||
ballSize: 70.0,
|
||||
ballSize: 70,
|
||||
);
|
||||
|
||||
final string = config.toString();
|
||||
|
|
@ -106,21 +102,15 @@ void main() {
|
|||
|
||||
test('相等性比较应该正确工作', () {
|
||||
const config1 = YxNetInspectorConfig(
|
||||
showFloatingBall: true,
|
||||
ballSize: 60.0,
|
||||
maxLogs: 1000,
|
||||
|
||||
);
|
||||
|
||||
const config2 = YxNetInspectorConfig(
|
||||
showFloatingBall: true,
|
||||
ballSize: 60.0,
|
||||
maxLogs: 1000,
|
||||
|
||||
);
|
||||
|
||||
const config3 = YxNetInspectorConfig(
|
||||
showFloatingBall: false, // 不同的值
|
||||
ballSize: 60.0,
|
||||
maxLogs: 1000,
|
||||
);
|
||||
|
||||
expect(config1, equals(config2));
|
||||
|
|
@ -130,9 +120,8 @@ void main() {
|
|||
});
|
||||
|
||||
test('空值参数应该正确处理', () {
|
||||
final config = YxNetInspectorConfig().copyWith(
|
||||
ballColor: null,
|
||||
initialPosition: null,
|
||||
final config = const YxNetInspectorConfig().copyWith(
|
||||
|
||||
);
|
||||
|
||||
expect(config.ballColor, isNull);
|
||||
|
|
@ -141,7 +130,7 @@ void main() {
|
|||
|
||||
test('边界值应该正确处理', () {
|
||||
const config = YxNetInspectorConfig(
|
||||
ballSize: 0.0, // 最小值
|
||||
ballSize: 0, // 最小值
|
||||
maxLogs: 1, // 最小日志数
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:yx_net_inspector/src/models/network_log_entry.dart';
|
||||
|
||||
void main() {
|
||||
|
|
@ -12,12 +12,11 @@ void main() {
|
|||
id: 'test-1',
|
||||
method: 'GET',
|
||||
url: 'https://api.example.com/users?page=1&limit=20',
|
||||
headers: {'Authorization': 'Bearer token'},
|
||||
requestData: null,
|
||||
queryParameters: {'page': '1', 'limit': '20'},
|
||||
headers: const {'Authorization': 'Bearer token'},
|
||||
queryParameters: const {'page': '1', 'limit': '20'},
|
||||
statusCode: 200,
|
||||
responseData: {'users': []},
|
||||
timestamp: DateTime(2024, 1, 1, 12, 0, 0),
|
||||
responseData: const {'users': []},
|
||||
timestamp: DateTime(2024, 1, 1, 12),
|
||||
duration: const Duration(milliseconds: 500),
|
||||
isSuccess: true,
|
||||
);
|
||||
|
|
@ -26,11 +25,11 @@ void main() {
|
|||
id: 'test-2',
|
||||
method: 'POST',
|
||||
url: 'https://api.example.com/users',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
requestData: {'name': 'Test User'},
|
||||
headers: const {'Content-Type': 'application/json'},
|
||||
requestData: const {'name': 'Test User'},
|
||||
statusCode: 404,
|
||||
errorMessage: '用户不存在',
|
||||
timestamp: DateTime(2024, 1, 1, 12, 5, 0),
|
||||
timestamp: DateTime(2024, 1, 1, 12, 5),
|
||||
duration: const Duration(milliseconds: 1500),
|
||||
isSuccess: false,
|
||||
);
|
||||
|
|
@ -65,7 +64,7 @@ void main() {
|
|||
test('requestSize 应该计算正确的请求大小', () {
|
||||
expect(successfulEntry.requestSize, greaterThan(0));
|
||||
expect(errorEntry.requestSize,
|
||||
greaterThanOrEqualTo(successfulEntry.requestSize));
|
||||
greaterThanOrEqualTo(successfulEntry.requestSize),);
|
||||
});
|
||||
|
||||
test('responseSize 应该计算正确的响应大小', () {
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ void main() {
|
|||
});
|
||||
|
||||
test('初始化后应该有正确的默认值', () {
|
||||
final config = YxNetInspectorConfig();
|
||||
const config = YxNetInspectorConfig();
|
||||
controller.initialize(config);
|
||||
|
||||
expect(controller.logs, isEmpty);
|
||||
|
|
@ -33,7 +33,7 @@ void main() {
|
|||
});
|
||||
|
||||
test('logRequest 应该正确添加请求日志', () {
|
||||
controller.initialize(YxNetInspectorConfig());
|
||||
controller.initialize(const YxNetInspectorConfig());
|
||||
|
||||
controller.logRequest(
|
||||
id: 'test-1',
|
||||
|
|
@ -50,7 +50,7 @@ void main() {
|
|||
});
|
||||
|
||||
test('logResponse 应该正确更新请求日志', () {
|
||||
controller.initialize(YxNetInspectorConfig());
|
||||
controller.initialize(const YxNetInspectorConfig());
|
||||
|
||||
// 先添加请求
|
||||
controller.logRequest(
|
||||
|
|
@ -72,11 +72,11 @@ void main() {
|
|||
expect(controller.logs.first.statusCode, equals(200));
|
||||
expect(controller.logs.first.isSuccess, isTrue);
|
||||
expect(controller.logs.first.duration,
|
||||
equals(const Duration(milliseconds: 500)));
|
||||
equals(const Duration(milliseconds: 500)),);
|
||||
});
|
||||
|
||||
test('logError 应该正确处理错误日志', () {
|
||||
controller.initialize(YxNetInspectorConfig());
|
||||
controller.initialize(const YxNetInspectorConfig());
|
||||
|
||||
// 先添加请求
|
||||
controller.logRequest(
|
||||
|
|
@ -101,11 +101,11 @@ void main() {
|
|||
});
|
||||
|
||||
test('clearLogs 应该清空所有日志和统计', () {
|
||||
controller.initialize(YxNetInspectorConfig());
|
||||
controller.initialize(const YxNetInspectorConfig());
|
||||
|
||||
// 添加一些日志
|
||||
controller.logRequest(
|
||||
id: 'test-1', method: 'GET', url: 'https://example.com');
|
||||
id: 'test-1', method: 'GET', url: 'https://example.com',);
|
||||
controller.logResponse(id: 'test-1', statusCode: 200);
|
||||
|
||||
expect(controller.logs, isNotEmpty);
|
||||
|
|
@ -122,7 +122,7 @@ void main() {
|
|||
});
|
||||
|
||||
test('悬浮球显示状态应该正确切换', () {
|
||||
controller.initialize(YxNetInspectorConfig(showFloatingBall: true));
|
||||
controller.initialize(const YxNetInspectorConfig());
|
||||
|
||||
expect(controller.showFloatingBall, isTrue);
|
||||
|
||||
|
|
@ -137,23 +137,23 @@ void main() {
|
|||
});
|
||||
|
||||
test('getStatistics 应该返回正确的统计信息', () {
|
||||
controller.initialize(YxNetInspectorConfig());
|
||||
controller.initialize(const YxNetInspectorConfig());
|
||||
|
||||
// 添加成功请求
|
||||
controller.logRequest(
|
||||
id: 'success', method: 'GET', url: 'https://example.com');
|
||||
id: 'success', method: 'GET', url: 'https://example.com',);
|
||||
controller.logResponse(
|
||||
id: 'success',
|
||||
statusCode: 200,
|
||||
duration: const Duration(milliseconds: 300));
|
||||
duration: const Duration(milliseconds: 300),);
|
||||
|
||||
// 添加失败请求
|
||||
controller.logRequest(
|
||||
id: 'error', method: 'POST', url: 'https://example.com');
|
||||
id: 'error', method: 'POST', url: 'https://example.com',);
|
||||
controller.logError(
|
||||
id: 'error',
|
||||
error: '错误',
|
||||
duration: const Duration(milliseconds: 500));
|
||||
duration: const Duration(milliseconds: 500),);
|
||||
|
||||
final stats = controller.getStatistics();
|
||||
|
||||
|
|
@ -165,12 +165,12 @@ void main() {
|
|||
});
|
||||
|
||||
test('getRecentLogs 应该返回指定数量的最近日志', () {
|
||||
controller.initialize(YxNetInspectorConfig());
|
||||
controller.initialize(const YxNetInspectorConfig());
|
||||
|
||||
// 添加多个日志
|
||||
for (int i = 0; i < 10; i++) {
|
||||
for (var i = 0; i < 10; i++) {
|
||||
controller.logRequest(
|
||||
id: 'test-$i', method: 'GET', url: 'https://example.com/$i');
|
||||
id: 'test-$i', method: 'GET', url: 'https://example.com/$i',);
|
||||
}
|
||||
|
||||
final recentLogs = controller.getRecentLogs(count: 5);
|
||||
|
|
@ -179,15 +179,15 @@ void main() {
|
|||
});
|
||||
|
||||
test('getLogsByStatus 应该正确过滤日志', () {
|
||||
controller.initialize(YxNetInspectorConfig());
|
||||
controller.initialize(const YxNetInspectorConfig());
|
||||
|
||||
// 添加成功和失败的请求
|
||||
controller.logRequest(
|
||||
id: 'success', method: 'GET', url: 'https://example.com');
|
||||
id: 'success', method: 'GET', url: 'https://example.com',);
|
||||
controller.logResponse(id: 'success', statusCode: 200);
|
||||
|
||||
controller.logRequest(
|
||||
id: 'error', method: 'POST', url: 'https://example.com');
|
||||
id: 'error', method: 'POST', url: 'https://example.com',);
|
||||
controller.logError(id: 'error', error: '错误');
|
||||
|
||||
final successLogs = controller.getLogsByStatus(isSuccess: true);
|
||||
|
|
@ -200,16 +200,16 @@ void main() {
|
|||
});
|
||||
|
||||
test('searchLogs 应该正确搜索日志', () {
|
||||
controller.initialize(YxNetInspectorConfig());
|
||||
controller.initialize(const YxNetInspectorConfig());
|
||||
|
||||
controller.logRequest(
|
||||
id: 'user-req', method: 'GET', url: 'https://api.example.com/users');
|
||||
id: 'user-req', method: 'GET', url: 'https://api.example.com/users',);
|
||||
controller.logRequest(
|
||||
id: 'post-req', method: 'POST', url: 'https://api.example.com/posts');
|
||||
id: 'post-req', method: 'POST', url: 'https://api.example.com/posts',);
|
||||
controller.logRequest(
|
||||
id: 'error-req',
|
||||
method: 'DELETE',
|
||||
url: 'https://api.example.com/user/1');
|
||||
url: 'https://api.example.com/user/1',);
|
||||
controller.logError(id: 'error-req', error: '用户不存在');
|
||||
|
||||
final userLogs = controller.searchLogs('user');
|
||||
|
|
@ -222,12 +222,12 @@ void main() {
|
|||
});
|
||||
|
||||
test('日志数量应该受到maxLogs限制', () {
|
||||
controller.initialize(YxNetInspectorConfig(maxLogs: 5));
|
||||
controller.initialize(const YxNetInspectorConfig(maxLogs: 5));
|
||||
|
||||
// 添加超过限制的日志
|
||||
for (int i = 0; i < 10; i++) {
|
||||
for (var i = 0; i < 10; i++) {
|
||||
controller.logRequest(
|
||||
id: 'test-$i', method: 'GET', url: 'https://example.com/$i');
|
||||
id: 'test-$i', method: 'GET', url: 'https://example.com/$i',);
|
||||
}
|
||||
|
||||
expect(controller.logs.length, equals(5));
|
||||
|
|
@ -236,13 +236,12 @@ void main() {
|
|||
});
|
||||
|
||||
test('配置未启用时不应该记录日志', () {
|
||||
controller.initialize(YxNetInspectorConfig(
|
||||
controller.initialize(const YxNetInspectorConfig(
|
||||
showInDebugMode: false,
|
||||
showInReleaseMode: false,
|
||||
));
|
||||
),);
|
||||
|
||||
controller.logRequest(
|
||||
id: 'test', method: 'GET', url: 'https://example.com');
|
||||
id: 'test', method: 'GET', url: 'https://example.com',);
|
||||
|
||||
expect(controller.logs, isEmpty);
|
||||
expect(controller.requestCount, equals(0));
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:yx_net_inspector/src/widgets/floating_ball.dart';
|
||||
import 'package:yx_net_inspector/src/controller/yx_net_inspector_controller.dart';
|
||||
import 'package:yx_net_inspector/src/models/inspector_config.dart';
|
||||
import 'package:yx_net_inspector/src/models/inspector_theme.dart';
|
||||
import 'package:yx_net_inspector/src/widgets/floating_ball.dart';
|
||||
|
||||
void main() {
|
||||
group('YxFloatingBall Widget Tests', () {
|
||||
|
|
@ -61,9 +61,9 @@ void main() {
|
|||
testWidgets('应该显示请求数量徽章', (WidgetTester tester) async {
|
||||
// 添加一些请求
|
||||
controller.logRequest(
|
||||
id: 'test-1', method: 'GET', url: 'https://example.com');
|
||||
id: 'test-1', method: 'GET', url: 'https://example.com',);
|
||||
controller.logRequest(
|
||||
id: 'test-2', method: 'POST', url: 'https://example.com');
|
||||
id: 'test-2', method: 'POST', url: 'https://example.com',);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
|
|
@ -86,7 +86,7 @@ void main() {
|
|||
testWidgets('应该显示错误数量徽章', (WidgetTester tester) async {
|
||||
// 添加请求和错误
|
||||
controller.logRequest(
|
||||
id: 'test-1', method: 'GET', url: 'https://example.com');
|
||||
id: 'test-1', method: 'GET', url: 'https://example.com',);
|
||||
controller.logError(id: 'test-1', error: '网络错误');
|
||||
|
||||
await tester.pumpWidget(
|
||||
|
|
@ -181,7 +181,7 @@ void main() {
|
|||
|
||||
// 添加一些请求
|
||||
controller.logRequest(
|
||||
id: 'test-1', method: 'GET', url: 'https://example.com');
|
||||
id: 'test-1', method: 'GET', url: 'https://example.com',);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
|
|
@ -221,7 +221,7 @@ void main() {
|
|||
});
|
||||
|
||||
testWidgets('自定义大小应该正确应用', (WidgetTester tester) async {
|
||||
final customSizeConfig = config.copyWith(ballSize: 80.0);
|
||||
final customSizeConfig = config.copyWith(ballSize: 80);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:yx_net_inspector/src/widgets/inspector_panel.dart';
|
||||
import 'package:yx_net_inspector/src/controller/yx_net_inspector_controller.dart';
|
||||
import 'package:yx_net_inspector/src/models/inspector_config.dart';
|
||||
import 'package:yx_net_inspector/src/models/inspector_theme.dart';
|
||||
import 'package:yx_net_inspector/src/widgets/inspector_panel.dart';
|
||||
|
||||
void main() {
|
||||
group('YxInspectorPanel Widget Tests', () {
|
||||
|
|
@ -43,11 +43,11 @@ void main() {
|
|||
testWidgets('应该显示统计信息', (WidgetTester tester) async {
|
||||
// 添加一些测试数据
|
||||
controller.logRequest(
|
||||
id: 'test-1', method: 'GET', url: 'https://example.com');
|
||||
id: 'test-1', method: 'GET', url: 'https://example.com',);
|
||||
controller.logResponse(id: 'test-1', statusCode: 200);
|
||||
|
||||
controller.logRequest(
|
||||
id: 'test-2', method: 'POST', url: 'https://example.com');
|
||||
id: 'test-2', method: 'POST', url: 'https://example.com',);
|
||||
controller.logError(id: 'test-2', error: '网络错误');
|
||||
|
||||
await tester.pumpWidget(
|
||||
|
|
@ -96,9 +96,9 @@ void main() {
|
|||
testWidgets('搜索功能应该正常工作', (WidgetTester tester) async {
|
||||
// 添加测试数据
|
||||
controller.logRequest(
|
||||
id: 'user-1', method: 'GET', url: 'https://api.example.com/users');
|
||||
id: 'user-1', method: 'GET', url: 'https://api.example.com/users',);
|
||||
controller.logRequest(
|
||||
id: 'post-1', method: 'POST', url: 'https://api.example.com/posts');
|
||||
id: 'post-1', method: 'POST', url: 'https://api.example.com/posts',);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
|
|
@ -125,11 +125,11 @@ void main() {
|
|||
testWidgets('错误过滤器应该正常工作', (WidgetTester tester) async {
|
||||
// 添加成功和失败的请求
|
||||
controller.logRequest(
|
||||
id: 'success', method: 'GET', url: 'https://example.com');
|
||||
id: 'success', method: 'GET', url: 'https://example.com',);
|
||||
controller.logResponse(id: 'success', statusCode: 200);
|
||||
|
||||
controller.logRequest(
|
||||
id: 'error', method: 'POST', url: 'https://example.com');
|
||||
id: 'error', method: 'POST', url: 'https://example.com',);
|
||||
controller.logError(id: 'error', error: '网络错误');
|
||||
|
||||
await tester.pumpWidget(
|
||||
|
|
@ -158,7 +158,7 @@ void main() {
|
|||
testWidgets('清空日志按钮应该正常工作', (WidgetTester tester) async {
|
||||
// 添加一些日志
|
||||
controller.logRequest(
|
||||
id: 'test', method: 'GET', url: 'https://example.com');
|
||||
id: 'test', method: 'GET', url: 'https://example.com',);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
|
|
@ -204,7 +204,7 @@ void main() {
|
|||
});
|
||||
|
||||
testWidgets('关闭按钮应该调用回调', (WidgetTester tester) async {
|
||||
bool closeCalled = false;
|
||||
var closeCalled = false;
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
|
|
|
|||
Loading…
Reference in New Issue