Files
MWClash/lib/views/logs.dart
chen08209 ed7868282a Add android separates the core process
Support core status check and force restart

Optimize proxies page and access page

Update flutter and pub dependencies

Update go version

Optimize more details
2025-09-23 15:23:58 +08:00

292 lines
8.1 KiB
Dart

import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/providers/providers.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../models/models.dart';
import '../widgets/widgets.dart';
class LogsView extends ConsumerStatefulWidget {
const LogsView({super.key});
@override
ConsumerState<LogsView> createState() => _LogsViewState();
}
class _LogsViewState extends ConsumerState<LogsView> {
final _logsStateNotifier = ValueNotifier<LogsState>(LogsState());
late ScrollController _scrollController;
List<Log> _logs = [];
@override
void initState() {
super.initState();
_logs = globalState.appState.logs.list;
_scrollController = ScrollController(
initialScrollOffset: _logs.length * LogItem.height,
);
_logsStateNotifier.value = _logsStateNotifier.value.copyWith(logs: _logs);
ref.listenManual(logsProvider.select((state) => state.list), (prev, next) {
if (prev != next) {
final isEquality = logListEquality.equals(prev, next);
if (!isEquality) {
_logs = next;
updateLogsThrottler();
}
}
});
}
List<Widget> _buildActions() {
return [
ValueListenableBuilder(
valueListenable: _logsStateNotifier,
builder: (_, state, _) {
return IconButton(
style: state.autoScrollToEnd
? IconButton.styleFrom(
backgroundColor: context.colorScheme.secondaryContainer,
foregroundColor: context.colorScheme.onSecondaryContainer,
)
: null,
onPressed: () {
_logsStateNotifier.value = _logsStateNotifier.value.copyWith(
autoScrollToEnd: !_logsStateNotifier.value.autoScrollToEnd,
);
},
icon: const Icon(Icons.vertical_align_top_outlined),
);
},
),
IconButton(
onPressed: () {
_handleExport();
},
icon: const Icon(Icons.save_as_outlined),
),
];
}
void _onSearch(String value) {
_logsStateNotifier.value = _logsStateNotifier.value.copyWith(query: value);
}
void _onKeywordsUpdate(List<String> keywords) {
_logsStateNotifier.value = _logsStateNotifier.value.copyWith(
keywords: keywords,
);
}
@override
void dispose() {
_logsStateNotifier.dispose();
_scrollController.dispose();
super.dispose();
}
Future<void> _handleExport() async {
final res = await globalState.appController.safeRun<bool>(
() async {
return await globalState.appController.exportLogs();
},
needLoading: true,
title: appLocalizations.exportLogs,
);
if (res != true) return;
globalState.showMessage(
title: appLocalizations.tip,
message: TextSpan(text: appLocalizations.exportSuccess),
);
}
void updateLogsThrottler() {
throttler.call(FunctionTag.logs, () {
if (!mounted) {
return;
}
final isEquality = logListEquality.equals(
_logs,
_logsStateNotifier.value.logs,
);
if (isEquality) {
return;
}
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
_logsStateNotifier.value = _logsStateNotifier.value.copyWith(
logs: _logs,
);
}
});
}, duration: commonDuration);
}
@override
Widget build(BuildContext context) {
return CommonScaffold(
actions: _buildActions(),
onKeywordsUpdate: _onKeywordsUpdate,
searchState: AppBarSearchState(onSearch: _onSearch),
title: appLocalizations.logs,
body: ValueListenableBuilder<LogsState>(
valueListenable: _logsStateNotifier,
builder: (context, state, _) {
final logs = state.list;
if (logs.isEmpty) {
return NullStatus(
label: appLocalizations.nullTip(appLocalizations.logs),
);
}
final items = logs
.map<Widget>(
(log) => LogItem(
key: Key(log.dateTime),
log: log,
onClick: (value) {
context.commonScaffoldState?.addKeyword(value);
},
),
)
.separated(const Divider(height: 0))
.toList();
return Align(
alignment: Alignment.topCenter,
child: ScrollToEndBox(
onCancelToEnd: () {
_logsStateNotifier.value = _logsStateNotifier.value.copyWith(
autoScrollToEnd: false,
);
},
controller: _scrollController,
enable: state.autoScrollToEnd,
dataSource: logs,
child: CommonScrollBar(
controller: _scrollController,
child: ListView.builder(
physics: NextClampingScrollPhysics(),
reverse: true,
shrinkWrap: true,
controller: _scrollController,
itemBuilder: (_, index) {
return items[index];
},
itemExtentBuilder: (index, _) {
if (index.isOdd) {
return 0;
}
return LogItem.height;
},
itemCount: items.length,
),
),
),
);
},
),
);
}
}
class LogItem extends StatelessWidget {
final Log log;
final Function(String)? onClick;
static double get height {
final measure = globalState.measure;
return measure.bodyLargeHeight * 2 +
8 +
24 +
globalState.measure.labelMediumHeight +
16 +
20;
}
const LogItem({super.key, required this.log, this.onClick});
@override
Widget build(BuildContext context) {
return ListItem(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
onTap: () {
globalState.showCommonDialog(child: LogDetailDialog(log: log));
},
title: SizedBox(
height: globalState.measure.bodyLargeHeight * 2,
child: Text(
log.payload,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: context.textTheme.bodyLarge?.copyWith(
color: log.logLevel.color,
),
),
),
subtitle: Column(
children: [
SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
CommonChip(
onPressed: () {
if (onClick == null) return;
onClick!(log.logLevel.name);
},
label: log.logLevel.name,
),
Text(
log.dateTime,
style: context.textTheme.bodySmall?.copyWith(
color: context.colorScheme.onSurface.opacity80,
),
),
],
),
],
),
);
}
}
class LogDetailDialog extends StatelessWidget {
final Log log;
const LogDetailDialog({super.key, required this.log});
@override
Widget build(BuildContext context) {
return CommonDialog(
title: appLocalizations.details(appLocalizations.log),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop(true);
},
child: Text(appLocalizations.confirm),
),
],
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 6,
children: [
SelectableText(
log.payload,
style: context.textTheme.bodyLarge?.copyWith(
color: log.logLevel.color,
),
),
SelectableText(
log.dateTime,
style: context.textTheme.bodySmall?.copyWith(
color: context.colorScheme.onSurfaceVariant,
),
),
],
),
);
}
}