911 lines
28 KiB
Dart
911 lines
28 KiB
Dart
part of 'overwrite.dart';
|
|
|
|
class _CustomProxyGroupsView extends ConsumerWidget {
|
|
final int profileId;
|
|
|
|
const _CustomProxyGroupsView(this.profileId);
|
|
|
|
void _handleReorder(
|
|
WidgetRef ref,
|
|
int profileId,
|
|
int oldIndex,
|
|
int newIndex,
|
|
) {
|
|
ref.read(proxyGroupsProvider(profileId).notifier).order(oldIndex, newIndex);
|
|
}
|
|
|
|
void _handleEditProxyGroup(BuildContext context, ProxyGroup proxyGroup) {
|
|
showSheet(
|
|
context: context,
|
|
props: SheetProps(
|
|
isScrollControlled: true,
|
|
backgroundColor: Colors.transparent,
|
|
maxWidth: double.maxFinite,
|
|
),
|
|
builder: (context) {
|
|
return ProfileIdProvider(
|
|
profileId: profileId,
|
|
child: ProviderScope(
|
|
overrides: [
|
|
proxyGroupProvider.overrideWithBuild((_, __) => proxyGroup),
|
|
],
|
|
child: _EditProxyGroupNestedSheet(),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
Widget _buildItem({
|
|
required BuildContext context,
|
|
required ProxyGroup proxyGroup,
|
|
required int index,
|
|
required int total,
|
|
required VoidCallback onPressed,
|
|
}) {
|
|
final position = ItemPosition.get(index, total);
|
|
return ItemPositionProvider(
|
|
key: ValueKey(proxyGroup.name),
|
|
position: position,
|
|
child: Padding(
|
|
padding: EdgeInsets.symmetric(horizontal: 16),
|
|
child: DecorationListItem(
|
|
onPressed: onPressed,
|
|
minVerticalPadding: 8,
|
|
title: Text(proxyGroup.name),
|
|
subtitle: Text(proxyGroup.type.name),
|
|
trailing: ReorderableDelayedDragStartListener(
|
|
index: index,
|
|
child: Icon(Icons.drag_handle),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final proxyGroups = ref.watch(proxyGroupsProvider(profileId)).value ?? [];
|
|
return CommonScaffold(
|
|
title: '策略组',
|
|
body: ReorderableListView.builder(
|
|
buildDefaultDragHandles: false,
|
|
padding: EdgeInsets.symmetric(vertical: 16),
|
|
itemBuilder: (context, index) {
|
|
final proxyGroup = proxyGroups[index];
|
|
return _buildItem(
|
|
context: context,
|
|
proxyGroup: proxyGroup,
|
|
total: proxyGroups.length,
|
|
index: index,
|
|
onPressed: () {
|
|
_handleEditProxyGroup(context, proxyGroup);
|
|
},
|
|
);
|
|
},
|
|
itemCount: proxyGroups.length,
|
|
onReorder: (oldIndex, newIndex) {
|
|
_handleReorder(ref, profileId, oldIndex, newIndex);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _EditProxyGroupNestedSheet extends StatelessWidget {
|
|
const _EditProxyGroupNestedSheet();
|
|
|
|
Future<void> _handleClose(
|
|
BuildContext context,
|
|
NavigatorState? navigatorState,
|
|
) async {
|
|
if (navigatorState != null && navigatorState.canPop()) {
|
|
final res = await globalState.showMessage(
|
|
message: TextSpan(text: '确定要退出当前窗口吗?'),
|
|
);
|
|
if (res != true) {
|
|
return;
|
|
}
|
|
}
|
|
if (context.mounted) {
|
|
Navigator.of(context).pop();
|
|
}
|
|
}
|
|
|
|
Future<void> _handlePop(
|
|
BuildContext context,
|
|
NavigatorState? navigatorState,
|
|
) async {
|
|
if (navigatorState != null && navigatorState.canPop()) {
|
|
navigatorState.pop();
|
|
} else {
|
|
Navigator.of(context).pop();
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final GlobalKey<NavigatorState> nestedNavigatorKey = GlobalKey();
|
|
final nestedNavigator = Navigator(
|
|
key: nestedNavigatorKey,
|
|
onGenerateInitialRoutes: (navigator, initialRoute) {
|
|
return [
|
|
PagedSheetRoute(
|
|
builder: (context) {
|
|
return _EditProxyGroupView();
|
|
},
|
|
),
|
|
];
|
|
},
|
|
);
|
|
final sheetProvider = SheetProvider.of(context);
|
|
return CommonPopScope(
|
|
onPop: (_) async {
|
|
_handlePop(context, nestedNavigatorKey.currentState);
|
|
return false;
|
|
},
|
|
child: sheetProvider!.copyWith(
|
|
nestedNavigatorPopCallback: () {
|
|
Navigator.of(context).pop();
|
|
},
|
|
child: Stack(
|
|
children: [
|
|
Positioned.fill(
|
|
child: GestureDetector(
|
|
onTap: () async {
|
|
_handleClose(context, nestedNavigatorKey.currentState);
|
|
},
|
|
),
|
|
),
|
|
SizedBox(
|
|
width: sheetProvider.type == SheetType.sideSheet ? 400 : null,
|
|
child: SheetViewport(
|
|
child: PagedSheet(
|
|
decoration: MaterialSheetDecoration(
|
|
size: SheetSize.stretch,
|
|
color: sheetProvider.type == SheetType.bottomSheet
|
|
? context.colorScheme.surfaceContainerLow
|
|
: context.colorScheme.surface,
|
|
borderRadius: sheetProvider.type == SheetType.bottomSheet
|
|
? BorderRadius.vertical(top: Radius.circular(28))
|
|
: BorderRadius.zero,
|
|
clipBehavior: Clip.antiAlias,
|
|
),
|
|
navigator: nestedNavigator,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _EditProxyGroupView extends ConsumerStatefulWidget {
|
|
const _EditProxyGroupView();
|
|
|
|
@override
|
|
ConsumerState createState() => _EditProxyGroupViewState();
|
|
}
|
|
|
|
class _EditProxyGroupViewState extends ConsumerState<_EditProxyGroupView> {
|
|
Future<void> _showTypeOptions(GroupType type) async {
|
|
final value = await globalState.showCommonDialog<GroupType>(
|
|
child: OptionsDialog<GroupType>(
|
|
title: '类型',
|
|
options: GroupType.values,
|
|
textBuilder: (item) => item.name,
|
|
value: type,
|
|
),
|
|
);
|
|
if (value == null) {
|
|
return;
|
|
}
|
|
ref
|
|
.read(proxyGroupProvider.notifier)
|
|
.update((state) => state.copyWith(type: value));
|
|
}
|
|
|
|
Widget _buildItem({
|
|
required Widget title,
|
|
Widget? trailing,
|
|
final VoidCallback? onPressed,
|
|
}) {
|
|
return DecorationListItem(
|
|
onPressed: onPressed,
|
|
title: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
spacing: 16,
|
|
children: [
|
|
title,
|
|
if (trailing != null)
|
|
Flexible(
|
|
child: IconTheme(
|
|
data: IconThemeData(
|
|
size: 16,
|
|
color: context.colorScheme.onSurface.opacity60,
|
|
),
|
|
child: Container(
|
|
alignment: Alignment.centerRight,
|
|
height: globalState.measure.bodyLargeHeight + 24,
|
|
child: trailing,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
void _handleToProxiesView() {
|
|
Navigator.of(
|
|
context,
|
|
).push(PagedSheetRoute(builder: (context) => _EditProxiesView()));
|
|
}
|
|
|
|
void _handleToProvidersView() {}
|
|
|
|
Widget _buildProvidersItem(bool includeAllProviders, List<String> use) {
|
|
return _buildItem(
|
|
title: Text('选择代理集'),
|
|
trailing: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
spacing: 2,
|
|
children: [
|
|
!includeAllProviders
|
|
? Card(
|
|
elevation: 0,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(14),
|
|
),
|
|
child: Container(
|
|
constraints: BoxConstraints(minWidth: 32),
|
|
child: Padding(
|
|
padding: EdgeInsets.symmetric(horizontal: 4, vertical: 3),
|
|
child: Text(
|
|
textAlign: TextAlign.center,
|
|
'${use.length}',
|
|
style: context.textTheme.bodySmall,
|
|
),
|
|
),
|
|
),
|
|
)
|
|
: Icon(
|
|
Icons.check_circle_outline,
|
|
size: 20,
|
|
color: Colors.greenAccent.shade200,
|
|
),
|
|
Icon(Icons.arrow_forward_ios),
|
|
],
|
|
),
|
|
onPressed: _handleToProvidersView,
|
|
);
|
|
}
|
|
|
|
Widget _buildProxiesItem(bool includeAllProxies, List<String> proxies) {
|
|
return _buildItem(
|
|
title: Text('选择代理'),
|
|
trailing: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
spacing: 2,
|
|
children: [
|
|
!includeAllProxies
|
|
? Card(
|
|
elevation: 0,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(14),
|
|
),
|
|
child: Container(
|
|
constraints: BoxConstraints(minWidth: 32),
|
|
child: Padding(
|
|
padding: EdgeInsets.symmetric(horizontal: 4, vertical: 3),
|
|
child: Text(
|
|
textAlign: TextAlign.center,
|
|
'${proxies.length}',
|
|
style: context.textTheme.bodySmall,
|
|
),
|
|
),
|
|
),
|
|
)
|
|
: Icon(
|
|
Icons.check_circle_outline,
|
|
size: 20,
|
|
color: Colors.greenAccent.shade200,
|
|
),
|
|
Icon(Icons.arrow_forward_ios),
|
|
],
|
|
),
|
|
onPressed: _handleToProxiesView,
|
|
);
|
|
}
|
|
|
|
Widget _buildTypeItem(GroupType type) {
|
|
return _buildItem(
|
|
title: Text('类型'),
|
|
onPressed: () {
|
|
_showTypeOptions(type);
|
|
},
|
|
trailing: Text(type.name),
|
|
);
|
|
}
|
|
|
|
void _handleChangeName(String value) {
|
|
ref
|
|
.read(proxyGroupProvider.notifier)
|
|
.update((state) => state.copyWith(name: value));
|
|
}
|
|
|
|
Widget _buildNameItem(String name) {
|
|
return _buildItem(
|
|
title: Text('名称'),
|
|
trailing: TextFormField(
|
|
initialValue: name,
|
|
onChanged: (value) {
|
|
_handleChangeName(value);
|
|
},
|
|
textAlign: TextAlign.end,
|
|
decoration: InputDecoration.collapsed(
|
|
border: NoInputBorder(),
|
|
hintText: '输入策略组名称',
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
void _handleChangeHidden() {
|
|
ref
|
|
.read(proxyGroupProvider.notifier)
|
|
.update((state) => state.copyWith(hidden: !(state.hidden ?? false)));
|
|
}
|
|
|
|
Widget _buildHiddenItem(bool hidden) {
|
|
return _buildItem(
|
|
title: Text('从列表中隐藏'),
|
|
onPressed: _handleChangeHidden,
|
|
trailing: Switch(
|
|
value: hidden,
|
|
onChanged: (_) {
|
|
_handleChangeHidden();
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
void _handleChangeDisableUDP() {
|
|
ref
|
|
.read(proxyGroupProvider.notifier)
|
|
.update(
|
|
(state) => state.copyWith(disableUDP: !(state.disableUDP ?? false)),
|
|
);
|
|
}
|
|
|
|
Widget _buildDisableUDPItem(bool disableUDP) {
|
|
return _buildItem(
|
|
title: Text('禁用UDP'),
|
|
onPressed: _handleChangeDisableUDP,
|
|
trailing: Switch(
|
|
value: disableUDP,
|
|
onChanged: (_) {
|
|
_handleChangeDisableUDP();
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
void _handleDelete() {}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final isBottomSheet =
|
|
SheetProvider.of(context)?.type == SheetType.bottomSheet;
|
|
final proxyGroup = ref.watch(proxyGroupProvider);
|
|
return AdaptiveSheetScaffold(
|
|
sheetTransparentToolBar: true,
|
|
actions: [IconButtonData(icon: Icons.check, onPressed: () {})],
|
|
body: SizedBox(
|
|
height: isBottomSheet
|
|
? appController.viewSize.height * 0.65
|
|
: double.maxFinite,
|
|
child: ListView(
|
|
padding: EdgeInsets.symmetric(
|
|
horizontal: 16,
|
|
).copyWith(bottom: 20, top: context.sheetTopPadding),
|
|
children: [
|
|
generateSectionV3(
|
|
title: '通用',
|
|
items: [
|
|
_buildNameItem(proxyGroup.name),
|
|
_buildTypeItem(proxyGroup.type),
|
|
_buildItem(title: Text('图标')),
|
|
_buildHiddenItem(proxyGroup.hidden ?? false),
|
|
_buildDisableUDPItem(proxyGroup.disableUDP ?? false),
|
|
],
|
|
),
|
|
generateSectionV3(
|
|
title: '节点',
|
|
items: [
|
|
_buildProxiesItem(
|
|
proxyGroup.includeAllProxies ?? false,
|
|
proxyGroup.proxies ?? [],
|
|
),
|
|
_buildProvidersItem(
|
|
proxyGroup.includeAllProviders ?? false,
|
|
proxyGroup.use ?? [],
|
|
),
|
|
_buildItem(
|
|
title: Text('节点过滤器'),
|
|
trailing: TextFormField(
|
|
textAlign: TextAlign.end,
|
|
decoration: InputDecoration.collapsed(
|
|
border: NoInputBorder(),
|
|
hintText: '可选',
|
|
),
|
|
),
|
|
),
|
|
_buildItem(
|
|
title: Text('排除过滤器'),
|
|
trailing: TextFormField(
|
|
textAlign: TextAlign.end,
|
|
decoration: InputDecoration.collapsed(
|
|
border: NoInputBorder(),
|
|
hintText: '可选',
|
|
),
|
|
),
|
|
),
|
|
_buildItem(
|
|
title: Text('排除类型'),
|
|
trailing: TextFormField(
|
|
textAlign: TextAlign.end,
|
|
decoration: InputDecoration.collapsed(
|
|
border: NoInputBorder(),
|
|
hintText: '可选',
|
|
),
|
|
),
|
|
),
|
|
_buildItem(
|
|
title: Text('预期状态'),
|
|
trailing: TextFormField(
|
|
textAlign: TextAlign.end,
|
|
decoration: InputDecoration.collapsed(
|
|
border: NoInputBorder(),
|
|
hintText: '可选',
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
generateSectionV3(
|
|
title: '其他',
|
|
items: [
|
|
_buildItem(
|
|
title: Text('测速链接'),
|
|
trailing: TextFormField(
|
|
textAlign: TextAlign.end,
|
|
decoration: InputDecoration.collapsed(
|
|
border: NoInputBorder(),
|
|
hintText: '可选',
|
|
),
|
|
),
|
|
),
|
|
_buildItem(
|
|
title: Text('最大失败次数'),
|
|
trailing: TextFormField(
|
|
textAlign: TextAlign.end,
|
|
decoration: InputDecoration.collapsed(
|
|
border: NoInputBorder(),
|
|
hintText: '可选',
|
|
),
|
|
),
|
|
),
|
|
_buildItem(
|
|
title: Text('使用时测速'),
|
|
trailing: Switch(value: false, onChanged: (_) {}),
|
|
),
|
|
_buildItem(
|
|
title: Text('测速间隔'),
|
|
trailing: TextFormField(
|
|
textAlign: TextAlign.end,
|
|
decoration: InputDecoration.collapsed(
|
|
border: NoInputBorder(),
|
|
hintText: '可选',
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
generateSectionV3(
|
|
title: '操作',
|
|
items: [
|
|
_buildItem(
|
|
title: Text(
|
|
'删除',
|
|
style: context.textTheme.bodyLarge?.copyWith(
|
|
color: context.colorScheme.error,
|
|
),
|
|
),
|
|
onPressed: _handleDelete,
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
title: '编辑策略组',
|
|
);
|
|
}
|
|
}
|
|
|
|
class _EditProxiesView extends ConsumerStatefulWidget {
|
|
const _EditProxiesView();
|
|
|
|
@override
|
|
ConsumerState<_EditProxiesView> createState() => _EditProxiesViewState();
|
|
}
|
|
|
|
class _EditProxiesViewState extends ConsumerState<_EditProxiesView>
|
|
with UniqueKeyStateMixin {
|
|
void _handleToAddProxiesView() {
|
|
Navigator.of(
|
|
context,
|
|
).push(PagedSheetRoute(builder: (context) => _AddProxiesView()));
|
|
}
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
}
|
|
|
|
void _handleRemove(String proxyName) {
|
|
final dismissItem = ref.read(itemProvider(key));
|
|
if (dismissItem != null) {
|
|
return;
|
|
}
|
|
ref.read(itemProvider(key).notifier).value = proxyName;
|
|
}
|
|
|
|
void _handleRealRemove(String proxyName) {
|
|
ref.read(proxyGroupProvider.notifier).update((state) {
|
|
final newProxies = List<String>.from(state.proxies ?? []);
|
|
newProxies.remove(proxyName);
|
|
return state.copyWith(proxies: newProxies);
|
|
});
|
|
if (mounted) {
|
|
ref.read(itemProvider(key).notifier).value = null;
|
|
}
|
|
}
|
|
|
|
Widget _buildItem({
|
|
required String proxyName,
|
|
required String? proxyType,
|
|
required int index,
|
|
required int length,
|
|
required bool dismiss,
|
|
}) {
|
|
final position = ItemPosition.get(index, length);
|
|
return ExternalDismissible(
|
|
dismiss: dismiss,
|
|
effect: ExternalDismissibleEffect.normal,
|
|
key: ValueKey(proxyName),
|
|
onDismissed: () {
|
|
_handleRealRemove(proxyName);
|
|
},
|
|
child: Padding(
|
|
padding: EdgeInsets.symmetric(horizontal: 16),
|
|
child: ItemPositionProvider(
|
|
position: position,
|
|
child: DecorationListItem(
|
|
minVerticalPadding: 8,
|
|
title: Text(proxyName),
|
|
subtitle: Text(proxyType ?? proxyName.toLowerCase()),
|
|
leading: CommonMinIconButtonTheme(
|
|
child: IconButton.filledTonal(
|
|
onPressed: () {
|
|
_handleRemove(proxyName);
|
|
},
|
|
icon: Icon(Icons.remove, size: 18),
|
|
padding: EdgeInsets.zero,
|
|
),
|
|
),
|
|
trailing: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
ReorderableDelayedDragStartListener(
|
|
index: index,
|
|
child: Icon(Icons.drag_handle, size: 24),
|
|
),
|
|
SizedBox(width: 12),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
void _handleReorder(int oldIndex, int newIndex) {
|
|
if (oldIndex < newIndex) {
|
|
newIndex -= 1;
|
|
}
|
|
ref.read(proxyGroupProvider.notifier).update((state) {
|
|
final nextItems = List<String>.from(state.proxies ?? []);
|
|
final item = nextItems.removeAt(oldIndex);
|
|
nextItems.insert(newIndex, item);
|
|
return state.copyWith(proxies: nextItems);
|
|
});
|
|
}
|
|
|
|
void _handleChangeIncludeAllProxies() {
|
|
ref
|
|
.read(proxyGroupProvider.notifier)
|
|
.update(
|
|
(state) => state.copyWith(
|
|
includeAllProxies: !(state.includeAllProxies ?? false),
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final profileId = ProfileIdProvider.of(context)!.profileId;
|
|
final vm2 = ref.watch(
|
|
proxyGroupProvider.select(
|
|
(state) => VM2(state.includeAllProxies ?? false, state.proxies ?? []),
|
|
),
|
|
);
|
|
final dismissItem = ref.watch(itemProvider(key));
|
|
final includeAllProxies = vm2.a;
|
|
final proxyNames = vm2.b;
|
|
final proxyTypeMap =
|
|
ref.watch(
|
|
clashConfigProvider(
|
|
profileId,
|
|
).select((state) => state.value?.proxyTypeMap),
|
|
) ??
|
|
{};
|
|
final isBottomSheet =
|
|
SheetProvider.of(context)?.type == SheetType.bottomSheet;
|
|
return SizedBox(
|
|
height: isBottomSheet
|
|
? appController.viewSize.height * 0.85
|
|
: double.maxFinite,
|
|
child: AdaptiveSheetScaffold(
|
|
title: '编辑代理',
|
|
sheetTransparentToolBar: true,
|
|
body: CustomScrollView(
|
|
slivers: [
|
|
SliverToBoxAdapter(
|
|
child: SizedBox(height: context.sheetTopPadding + 8),
|
|
),
|
|
SliverPadding(
|
|
padding: EdgeInsets.symmetric(horizontal: 16),
|
|
sliver: SliverToBoxAdapter(
|
|
child: CommonCard(
|
|
radius: 20,
|
|
type: CommonCardType.filled,
|
|
child: ListItem.switchItem(
|
|
minTileHeight: 54,
|
|
title: Text('包含所有代理'),
|
|
delegate: SwitchDelegate(
|
|
value: includeAllProxies,
|
|
onChanged: (_) {
|
|
_handleChangeIncludeAllProxies();
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
SliverToBoxAdapter(child: SizedBox(height: 8)),
|
|
SliverPadding(
|
|
padding: EdgeInsets.symmetric(horizontal: 16),
|
|
sliver: SliverToBoxAdapter(
|
|
child: InfoHeader(
|
|
info: Info(label: '节点'),
|
|
actions: [
|
|
CommonMinFilledButtonTheme(
|
|
child: FilledButton.tonal(
|
|
onPressed: _handleToAddProxiesView,
|
|
child: Text('添加'),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
if (proxyNames.isNotEmpty)
|
|
SliverReorderableList(
|
|
findChildIndexCallback: (Key key) {
|
|
final String keyValue = (key as dynamic).subKey?.value;
|
|
final index = proxyNames.indexOf(keyValue);
|
|
return index;
|
|
},
|
|
itemBuilder: (_, index) {
|
|
final proxyName = proxyNames[index];
|
|
return _buildItem(
|
|
dismiss: dismissItem == proxyName,
|
|
proxyName: proxyName,
|
|
proxyType: proxyTypeMap[proxyName],
|
|
index: index,
|
|
length: proxyNames.length,
|
|
);
|
|
},
|
|
itemCount: proxyNames.length,
|
|
onReorder: (int oldIndex, int newIndex) {
|
|
_handleReorder(oldIndex, newIndex);
|
|
},
|
|
)
|
|
else
|
|
SliverFillRemaining(child: NullStatus(label: '代理为空')),
|
|
SliverToBoxAdapter(child: SizedBox(height: 16)),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _AddProxiesView extends ConsumerStatefulWidget {
|
|
const _AddProxiesView();
|
|
|
|
@override
|
|
ConsumerState<_AddProxiesView> createState() => _AddProxiesViewState();
|
|
}
|
|
|
|
class _AddProxiesViewState extends ConsumerState<_AddProxiesView>
|
|
with UniqueKeyStateMixin {
|
|
void _handleAdd(String name) {
|
|
final dismissItem = ref.read(itemProvider(key));
|
|
if (dismissItem != null) {
|
|
return;
|
|
}
|
|
ref.read(itemProvider(key).notifier).value = name;
|
|
}
|
|
|
|
void _handleRealAdd(String name) {
|
|
ref
|
|
.read(proxyGroupProvider.notifier)
|
|
.update(
|
|
(state) => state.copyWith(proxies: [...state.proxies ?? [], name]),
|
|
);
|
|
ref.read(itemProvider(key).notifier).value = null;
|
|
}
|
|
|
|
Widget _buildItem({
|
|
required String title,
|
|
required String subtitle,
|
|
required ItemPosition position,
|
|
required bool dismiss,
|
|
required VoidCallback onAdd,
|
|
required VoidCallback onDismissed,
|
|
}) {
|
|
return ExternalDismissible(
|
|
key: ValueKey(title),
|
|
dismiss: dismiss,
|
|
onDismissed: onDismissed,
|
|
child: Padding(
|
|
padding: EdgeInsets.symmetric(horizontal: 16),
|
|
child: ItemPositionProvider(
|
|
position: position,
|
|
child: DecorationListItem(
|
|
minVerticalPadding: 8,
|
|
title: Text(title),
|
|
subtitle: Text(subtitle),
|
|
trailing: CommonMinIconButtonTheme(
|
|
child: IconButton.filledTonal(
|
|
onPressed: onAdd,
|
|
icon: Icon(Icons.add, size: 18),
|
|
padding: EdgeInsets.zero,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final dismissItem = ref.watch(itemProvider(key));
|
|
final isBottomSheet =
|
|
SheetProvider.of(context)?.type == SheetType.bottomSheet;
|
|
final profileId = ProfileIdProvider.of(context)!.profileId;
|
|
final allProxiesAndProxyGroups = ref.watch(
|
|
clashConfigProvider(profileId).select(
|
|
(state) =>
|
|
VM2(state.value?.proxies ?? [], state.value?.proxyGroups ?? []),
|
|
),
|
|
);
|
|
final allProxies = allProxiesAndProxyGroups.a;
|
|
final allProxyGroups = allProxiesAndProxyGroups.b;
|
|
final proxyNames = ref.watch(
|
|
proxyGroupProvider.select((state) {
|
|
return [...?state.proxies, state.name];
|
|
}),
|
|
);
|
|
final proxies = allProxies
|
|
.where((item) => !proxyNames.contains(item.name))
|
|
.toList();
|
|
final proxyGroups = allProxyGroups
|
|
.where((item) => !proxyNames.contains(item.name))
|
|
.toList();
|
|
return SizedBox(
|
|
height: isBottomSheet
|
|
? appController.viewSize.height * 0.80
|
|
: double.maxFinite,
|
|
child: AdaptiveSheetScaffold(
|
|
sheetTransparentToolBar: true,
|
|
title: '添加代理',
|
|
body: proxies.isEmpty && proxyGroups.isEmpty
|
|
? NullStatus(label: appLocalizations.noData)
|
|
: CustomScrollView(
|
|
slivers: [
|
|
SliverToBoxAdapter(
|
|
child: SizedBox(height: context.sheetTopPadding),
|
|
),
|
|
if (proxyGroups.isNotEmpty) ...[
|
|
SliverPadding(
|
|
padding: EdgeInsets.symmetric(horizontal: 16),
|
|
sliver: SliverToBoxAdapter(
|
|
child: InfoHeader(info: Info(label: '策略组')),
|
|
),
|
|
),
|
|
SliverList(
|
|
delegate: SliverChildBuilderDelegate((_, index) {
|
|
final proxyGroup = proxyGroups[index];
|
|
final position = ItemPosition.get(
|
|
index,
|
|
proxyGroups.length,
|
|
);
|
|
return _buildItem(
|
|
title: proxyGroup.name,
|
|
subtitle: proxyGroup.type.value,
|
|
position: position,
|
|
dismiss: dismissItem == proxyGroup.name,
|
|
onAdd: () {
|
|
_handleAdd(proxyGroup.name);
|
|
},
|
|
onDismissed: () {
|
|
_handleRealAdd(proxyGroup.name);
|
|
},
|
|
);
|
|
}, childCount: proxyGroups.length),
|
|
),
|
|
SliverToBoxAdapter(child: SizedBox(height: 8)),
|
|
],
|
|
if (proxies.isNotEmpty) ...[
|
|
SliverPadding(
|
|
padding: EdgeInsets.symmetric(horizontal: 16),
|
|
sliver: SliverToBoxAdapter(
|
|
child: InfoHeader(info: Info(label: '代理')),
|
|
),
|
|
),
|
|
SliverList(
|
|
delegate: SliverChildBuilderDelegate((_, index) {
|
|
final proxy = proxies[index];
|
|
final position = ItemPosition.get(
|
|
index,
|
|
proxies.length,
|
|
);
|
|
return _buildItem(
|
|
title: proxy.name,
|
|
subtitle: proxy.type,
|
|
position: position,
|
|
dismiss: dismissItem == proxy.name,
|
|
onAdd: () {
|
|
_handleAdd(proxy.name);
|
|
},
|
|
onDismissed: () {
|
|
_handleRealAdd(proxy.name);
|
|
},
|
|
);
|
|
}, childCount: proxies.length),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|