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

Optimize proxies page and access page

Update flutter and pub dependencies
2025-09-03 17:05:46 +08:00

370 lines
11 KiB
Dart

import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/plugins/app.dart';
import 'package:fl_clash/providers/config.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class TrackerInfoItem extends ConsumerWidget {
final TrackerInfo trackerInfo;
final Function(String)? onClickKeyword;
final Widget? trailing;
final String detailTitle;
const TrackerInfoItem({
super.key,
required this.trackerInfo,
this.onClickKeyword,
this.trailing,
required this.detailTitle,
});
static double get subTitleHeight {
return globalState.measure.bodySmallHeight + 20;
}
static double get height {
final measure = globalState.measure;
return measure.bodyMediumHeight +
8 +
8 +
measure.bodyLargeHeight +
subTitleHeight +
16 * 2;
}
Future<ImageProvider?> _getPackageIcon(TrackerInfo connection) async {
return await app?.getPackageIcon(connection.metadata.process);
}
String _getSourceText(TrackerInfo trackerInfo) {
final progress = trackerInfo.progressText.isNotEmpty
? '${trackerInfo.progressText} · '
: '';
final traffic = Traffic(up: trackerInfo.upload, down: trackerInfo.download);
return '$progress${traffic.desc}';
}
@override
Widget build(BuildContext context, ref) {
final value = ref.watch(
patchClashConfigProvider.select(
(state) =>
state.findProcessMode == FindProcessMode.always && system.isAndroid,
),
);
final title = Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
spacing: 8,
children: [
Flexible(
child: Text(
trackerInfo.desc,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: context.textTheme.bodyLarge,
),
),
Text(
trackerInfo.start.lastUpdateTimeDesc,
style: context.textTheme.bodySmall?.copyWith(
color: context.colorScheme.onSurface.opacity60,
),
),
],
),
const SizedBox(height: 6),
Text(
_getSourceText(trackerInfo),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: context.textTheme.bodyMedium?.copyWith(
color: context.colorScheme.onSurfaceVariant,
),
),
],
);
final subTitle = SizedBox(
height: subTitleHeight,
child: Row(
spacing: 8,
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: ListView.separated(
separatorBuilder: (_, _) => SizedBox(width: 6),
padding: EdgeInsets.zero,
scrollDirection: Axis.horizontal,
itemCount: trackerInfo.chains.length,
itemBuilder: (_, index) {
final chain = trackerInfo.chains[index];
return CommonChip(
label: chain,
labelStyle: context.textTheme.bodySmall?.copyWith(
color: context.colorScheme.onSurfaceVariant,
),
onPressed: () {
if (onClickKeyword == null) return;
onClickKeyword!(chain);
},
);
},
),
),
if (trailing != null) trailing!,
],
),
);
final icon = value
? GestureDetector(
onTap: () {
if (onClickKeyword == null) return;
final process = trackerInfo.metadata.process;
if (process.isEmpty) return;
onClickKeyword!(process);
},
child: Container(
margin: const EdgeInsets.only(top: 4),
width: 42,
height: 42,
child: FutureBuilder<ImageProvider?>(
future: _getPackageIcon(trackerInfo),
builder: (_, snapshot) {
if (!snapshot.hasData && snapshot.data == null) {
return Container();
} else {
return Image(
image: snapshot.data!,
gaplessPlayback: true,
width: 42,
height: 42,
);
}
},
),
),
)
: null;
return ListItem(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
onTap: () {
showExtend(
context,
builder: (_, type) {
return AdaptiveSheetScaffold(
type: type,
body: TrackerInfoDetailView(trackerInfo: trackerInfo),
title: detailTitle,
);
},
);
},
title: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
spacing: 12,
children: [
if (icon != null) icon,
Flexible(child: title),
],
),
const SizedBox(height: 8),
subTitle,
],
),
);
}
}
class TrackerInfoDetailView extends StatelessWidget {
final TrackerInfo trackerInfo;
const TrackerInfoDetailView({super.key, required this.trackerInfo});
String _getRuleText() {
final rule = trackerInfo.rule;
final rulePayload = trackerInfo.rulePayload;
if (rulePayload.isNotEmpty) {
return '$rule($rulePayload)';
}
return rule;
}
String _getProgressText() {
final process = trackerInfo.metadata.process;
final uid = trackerInfo.metadata.uid;
if (uid != 0) {
return '$process($uid)';
}
return process;
}
String _getSourceText() {
final sourceIP = trackerInfo.metadata.sourceIP;
if (sourceIP.isEmpty) {
return '';
}
final sourcePort = trackerInfo.metadata.sourcePort;
if (sourcePort.isNotEmpty) {
return '$sourceIP:$sourcePort';
}
return sourceIP;
}
String _getDestinationText() {
final destinationIP = trackerInfo.metadata.destinationIP;
if (destinationIP.isEmpty) {
return '';
}
final destinationPort = trackerInfo.metadata.destinationPort;
if (destinationPort.isNotEmpty) {
return '$destinationIP:$destinationPort';
}
return destinationIP;
}
Widget _buildChains() {
final chains = Wrap(
spacing: 8,
runSpacing: 8,
alignment: WrapAlignment.end,
children: [
for (final chain in trackerInfo.chains)
CommonChip(label: chain, onPressed: () {}),
],
);
return ListItem(
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(appLocalizations.proxyChains),
Flexible(child: chains),
],
),
);
}
Widget _buildItem({
required String title,
required String desc,
bool quickCopy = false,
}) {
return ListItem(
title: Row(
spacing: 16,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
spacing: 4,
children: [
Text(title),
if (quickCopy)
Padding(
padding: EdgeInsets.only(top: 4),
child: IconButton(
visualDensity: VisualDensity.compact,
padding: EdgeInsets.zero,
icon: Icon(Icons.content_copy, size: 18),
onPressed: () {},
),
),
],
),
Flexible(child: Text(desc, textAlign: TextAlign.end)),
],
),
);
}
@override
Widget build(BuildContext context) {
final items = [
_buildItem(
title: appLocalizations.creationTime,
desc: trackerInfo.start.showFull,
),
if (_getProgressText().isNotEmpty)
_buildItem(title: appLocalizations.progress, desc: _getProgressText()),
_buildItem(
title: appLocalizations.networkType,
desc: trackerInfo.metadata.network,
),
_buildItem(title: appLocalizations.rule, desc: _getRuleText()),
if (trackerInfo.metadata.host.isNotEmpty)
_buildItem(
title: appLocalizations.host,
desc: trackerInfo.metadata.host,
),
if (_getSourceText().isNotEmpty)
_buildItem(title: appLocalizations.source, desc: _getSourceText()),
if (_getDestinationText().isNotEmpty)
_buildItem(
title: appLocalizations.destination,
desc: _getDestinationText(),
),
_buildItem(
title: appLocalizations.upload,
desc: trackerInfo.upload.traffic.show,
),
_buildItem(
title: appLocalizations.download,
desc: trackerInfo.download.traffic.show,
),
if (trackerInfo.metadata.destinationGeoIP.isNotEmpty)
_buildItem(
title: appLocalizations.destinationGeoIP,
desc: trackerInfo.metadata.destinationGeoIP.join(' '),
),
if (trackerInfo.metadata.destinationIPASN.isNotEmpty)
_buildItem(
title: appLocalizations.destinationIPASN,
desc: trackerInfo.metadata.destinationIPASN,
),
if (trackerInfo.metadata.dnsMode != null)
_buildItem(
title: appLocalizations.dnsMode,
desc: trackerInfo.metadata.dnsMode!.name,
),
if (trackerInfo.metadata.specialProxy.isNotEmpty)
_buildItem(
title: appLocalizations.specialProxy,
desc: trackerInfo.metadata.specialProxy,
),
if (trackerInfo.metadata.specialRules.isNotEmpty)
_buildItem(
title: appLocalizations.specialRules,
desc: trackerInfo.metadata.specialRules,
),
if (trackerInfo.metadata.remoteDestination.isNotEmpty)
_buildItem(
title: appLocalizations.remoteDestination,
desc: trackerInfo.metadata.remoteDestination,
),
_buildChains(),
];
return SelectionArea(
child: ListView.builder(
padding: EdgeInsets.symmetric(vertical: 12),
itemCount: items.length,
itemBuilder: (_, index) {
return items[index];
},
),
);
}
}