446 lines
15 KiB
Dart
446 lines
15 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
|
|
|
class BaishunDebugLogEntry {
|
|
const BaishunDebugLogEntry({
|
|
required this.timestamp,
|
|
required this.tag,
|
|
required this.message,
|
|
});
|
|
|
|
final DateTime timestamp;
|
|
final String tag;
|
|
final String message;
|
|
|
|
String get formattedTime {
|
|
final hh = timestamp.hour.toString().padLeft(2, '0');
|
|
final mm = timestamp.minute.toString().padLeft(2, '0');
|
|
final ss = timestamp.second.toString().padLeft(2, '0');
|
|
return '$hh:$mm:$ss';
|
|
}
|
|
}
|
|
|
|
class BaishunDebugSnapshot {
|
|
const BaishunDebugSnapshot({
|
|
required this.roomId,
|
|
required this.gameName,
|
|
required this.gameId,
|
|
required this.gameSessionId,
|
|
required this.entryUrl,
|
|
required this.launchMode,
|
|
required this.loading,
|
|
required this.pageFinished,
|
|
required this.receivedBridgeMessage,
|
|
required this.injectCount,
|
|
required this.configSendCount,
|
|
required this.lastBridgeSource,
|
|
required this.lastBridgeAction,
|
|
required this.lastBridgePayload,
|
|
required this.lastJsCallback,
|
|
required this.lastConfigSummary,
|
|
required this.errorMessage,
|
|
required this.logs,
|
|
});
|
|
|
|
final String roomId;
|
|
final String gameName;
|
|
final String gameId;
|
|
final String gameSessionId;
|
|
final String entryUrl;
|
|
final String launchMode;
|
|
final bool loading;
|
|
final bool pageFinished;
|
|
final bool receivedBridgeMessage;
|
|
final int injectCount;
|
|
final int configSendCount;
|
|
final String lastBridgeSource;
|
|
final String lastBridgeAction;
|
|
final String lastBridgePayload;
|
|
final String lastJsCallback;
|
|
final String lastConfigSummary;
|
|
final String errorMessage;
|
|
final List<BaishunDebugLogEntry> logs;
|
|
}
|
|
|
|
class BaishunDebugPanel extends StatefulWidget {
|
|
const BaishunDebugPanel({
|
|
super.key,
|
|
required this.snapshot,
|
|
required this.onReload,
|
|
required this.onInjectBridge,
|
|
required this.onReplayConfig,
|
|
required this.onWalletUpdate,
|
|
required this.onClearLogs,
|
|
});
|
|
|
|
final BaishunDebugSnapshot snapshot;
|
|
final VoidCallback onReload;
|
|
final VoidCallback onInjectBridge;
|
|
final VoidCallback onReplayConfig;
|
|
final VoidCallback onWalletUpdate;
|
|
final VoidCallback onClearLogs;
|
|
|
|
@override
|
|
State<BaishunDebugPanel> createState() => _BaishunDebugPanelState();
|
|
}
|
|
|
|
class _BaishunDebugPanelState extends State<BaishunDebugPanel> {
|
|
bool _expanded = false;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final maxWidth = (ScreenUtil().screenWidth - 24.w).clamp(220.w, 360.w);
|
|
return AnimatedSwitcher(
|
|
duration: const Duration(milliseconds: 180),
|
|
child:
|
|
_expanded
|
|
? Container(
|
|
key: const ValueKey<String>('baishun_debug_panel_expanded'),
|
|
width: maxWidth,
|
|
padding: EdgeInsets.all(12.w),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xE616302B),
|
|
borderRadius: BorderRadius.circular(16.w),
|
|
border: Border.all(
|
|
color: Colors.white.withValues(alpha: 0.14),
|
|
width: 1.w,
|
|
),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withValues(alpha: 0.22),
|
|
blurRadius: 18.w,
|
|
offset: Offset(0, 8.w),
|
|
),
|
|
],
|
|
),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Text(
|
|
'BAISHUN DEBUG',
|
|
style: TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 12.sp,
|
|
fontWeight: FontWeight.w800,
|
|
letterSpacing: 0.4,
|
|
),
|
|
),
|
|
const Spacer(),
|
|
GestureDetector(
|
|
behavior: HitTestBehavior.opaque,
|
|
onTap: () {
|
|
setState(() {
|
|
_expanded = false;
|
|
});
|
|
},
|
|
child: Padding(
|
|
padding: EdgeInsets.all(2.w),
|
|
child: Icon(
|
|
Icons.close,
|
|
size: 16.w,
|
|
color: Colors.white70,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 8.w),
|
|
_InfoLine(label: 'Room', value: widget.snapshot.roomId),
|
|
_InfoLine(
|
|
label: 'Game',
|
|
value:
|
|
'${widget.snapshot.gameName} (${widget.snapshot.gameId})',
|
|
),
|
|
_InfoLine(
|
|
label: 'Session',
|
|
value: widget.snapshot.gameSessionId,
|
|
),
|
|
_InfoLine(
|
|
label: 'Launch',
|
|
value: widget.snapshot.launchMode,
|
|
),
|
|
_InfoLine(
|
|
label: 'Loading',
|
|
value: widget.snapshot.loading ? 'YES' : 'NO',
|
|
),
|
|
_InfoLine(
|
|
label: 'PageFinished',
|
|
value: widget.snapshot.pageFinished ? 'YES' : 'NO',
|
|
),
|
|
_InfoLine(
|
|
label: 'BridgeMsg',
|
|
value:
|
|
widget.snapshot.receivedBridgeMessage ? 'YES' : 'NO',
|
|
),
|
|
_InfoLine(
|
|
label: 'InjectCount',
|
|
value: '${widget.snapshot.injectCount}',
|
|
),
|
|
_InfoLine(
|
|
label: 'ConfigSend',
|
|
value: '${widget.snapshot.configSendCount}',
|
|
),
|
|
_InfoLine(
|
|
label: 'LastSource',
|
|
value:
|
|
widget.snapshot.lastBridgeSource.isEmpty
|
|
? '--'
|
|
: widget.snapshot.lastBridgeSource,
|
|
),
|
|
_InfoLine(
|
|
label: 'LastAction',
|
|
value:
|
|
widget.snapshot.lastBridgeAction.isEmpty
|
|
? '--'
|
|
: widget.snapshot.lastBridgeAction,
|
|
),
|
|
_InfoLine(
|
|
label: 'LastCallback',
|
|
value:
|
|
widget.snapshot.lastJsCallback.isEmpty
|
|
? '--'
|
|
: widget.snapshot.lastJsCallback,
|
|
),
|
|
if (widget.snapshot.lastConfigSummary.isNotEmpty)
|
|
_MultilineLine(
|
|
label: 'Config',
|
|
value: widget.snapshot.lastConfigSummary,
|
|
),
|
|
_MultilineLine(
|
|
label: 'EntryUrl',
|
|
value: widget.snapshot.entryUrl,
|
|
),
|
|
if (widget.snapshot.lastBridgePayload.isNotEmpty)
|
|
_MultilineLine(
|
|
label: 'Payload',
|
|
value: widget.snapshot.lastBridgePayload,
|
|
),
|
|
if (widget.snapshot.errorMessage.isNotEmpty)
|
|
_MultilineLine(
|
|
label: 'Error',
|
|
value: widget.snapshot.errorMessage,
|
|
valueColor: const Color(0xFFFFB4B4),
|
|
),
|
|
SizedBox(height: 10.w),
|
|
Wrap(
|
|
spacing: 8.w,
|
|
runSpacing: 8.w,
|
|
children: [
|
|
_DebugActionChip(
|
|
label: 'Reload',
|
|
onTap: widget.onReload,
|
|
),
|
|
_DebugActionChip(
|
|
label: 'Inject',
|
|
onTap: widget.onInjectBridge,
|
|
),
|
|
_DebugActionChip(
|
|
label: 'ReplayConfig',
|
|
onTap: widget.onReplayConfig,
|
|
),
|
|
_DebugActionChip(
|
|
label: 'Wallet',
|
|
onTap: widget.onWalletUpdate,
|
|
),
|
|
_DebugActionChip(
|
|
label: 'ClearLogs',
|
|
onTap: widget.onClearLogs,
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 10.w),
|
|
Text(
|
|
'Recent Logs',
|
|
style: TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 11.sp,
|
|
fontWeight: FontWeight.w700,
|
|
),
|
|
),
|
|
SizedBox(height: 6.w),
|
|
Container(
|
|
height: 180.w,
|
|
padding: EdgeInsets.all(8.w),
|
|
decoration: BoxDecoration(
|
|
color: Colors.black.withValues(alpha: 0.22),
|
|
borderRadius: BorderRadius.circular(12.w),
|
|
border: Border.all(
|
|
color: Colors.white.withValues(alpha: 0.08),
|
|
),
|
|
),
|
|
child:
|
|
widget.snapshot.logs.isEmpty
|
|
? Center(
|
|
child: Text(
|
|
'No bridge logs yet',
|
|
style: TextStyle(
|
|
color: Colors.white54,
|
|
fontSize: 11.sp,
|
|
),
|
|
),
|
|
)
|
|
: ListView.separated(
|
|
itemCount: widget.snapshot.logs.length,
|
|
separatorBuilder:
|
|
(_, __) => SizedBox(height: 6.w),
|
|
itemBuilder: (context, index) {
|
|
final entry = widget.snapshot.logs[index];
|
|
return Text(
|
|
'[${entry.formattedTime}] ${entry.tag} ${entry.message}',
|
|
style: TextStyle(
|
|
color: Colors.white70,
|
|
fontSize: 10.sp,
|
|
height: 1.35,
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
)
|
|
: GestureDetector(
|
|
key: const ValueKey<String>('baishun_debug_panel_collapsed'),
|
|
behavior: HitTestBehavior.opaque,
|
|
onTap: () {
|
|
setState(() {
|
|
_expanded = true;
|
|
});
|
|
},
|
|
child: Container(
|
|
padding: EdgeInsets.symmetric(
|
|
horizontal: 12.w,
|
|
vertical: 8.w,
|
|
),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xD916302B),
|
|
borderRadius: BorderRadius.circular(999.w),
|
|
border: Border.all(
|
|
color: Colors.white.withValues(alpha: 0.12),
|
|
),
|
|
),
|
|
child: Text(
|
|
'BS DEBUG',
|
|
style: TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 11.sp,
|
|
fontWeight: FontWeight.w700,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _InfoLine extends StatelessWidget {
|
|
const _InfoLine({required this.label, required this.value});
|
|
|
|
final String label;
|
|
final String value;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Padding(
|
|
padding: EdgeInsets.only(bottom: 4.w),
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
SizedBox(
|
|
width: 82.w,
|
|
child: Text(
|
|
'$label:',
|
|
style: TextStyle(
|
|
color: Colors.white60,
|
|
fontSize: 10.sp,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
),
|
|
Expanded(
|
|
child: Text(
|
|
value,
|
|
style: TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 10.sp,
|
|
height: 1.3,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _MultilineLine extends StatelessWidget {
|
|
const _MultilineLine({
|
|
required this.label,
|
|
required this.value,
|
|
this.valueColor = Colors.white,
|
|
});
|
|
|
|
final String label;
|
|
final String value;
|
|
final Color valueColor;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Padding(
|
|
padding: EdgeInsets.only(top: 4.w),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'$label:',
|
|
style: TextStyle(
|
|
color: Colors.white60,
|
|
fontSize: 10.sp,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
SizedBox(height: 2.w),
|
|
SelectableText(
|
|
value,
|
|
style: TextStyle(color: valueColor, fontSize: 10.sp, height: 1.35),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _DebugActionChip extends StatelessWidget {
|
|
const _DebugActionChip({required this.label, required this.onTap});
|
|
|
|
final String label;
|
|
final VoidCallback onTap;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return GestureDetector(
|
|
behavior: HitTestBehavior.opaque,
|
|
onTap: onTap,
|
|
child: Container(
|
|
padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 6.w),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white.withValues(alpha: 0.08),
|
|
borderRadius: BorderRadius.circular(999.w),
|
|
border: Border.all(color: Colors.white.withValues(alpha: 0.08)),
|
|
),
|
|
child: Text(
|
|
label,
|
|
style: TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 10.sp,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|