Support core status check and force restart Optimize proxies page and access page Update flutter and pub dependencies
370 lines
11 KiB
Dart
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];
|
|
},
|
|
),
|
|
);
|
|
}
|
|
}
|