修复悬浮球上下文问题并改进详情页显示
- 修改example应用结构:将YxNetInspector放到MaterialApp内部 - 修复Navigator和Overlay上下文问题,悬浮球现在能正常点击 - 改进网络检查器面板:详情页现在在检查器内部显示而非主应用导航 - 添加内部页面导航状态管理和返回按钮 - 优化用户体验:保持检查器操作在独立的上下文中
This commit is contained in:
parent
31b33ce1e4
commit
10a251dcf9
|
|
@ -0,0 +1,73 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:yx_net_inspector/yx_net_inspector.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
runApp(const MyDebugApp());
|
||||||
|
}
|
||||||
|
|
||||||
|
class MyDebugApp extends StatelessWidget {
|
||||||
|
const MyDebugApp({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return MaterialApp(
|
||||||
|
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('调试测试'),
|
||||||
|
),
|
||||||
|
body: Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Text('点击悬浮球测试'),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
// 模拟一个网络请求来测试
|
||||||
|
final requestId =
|
||||||
|
'test_${DateTime.now().millisecondsSinceEpoch}';
|
||||||
|
YxNetInspectorController.instance.logRequest(
|
||||||
|
id: requestId,
|
||||||
|
method: 'GET',
|
||||||
|
url: 'https://jsonplaceholder.typicode.com/posts/1',
|
||||||
|
headers: const {'Content-Type': 'application/json'},
|
||||||
|
);
|
||||||
|
|
||||||
|
Future.delayed(const Duration(seconds: 1), () {
|
||||||
|
YxNetInspectorController.instance.logResponse(
|
||||||
|
id: requestId,
|
||||||
|
statusCode: 200,
|
||||||
|
responseData: const {
|
||||||
|
'title': 'Test Post',
|
||||||
|
'body': 'Test content'
|
||||||
|
},
|
||||||
|
duration: const Duration(seconds: 1),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: const Text('发送测试请求'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -13,50 +13,50 @@ class MyApp extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return YxNetInspector(
|
return MaterialApp(
|
||||||
config: const YxNetInspectorConfig(
|
title: 'YX 网络检查器演示',
|
||||||
showFloatingBall: true,
|
theme: ThemeData(
|
||||||
ballSize: 70.0,
|
primarySwatch: Colors.blue,
|
||||||
ballColor: Colors.blue,
|
visualDensity: VisualDensity.adaptivePlatformDensity,
|
||||||
showInDebugMode: true,
|
appBarTheme: const AppBarTheme(
|
||||||
showInReleaseMode: false,
|
backgroundColor: Colors.blue,
|
||||||
maxLogs: 1000,
|
foregroundColor: Colors.white,
|
||||||
draggable: true,
|
elevation: 2,
|
||||||
showBadge: true,
|
),
|
||||||
autoHide: false,
|
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||||
),
|
style: ElevatedButton.styleFrom(
|
||||||
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: MaterialApp(
|
|
||||||
title: 'YX 网络检查器演示',
|
|
||||||
theme: ThemeData(
|
|
||||||
primarySwatch: Colors.blue,
|
|
||||||
visualDensity: VisualDensity.adaptivePlatformDensity,
|
|
||||||
appBarTheme: const AppBarTheme(
|
|
||||||
backgroundColor: Colors.blue,
|
backgroundColor: Colors.blue,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
elevation: 2,
|
shape: RoundedRectangleBorder(
|
||||||
),
|
borderRadius: BorderRadius.circular(8),
|
||||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: Colors.blue,
|
|
||||||
foregroundColor: Colors.white,
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
home: const MainPage(),
|
|
||||||
debugShowCheckedModeBanner: false,
|
|
||||||
),
|
),
|
||||||
|
home: YxNetInspector(
|
||||||
|
config: const YxNetInspectorConfig(
|
||||||
|
showFloatingBall: true,
|
||||||
|
ballSize: 70.0,
|
||||||
|
ballColor: Colors.blue,
|
||||||
|
showInDebugMode: true,
|
||||||
|
showInReleaseMode: false,
|
||||||
|
maxLogs: 1000,
|
||||||
|
draggable: true,
|
||||||
|
showBadge: true,
|
||||||
|
autoHide: false,
|
||||||
|
),
|
||||||
|
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: const MainPage(),
|
||||||
|
),
|
||||||
|
debugShowCheckedModeBanner: false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -96,46 +96,63 @@ class _YxFloatingBallState extends State<YxFloatingBall>
|
||||||
OverlayState? overlay;
|
OverlayState? overlay;
|
||||||
try {
|
try {
|
||||||
overlay = Overlay.of(context, rootOverlay: true);
|
overlay = Overlay.of(context, rootOverlay: true);
|
||||||
|
print('YxNetInspector: 成功找到根 Overlay');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
print('YxNetInspector: 根 Overlay 查找失败: $e');
|
||||||
// 如果 Overlay.of 失败,尝试手动查找
|
// 如果 Overlay.of 失败,尝试手动查找
|
||||||
overlay = _findOverlayInContext(context);
|
overlay = _findOverlayInContext(context);
|
||||||
|
if (overlay != null) {
|
||||||
|
print('YxNetInspector: 手动查找 Overlay 成功');
|
||||||
|
} else {
|
||||||
|
print('YxNetInspector: 手动查找 Overlay 也失败');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (overlay == null) {
|
if (overlay == null) {
|
||||||
// 如果仍然找不到 Overlay,使用备选方案
|
// 如果仍然找不到 Overlay,使用备选方案
|
||||||
_showInspectorDialog();
|
_showInspectorDialog();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final overlayEntry = OverlayEntry(
|
try {
|
||||||
builder: (context) => Material(
|
final overlayEntry = OverlayEntry(
|
||||||
color: Colors.black54,
|
builder: (context) => Material(
|
||||||
child: Stack(
|
color: Colors.black54,
|
||||||
children: [
|
child: Stack(
|
||||||
// 背景遮罩
|
children: [
|
||||||
GestureDetector(
|
// 背景遮罩
|
||||||
onTap: _hideInspectorPanel,
|
GestureDetector(
|
||||||
child: Container(
|
onTap: _hideInspectorPanel,
|
||||||
color: Colors.transparent,
|
child: Container(
|
||||||
width: double.infinity,
|
color: Colors.transparent,
|
||||||
height: double.infinity,
|
width: double.infinity,
|
||||||
|
height: double.infinity,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
// 检查器面板
|
||||||
// 检查器面板
|
Center(
|
||||||
Center(
|
child: YxInspectorPanel(
|
||||||
child: YxInspectorPanel(
|
theme: widget.theme,
|
||||||
theme: widget.theme,
|
controller: widget.controller,
|
||||||
controller: widget.controller,
|
onClose: _hideInspectorPanel,
|
||||||
onClose: _hideInspectorPanel,
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
|
||||||
|
|
||||||
overlay.insert(overlayEntry);
|
overlay.insert(overlayEntry);
|
||||||
_currentOverlayEntry = overlayEntry;
|
_currentOverlayEntry = overlayEntry;
|
||||||
|
print('YxNetInspector: 检查器面板显示成功');
|
||||||
|
} catch (e) {
|
||||||
|
print('YxNetInspector: 插入 OverlayEntry 失败: $e');
|
||||||
|
// 重置状态
|
||||||
|
setState(() {
|
||||||
|
_isExpanded = false;
|
||||||
|
});
|
||||||
|
_animationController.reverse();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
OverlayState? _findOverlayInContext(BuildContext context) {
|
OverlayState? _findOverlayInContext(BuildContext context) {
|
||||||
|
|
@ -157,53 +174,26 @@ class _YxFloatingBallState extends State<YxFloatingBall>
|
||||||
}
|
}
|
||||||
|
|
||||||
void _createCustomOverlay() {
|
void _createCustomOverlay() {
|
||||||
// 获取屏幕尺寸
|
// 如果所有 Overlay 方法都失败,我们显示一个简单的调试信息
|
||||||
final mediaQuery = MediaQuery.of(context);
|
// 并重置状态,避免悬浮球卡在展开状态
|
||||||
final screenSize = mediaQuery.size;
|
print('YxNetInspector: 无法找到 Overlay 上下文,请确保 YxNetInspector 在 MaterialApp 内部使用');
|
||||||
|
|
||||||
// 创建一个简单的OverlayEntry但是通过自定义方式
|
// 显示一个简单的 SnackBar 或 print 提示
|
||||||
final overlayEntry = OverlayEntry(
|
|
||||||
builder: (context) => Positioned.fill(
|
|
||||||
child: Material(
|
|
||||||
color: Colors.black54,
|
|
||||||
child: Stack(
|
|
||||||
children: [
|
|
||||||
// 背景遮罩
|
|
||||||
GestureDetector(
|
|
||||||
onTap: _hideInspectorPanel,
|
|
||||||
child: Container(
|
|
||||||
color: Colors.transparent,
|
|
||||||
width: screenSize.width,
|
|
||||||
height: screenSize.height,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
// 检查器面板
|
|
||||||
Center(
|
|
||||||
child: Container(
|
|
||||||
width: screenSize.width * 0.9,
|
|
||||||
height: screenSize.height * 0.8,
|
|
||||||
child: YxInspectorPanel(
|
|
||||||
theme: widget.theme,
|
|
||||||
controller: widget.controller,
|
|
||||||
onClose: _hideInspectorPanel,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// 尝试直接插入到根Overlay中
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
try {
|
if (mounted) {
|
||||||
final rootOverlay = Overlay.of(context, rootOverlay: true);
|
try {
|
||||||
rootOverlay.insert(overlayEntry);
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
_currentOverlayEntry = overlayEntry;
|
const SnackBar(
|
||||||
} catch (e) {
|
content: Text('网络检查器暂时无法显示,请检查应用结构'),
|
||||||
// 如果还是失败,记录错误但不崩溃
|
duration: Duration(seconds: 2),
|
||||||
print('YxNetInspector: 无法显示检查器面板 - $e');
|
),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
// 如果 ScaffoldMessenger 也不可用,只打印日志
|
||||||
|
print('YxNetInspector: 无法显示检查器面板 - 请确保在 MaterialApp 内使用');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置状态
|
||||||
setState(() {
|
setState(() {
|
||||||
_isExpanded = false;
|
_isExpanded = false;
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,10 @@ class _YxInspectorPanelState extends State<YxInspectorPanel> {
|
||||||
String _searchKeyword = '';
|
String _searchKeyword = '';
|
||||||
bool _showOnlyErrors = false;
|
bool _showOnlyErrors = false;
|
||||||
bool _isFullScreen = false;
|
bool _isFullScreen = false;
|
||||||
|
|
||||||
|
// 页面导航状态
|
||||||
|
NetworkLogEntry? _selectedLog;
|
||||||
|
bool _showDetailPage = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
|
@ -33,6 +37,20 @@ class _YxInspectorPanelState extends State<YxInspectorPanel> {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _showLogDetail(NetworkLogEntry log) {
|
||||||
|
setState(() {
|
||||||
|
_selectedLog = log;
|
||||||
|
_showDetailPage = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _hideLogDetail() {
|
||||||
|
setState(() {
|
||||||
|
_selectedLog = null;
|
||||||
|
_showDetailPage = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
List<NetworkLogEntry> get _filteredLogs {
|
List<NetworkLogEntry> get _filteredLogs {
|
||||||
List<NetworkLogEntry> logs = widget.controller.logs;
|
List<NetworkLogEntry> logs = widget.controller.logs;
|
||||||
|
|
||||||
|
|
@ -54,6 +72,33 @@ class _YxInspectorPanelState extends State<YxInspectorPanel> {
|
||||||
return ListenableBuilder(
|
return ListenableBuilder(
|
||||||
listenable: widget.controller,
|
listenable: widget.controller,
|
||||||
builder: (context, child) {
|
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) {
|
if (_isFullScreen) {
|
||||||
// 全屏模式
|
// 全屏模式
|
||||||
return Material(
|
return Material(
|
||||||
|
|
@ -62,14 +107,7 @@ class _YxInspectorPanelState extends State<YxInspectorPanel> {
|
||||||
width: MediaQuery.of(context).size.width,
|
width: MediaQuery.of(context).size.width,
|
||||||
height: MediaQuery.of(context).size.height,
|
height: MediaQuery.of(context).size.height,
|
||||||
color: widget.theme.backgroundColor,
|
color: widget.theme.backgroundColor,
|
||||||
child: Column(
|
child: content,
|
||||||
children: [
|
|
||||||
_buildHeader(),
|
|
||||||
_buildStatistics(),
|
|
||||||
_buildSearchBar(),
|
|
||||||
Expanded(child: _buildLogList()),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -83,14 +121,7 @@ class _YxInspectorPanelState extends State<YxInspectorPanel> {
|
||||||
color: widget.theme.backgroundColor,
|
color: widget.theme.backgroundColor,
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: content,
|
||||||
children: [
|
|
||||||
_buildHeader(),
|
|
||||||
_buildStatistics(),
|
|
||||||
_buildSearchBar(),
|
|
||||||
Expanded(child: _buildLogList()),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -98,6 +129,68 @@ class _YxInspectorPanelState extends State<YxInspectorPanel> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildDetailHeader() {
|
||||||
|
return 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 SizedBox(width: 8),
|
||||||
|
const Icon(
|
||||||
|
Icons.article_outlined,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 24,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
const Text(
|
||||||
|
'请求详情',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
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: '关闭',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildHeader() {
|
Widget _buildHeader() {
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
|
|
@ -309,12 +402,7 @@ class _YxInspectorPanelState extends State<YxInspectorPanel> {
|
||||||
color: widget.theme.cardColor,
|
color: widget.theme.cardColor,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context).push(
|
_showLogDetail(log);
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) =>
|
|
||||||
YxLogDetailPage(log: log, theme: widget.theme),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue