| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338 |
- import 'package:flutter/material.dart';
- import 'package:flutter/services.dart';
- import 'package:get/get.dart';
- import 'ix_developer_tools.dart';
- /// 轻量级日志条目组件 - 纵向排列,点击弹窗显示完整内容
- class SimpleLogCard extends StatelessWidget {
- final ConsoleLogEntry log;
- const SimpleLogCard({super.key, required this.log});
- @override
- Widget build(BuildContext context) {
- return InkWell(
- onTap: () => _showDetailDialog(context),
- borderRadius: BorderRadius.circular(10),
- child: Container(
- margin: const EdgeInsets.symmetric(horizontal: 4, vertical: 4),
- padding: const EdgeInsets.all(12),
- decoration: BoxDecoration(
- color: Colors.white,
- borderRadius: BorderRadius.circular(10),
- border: Border.all(color: Colors.grey[200]!, width: 1),
- boxShadow: [
- BoxShadow(
- color: Colors.black.withValues(alpha: 0.03),
- blurRadius: 4,
- offset: const Offset(0, 1),
- ),
- ],
- ),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- // 第一行:时间、Tag
- Row(
- children: [
- // 状态指示点
- Container(
- width: 8,
- height: 8,
- decoration: BoxDecoration(
- color: _getTagColor(),
- shape: BoxShape.circle,
- ),
- ),
- const SizedBox(width: 8),
- // Tag标签
- Container(
- padding: const EdgeInsets.symmetric(
- horizontal: 8,
- vertical: 3,
- ),
- decoration: BoxDecoration(
- color: _getTagColor().withValues(alpha: 0.1),
- borderRadius: BorderRadius.circular(4),
- ),
- child: Text(
- log.tag,
- style: TextStyle(
- fontSize: 11,
- color: _getTagColor(),
- fontWeight: FontWeight.w600,
- ),
- ),
- ),
- const Spacer(),
- // 时间
- Text(
- log.formattedTime,
- style: TextStyle(
- fontFamily: 'monospace',
- fontSize: 10,
- color: Colors.grey[400],
- ),
- ),
- ],
- ),
- const SizedBox(height: 8),
- // 第二行:日志内容(完整显示,最多3行)
- Text(
- log.message,
- style: const TextStyle(
- fontSize: 12,
- color: Colors.black87,
- height: 1.4,
- ),
- maxLines: 3,
- overflow: TextOverflow.ellipsis,
- ),
- ],
- ),
- ),
- );
- }
- Color _getTagColor() {
- final tag = log.tag.toLowerCase();
- if (tag.contains('error') || tag.contains('err')) return Colors.red;
- if (tag.contains('warn')) return Colors.orange;
- if (tag.contains('info')) return Colors.blue;
- if (tag.contains('debug')) return Colors.purple;
- if (tag.contains('api') || tag.contains('http')) return Colors.teal;
- if (tag.contains('vpn') || tag.contains('xray')) return Colors.indigo;
- return Colors.blueGrey;
- }
- void _showDetailDialog(BuildContext context) {
- showDialog(
- context: context,
- builder: (context) => _LogDetailDialog(log: log),
- );
- }
- }
- /// 日志详情弹窗 - 纵向排列
- class _LogDetailDialog extends StatelessWidget {
- final ConsoleLogEntry log;
- const _LogDetailDialog({required this.log});
- @override
- Widget build(BuildContext context) {
- return Dialog(
- backgroundColor: Colors.grey[50],
- shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
- insetPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24),
- child: Container(
- width: Get.width * 0.92,
- constraints: BoxConstraints(maxHeight: Get.height * 0.75),
- child: Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- // 头部
- Container(
- padding: const EdgeInsets.all(16),
- decoration: BoxDecoration(
- color: Colors.white,
- borderRadius: const BorderRadius.vertical(
- top: Radius.circular(16),
- ),
- boxShadow: [
- BoxShadow(
- color: Colors.black.withValues(alpha: 0.05),
- blurRadius: 4,
- offset: const Offset(0, 2),
- ),
- ],
- ),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- // 第一行:Tag、时间、操作按钮
- Row(
- children: [
- // Tag
- Container(
- padding: const EdgeInsets.symmetric(
- horizontal: 10,
- vertical: 5,
- ),
- decoration: BoxDecoration(
- color: _getTagColor().withValues(alpha: 0.1),
- borderRadius: BorderRadius.circular(6),
- border: Border.all(
- color: _getTagColor().withValues(alpha: 0.3),
- ),
- ),
- child: Text(
- log.tag,
- style: TextStyle(
- fontSize: 13,
- fontWeight: FontWeight.w600,
- color: _getTagColor(),
- ),
- ),
- ),
- const SizedBox(width: 10),
- // 时间
- Container(
- padding: const EdgeInsets.symmetric(
- horizontal: 8,
- vertical: 4,
- ),
- decoration: BoxDecoration(
- color: Colors.grey[100],
- borderRadius: BorderRadius.circular(6),
- ),
- child: Text(
- log.formattedTime,
- style: TextStyle(
- fontSize: 12,
- color: Colors.grey[700],
- fontFamily: 'monospace',
- fontWeight: FontWeight.w500,
- ),
- ),
- ),
- const Spacer(),
- // 复制按钮
- InkWell(
- onTap: () =>
- _copyToClipboard('${log.tag}: ${log.message}'),
- borderRadius: BorderRadius.circular(8),
- child: Container(
- padding: const EdgeInsets.symmetric(
- horizontal: 10,
- vertical: 6,
- ),
- decoration: BoxDecoration(
- color: Colors.grey[100],
- borderRadius: BorderRadius.circular(8),
- ),
- child: Row(
- mainAxisSize: MainAxisSize.min,
- children: [
- Icon(
- Icons.copy,
- size: 14,
- color: Colors.grey[600],
- ),
- const SizedBox(width: 4),
- Text(
- '复制',
- style: TextStyle(
- fontSize: 12,
- fontWeight: FontWeight.w500,
- color: Colors.grey[600],
- ),
- ),
- ],
- ),
- ),
- ),
- const SizedBox(width: 8),
- // 关闭按钮
- InkWell(
- onTap: () => Navigator.pop(context),
- borderRadius: BorderRadius.circular(8),
- child: Container(
- padding: const EdgeInsets.all(6),
- decoration: BoxDecoration(
- color: Colors.grey[100],
- borderRadius: BorderRadius.circular(8),
- ),
- child: Icon(
- Icons.close,
- size: 18,
- color: Colors.grey[600],
- ),
- ),
- ),
- ],
- ),
- ],
- ),
- ),
- // 内容标题
- Padding(
- padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
- child: Row(
- children: [
- Icon(
- Icons.article_outlined,
- size: 16,
- color: Colors.grey[600],
- ),
- const SizedBox(width: 6),
- Text(
- '日志内容',
- style: TextStyle(
- fontSize: 13,
- fontWeight: FontWeight.w600,
- color: Colors.grey[700],
- ),
- ),
- ],
- ),
- ),
- // 内容
- Flexible(
- child: Container(
- margin: const EdgeInsets.fromLTRB(16, 0, 16, 16),
- decoration: BoxDecoration(
- color: Colors.white,
- borderRadius: BorderRadius.circular(12),
- border: Border.all(color: Colors.grey[200]!),
- ),
- child: SingleChildScrollView(
- padding: const EdgeInsets.all(16),
- child: SelectableText(
- log.message,
- style: const TextStyle(
- fontFamily: 'monospace',
- fontSize: 13,
- color: Colors.black87,
- height: 1.6,
- ),
- ),
- ),
- ),
- ),
- ],
- ),
- ),
- );
- }
- Color _getTagColor() {
- final tag = log.tag.toLowerCase();
- if (tag.contains('error') || tag.contains('err')) return Colors.red;
- if (tag.contains('warn')) return Colors.orange;
- if (tag.contains('info')) return Colors.blue;
- if (tag.contains('debug')) return Colors.purple;
- if (tag.contains('api') || tag.contains('http')) return Colors.teal;
- if (tag.contains('vpn') || tag.contains('xray')) return Colors.indigo;
- return Colors.blueGrey;
- }
- void _copyToClipboard(String text) {
- Clipboard.setData(ClipboardData(text: text));
- Get.snackbar(
- '已复制',
- '日志内容已复制到剪贴板',
- duration: const Duration(seconds: 1),
- snackPosition: SnackPosition.bottom,
- backgroundColor: Colors.grey[800],
- colorText: Colors.white,
- borderRadius: 10,
- margin: const EdgeInsets.all(16),
- icon: const Padding(
- padding: EdgeInsets.only(left: 12),
- child: Icon(Icons.check_circle, color: Colors.white, size: 20),
- ),
- );
- }
- }
|