Files
FlClash/lib/features/connection/tracker_info_list.dart
T
chen08209 491fae10f5 feat(views)!: set DNS, NTP, sniffer and TUN key by key
BREAKING CHANGE: DNS and NTP override toggles are removed; picked keys apply to every profile
Changelog: Per-key DNS, NTP, sniffer and TUN settings
2026-10-12 10:34:14 +08:00

143 lines
3.9 KiB
Dart

import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:material_ui/material_ui.dart';
import 'package:super_sliver_list/super_sliver_list.dart';
import 'tracker_info_item.dart';
class TrackerInfoListController extends ValueNotifier<TrackerInfosState> {
TrackerInfoListController() : super(const TrackerInfosState());
void search(String query) {
value = value.copyWith(query: query);
}
void updateKeywords(List<String> keywords) {
value = value.copyWith(keywords: keywords);
}
void setTrackerInfos(List<TrackerInfo> trackerInfos) {
if (identical(trackerInfos, value.trackerInfos)) {
return;
}
value = value.copyWith(
trackerInfos: value.autoScrollToEnd
? trackerInfos
: retainTrimmedHead(
value.trackerInfos,
trackerInfos,
pausedMaxRequestsLength,
),
);
}
void setAutoScrollToEnd(bool autoScrollToEnd) {
value = value.copyWith(autoScrollToEnd: autoScrollToEnd);
}
void resumeAutoScrollToEnd(List<TrackerInfo> trackerInfos) {
value = value.copyWith(autoScrollToEnd: true, trackerInfos: trackerInfos);
}
}
class TrackerInfoList extends StatelessWidget {
final List<TrackerInfo> trackerInfos;
final String detailTitle;
final ScrollController? controller;
final bool reverse;
final bool shrinkWrap;
final ScrollPhysics? physics;
final EdgeInsetsGeometry? padding;
final Widget? Function(TrackerInfo trackerInfo)? actionBuilder;
const TrackerInfoList({
super.key,
required this.trackerInfos,
required this.detailTitle,
this.controller,
this.reverse = false,
this.shrinkWrap = false,
this.physics,
this.padding,
this.actionBuilder,
});
@override
Widget build(BuildContext context) {
return SuperListView.separated(
reverse: reverse,
shrinkWrap: shrinkWrap,
physics: physics,
controller: controller,
padding: padding,
itemCount: trackerInfos.length,
extentEstimation: zeroDividerExtentEstimation,
separatorBuilder: (_, _) => const Divider(height: 0),
itemBuilder: (_, index) => _buildTrackerInfoItem(
context,
trackerInfos[index],
isLive: false,
detailTitle: detailTitle,
actionBuilder: actionBuilder,
),
);
}
}
/// Same rows as [TrackerInfoList], but diffed by connection id so closed
/// connections collapse out and surviving rows slide to their new slot.
class TrackerInfoAnimatedList extends StatelessWidget {
final List<TrackerInfo> trackerInfos;
final String detailTitle;
final ScrollController? controller;
final EdgeInsetsGeometry? padding;
final Widget? Function(TrackerInfo trackerInfo)? actionBuilder;
const TrackerInfoAnimatedList({
super.key,
required this.trackerInfos,
required this.detailTitle,
this.controller,
this.padding,
this.actionBuilder,
});
@override
Widget build(BuildContext context) {
return KeyedAnimatedList<TrackerInfo>(
controller: controller,
padding: padding,
items: trackerInfos,
keyOf: (trackerInfo) => trackerInfo.id,
separator: const Divider(height: 0),
itemBuilder: (context, trackerInfo) => _buildTrackerInfoItem(
context,
trackerInfo,
isLive: true,
detailTitle: detailTitle,
actionBuilder: actionBuilder,
),
);
}
}
Widget _buildTrackerInfoItem(
BuildContext context,
TrackerInfo trackerInfo, {
required bool isLive,
required String detailTitle,
required Widget? Function(TrackerInfo trackerInfo)? actionBuilder,
}) {
return TrackerInfoItem(
key: Key(trackerInfo.id),
trackerInfo: trackerInfo,
isLive: isLive,
onClickKeyword: (value) {
context.commonScaffoldState?.addKeyword(value);
},
action: actionBuilder?.call(trackerInfo),
detailTitle: detailTitle,
);
}