Files
pilipala/lib/pages/setting/pages/logs.dart
2024-06-02 22:32:00 +08:00

203 lines
6.4 KiB
Dart

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:pilipala/common/widgets/no_data.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../../services/loggeer.dart';
class LogsPage extends StatefulWidget {
const LogsPage({super.key});
@override
State<LogsPage> createState() => _LogsPageState();
}
class _LogsPageState extends State<LogsPage> {
late File logsPath;
late String fileContent;
List logsContent = [];
@override
void initState() {
getPath();
super.initState();
}
void getPath() async {
logsPath = await getLogsPath();
fileContent = await logsPath.readAsString();
logsContent = await parseLogs(fileContent);
setState(() {});
}
Future<List<Map<String, dynamic>>> parseLogs(String fileContent) async {
const String splitToken =
'======================================================================';
List contentList = fileContent.split(splitToken).map((item) {
return item
.replaceAll(
'============================== CATCHER 2 LOG ==============================',
'Pilipala错误日志 \n ********************')
.replaceAll('DEVICE INFO', '设备信息')
.replaceAll('APP INFO', '应用信息')
.replaceAll('ERROR', '错误信息')
.replaceAll('STACK TRACE', '错误堆栈')
.replaceAll('#', 'Line');
}).toList();
List<Map<String, dynamic>> result = [];
for (String i in contentList) {
DateTime? date;
String body = i
.split("\n")
.map((l) {
if (l.startsWith("Crash occurred on")) {
date = DateTime.tryParse(
l.split("Crash occurred on")[1].trim().split('.')[0],
);
return "";
}
return l;
})
.where((dynamic l) => l.replaceAll("\n", "").trim().isNotEmpty)
.join("\n");
if (date != null || body != '') {
result.add({'date': date, 'body': body, 'expand': false});
}
}
return result.reversed.toList();
}
void copyLogs() async {
await Clipboard.setData(ClipboardData(text: fileContent));
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('复制成功')),
);
}
}
void feedback() {
launchUrl(
Uri.parse('https://github.com/guozhigq/pilipala/issues'),
// 系统自带浏览器打开
mode: LaunchMode.externalApplication,
);
}
void clearLogsHandle() async {
if (await clearLogs()) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('已清空')),
);
logsContent = [];
setState(() {});
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: false,
titleSpacing: 0,
title: Text('日志', style: Theme.of(context).textTheme.titleMedium),
actions: [
PopupMenuButton<String>(
onSelected: (String type) {
// 处理菜单项选择的逻辑
switch (type) {
case 'copy':
copyLogs();
break;
case 'feedback':
feedback();
break;
case 'clear':
clearLogsHandle();
break;
default:
}
},
itemBuilder: (BuildContext context) => <PopupMenuEntry<String>>[
const PopupMenuItem<String>(
value: 'copy',
child: Text('复制日志'),
),
const PopupMenuItem<String>(
value: 'feedback',
child: Text('错误反馈'),
),
const PopupMenuItem<String>(
value: 'clear',
child: Text('清空日志'),
),
],
),
const SizedBox(width: 6),
],
),
body: logsContent.isNotEmpty
? ListView.builder(
itemCount: logsContent.length,
itemBuilder: (context, index) {
final log = logsContent[index];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
log['date'].toString(),
style: Theme.of(context).textTheme.titleMedium,
),
),
TextButton.icon(
onPressed: () async {
await Clipboard.setData(
ClipboardData(text: log['body']),
);
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'已将 ${log['date'].toString()} 复制至剪贴板',
),
),
);
}
},
icon: const Icon(Icons.copy_outlined, size: 16),
label: const Text('复制'),
)
],
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Card(
elevation: 1,
clipBehavior: Clip.antiAliasWithSaveLayer,
child: Padding(
padding: const EdgeInsets.all(12.0),
child: SelectableText(log['body']),
),
),
),
const Divider(indent: 12, endIndent: 12),
],
);
},
)
: const CustomScrollView(
slivers: <Widget>[
NoData(),
],
),
);
}
}