cache
This commit is contained in:
@@ -83,6 +83,14 @@ class ProxyGroupsDao extends DatabaseAccessor<Database>
|
||||
return stmt.count;
|
||||
}
|
||||
|
||||
Selectable<ProxyGroup> get(int profileId, String name) {
|
||||
final stmt = proxyGroups.select();
|
||||
stmt.where(
|
||||
(row) => row.profileId.equals(profileId) & row.name.equals(name),
|
||||
);
|
||||
return stmt.map((item) => item.toProxyGroup());
|
||||
}
|
||||
|
||||
Future<int> order(
|
||||
int profileId, {
|
||||
required ProxyGroup proxyGroup,
|
||||
|
||||
@@ -333,6 +333,17 @@ class ProxyGroups extends _$ProxyGroups with AsyncNotifierMixin {
|
||||
List<ProxyGroup> get value => state.value ?? [];
|
||||
}
|
||||
|
||||
@Riverpod(name: 'proxyGroupProvider')
|
||||
class ProxyGroupProvider extends _$ProxyGroupProvider with AsyncNotifierMixin {
|
||||
@override
|
||||
Stream<ProxyGroup> build(int profileId, String name) {
|
||||
return database.proxyGroupsDao.get(profileId, name).watchSingle();
|
||||
}
|
||||
|
||||
@override
|
||||
ProxyGroup get value => state.requireValue;
|
||||
}
|
||||
|
||||
@riverpod
|
||||
class ProfileDisabledRuleIds extends _$ProfileDisabledRuleIds
|
||||
with AsyncNotifierMixin {
|
||||
|
||||
@@ -749,6 +749,98 @@ abstract class _$ProxyGroups extends $StreamNotifier<List<ProxyGroup>> {
|
||||
}
|
||||
}
|
||||
|
||||
@ProviderFor(ProxyGroupProvider)
|
||||
const proxyGroupProvider = ProxyGroupProviderFamily._();
|
||||
|
||||
final class ProxyGroupProviderProvider
|
||||
extends $StreamNotifierProvider<ProxyGroupProvider, ProxyGroup> {
|
||||
const ProxyGroupProviderProvider._({
|
||||
required ProxyGroupProviderFamily super.from,
|
||||
required (int, String) super.argument,
|
||||
}) : super(
|
||||
retry: null,
|
||||
name: r'proxyGroupProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$proxyGroupProviderHash();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return r'proxyGroupProvider'
|
||||
''
|
||||
'$argument';
|
||||
}
|
||||
|
||||
@$internal
|
||||
@override
|
||||
ProxyGroupProvider create() => ProxyGroupProvider();
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is ProxyGroupProviderProvider && other.argument == argument;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return argument.hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
String _$proxyGroupProviderHash() =>
|
||||
r'60ec3f8a2a09e3ed1ac73d724b1ade295309897d';
|
||||
|
||||
final class ProxyGroupProviderFamily extends $Family
|
||||
with
|
||||
$ClassFamilyOverride<
|
||||
ProxyGroupProvider,
|
||||
AsyncValue<ProxyGroup>,
|
||||
ProxyGroup,
|
||||
Stream<ProxyGroup>,
|
||||
(int, String)
|
||||
> {
|
||||
const ProxyGroupProviderFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'proxyGroupProvider',
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
isAutoDispose: true,
|
||||
);
|
||||
|
||||
ProxyGroupProviderProvider call(int profileId, String name) =>
|
||||
ProxyGroupProviderProvider._(argument: (profileId, name), from: this);
|
||||
|
||||
@override
|
||||
String toString() => r'proxyGroupProvider';
|
||||
}
|
||||
|
||||
abstract class _$ProxyGroupProvider extends $StreamNotifier<ProxyGroup> {
|
||||
late final _$args = ref.$arg as (int, String);
|
||||
int get profileId => _$args.$1;
|
||||
String get name => _$args.$2;
|
||||
|
||||
Stream<ProxyGroup> build(int profileId, String name);
|
||||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build(_$args.$1, _$args.$2);
|
||||
final ref = this.ref as $Ref<AsyncValue<ProxyGroup>, ProxyGroup>;
|
||||
final element =
|
||||
ref.element
|
||||
as $ClassProviderElement<
|
||||
AnyNotifier<AsyncValue<ProxyGroup>, ProxyGroup>,
|
||||
AsyncValue<ProxyGroup>,
|
||||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
}
|
||||
}
|
||||
|
||||
@ProviderFor(ProfileDisabledRuleIds)
|
||||
const profileDisabledRuleIdsProvider = ProfileDisabledRuleIdsFamily._();
|
||||
|
||||
|
||||
@@ -2443,6 +2443,81 @@ final class ScriptFamily extends $Family
|
||||
String toString() => r'scriptProvider';
|
||||
}
|
||||
|
||||
@ProviderFor(clashConfig)
|
||||
const clashConfigProvider = ClashConfigFamily._();
|
||||
|
||||
final class ClashConfigProvider
|
||||
extends
|
||||
$FunctionalProvider<
|
||||
AsyncValue<ClashConfig>,
|
||||
ClashConfig,
|
||||
FutureOr<ClashConfig>
|
||||
>
|
||||
with $FutureModifier<ClashConfig>, $FutureProvider<ClashConfig> {
|
||||
const ClashConfigProvider._({
|
||||
required ClashConfigFamily super.from,
|
||||
required int super.argument,
|
||||
}) : super(
|
||||
retry: null,
|
||||
name: r'clashConfigProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$clashConfigHash();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return r'clashConfigProvider'
|
||||
''
|
||||
'($argument)';
|
||||
}
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$FutureProviderElement<ClashConfig> $createElement(
|
||||
$ProviderPointer pointer,
|
||||
) => $FutureProviderElement(pointer);
|
||||
|
||||
@override
|
||||
FutureOr<ClashConfig> create(Ref ref) {
|
||||
final argument = this.argument as int;
|
||||
return clashConfig(ref, argument);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is ClashConfigProvider && other.argument == argument;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return argument.hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
String _$clashConfigHash() => r'3987f6aee1131fe9b3978b914372b04ca2ae773c';
|
||||
|
||||
final class ClashConfigFamily extends $Family
|
||||
with $FunctionalFamilyOverride<FutureOr<ClashConfig>, int> {
|
||||
const ClashConfigFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'clashConfigProvider',
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
isAutoDispose: true,
|
||||
);
|
||||
|
||||
ClashConfigProvider call(int profileId) =>
|
||||
ClashConfigProvider._(argument: profileId, from: this);
|
||||
|
||||
@override
|
||||
String toString() => r'clashConfigProvider';
|
||||
}
|
||||
|
||||
@ProviderFor(setupState)
|
||||
const setupStateProvider = SetupStateFamily._();
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:dynamic_color/dynamic_color.dart';
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/core/controller.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
@@ -677,6 +678,13 @@ Future<Script?> script(Ref ref, int? scriptId) async {
|
||||
return script;
|
||||
}
|
||||
|
||||
@riverpod
|
||||
Future<ClashConfig> clashConfig(Ref ref, int profileId) async {
|
||||
final configMap = await coreController.getConfig(profileId);
|
||||
final clashConfig = ClashConfig.fromJson(configMap);
|
||||
return clashConfig;
|
||||
}
|
||||
|
||||
@riverpod
|
||||
Future<SetupState> setupState(Ref ref, int? profileId) async {
|
||||
final profile = ref.watch(profileProvider(profileId));
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
part of 'overwrite.dart';
|
||||
|
||||
class _CustomContent extends ConsumerWidget {
|
||||
final int profileId;
|
||||
const _CustomContent();
|
||||
|
||||
const _CustomContent(this.profileId);
|
||||
|
||||
void _handleUseDefault() async {
|
||||
final configMap = await coreController.getConfig(profileId);
|
||||
final clashConfig = ClashConfig.fromJson(configMap);
|
||||
void _handleUseDefault(WidgetRef ref, int profileId) async {
|
||||
final clashConfig = await ref.read(clashConfigProvider(profileId).future);
|
||||
await database.setProfileCustomData(
|
||||
profileId,
|
||||
clashConfig.proxyGroups,
|
||||
@@ -15,19 +12,28 @@ class _CustomContent extends ConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
void _handleToProxyGroupsView(BuildContext context) {
|
||||
void _handleToProxyGroupsView(BuildContext context, int profileId) {
|
||||
BaseNavigator.push(context, _CustomProxyGroupsView(profileId));
|
||||
}
|
||||
|
||||
void _handleToRulesView(BuildContext context) {
|
||||
void _handleToRulesView(BuildContext context, int profileId) {
|
||||
BaseNavigator.push(context, _CustomRulesView(profileId));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final profileId = ProfileIdProvider.of(context)!.profileId;
|
||||
final proxyGroupNum =
|
||||
ref.watch(proxyGroupsCountProvider(profileId)).value ?? -1;
|
||||
final ruleNum = ref.watch(customRulesCountProvider(profileId)).value ?? -1;
|
||||
final hasDefault = ref.watch(
|
||||
clashConfigProvider(profileId).select((state) {
|
||||
final clashConfig = state.value;
|
||||
return ((clashConfig?.proxyGroups.length ?? 0) +
|
||||
(clashConfig?.rules.length ?? 0)) >
|
||||
0;
|
||||
}),
|
||||
);
|
||||
return SliverMainAxisGroup(
|
||||
slivers: [
|
||||
SliverToBoxAdapter(child: SizedBox(height: 24)),
|
||||
@@ -41,7 +47,7 @@ class _CustomContent extends ConsumerWidget {
|
||||
child: _MoreActionButton(
|
||||
label: '代理组',
|
||||
onPressed: () {
|
||||
_handleToProxyGroupsView(context);
|
||||
_handleToProxyGroupsView(context, profileId);
|
||||
},
|
||||
trailing: Card.filled(
|
||||
shape: RoundedRectangleBorder(
|
||||
@@ -66,7 +72,7 @@ class _CustomContent extends ConsumerWidget {
|
||||
child: _MoreActionButton(
|
||||
label: '规则',
|
||||
onPressed: () {
|
||||
_handleToRulesView(context);
|
||||
_handleToRulesView(context, profileId);
|
||||
},
|
||||
trailing: Card.filled(
|
||||
shape: RoundedRectangleBorder(
|
||||
@@ -85,7 +91,7 @@ class _CustomContent extends ConsumerWidget {
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(child: SizedBox(height: 32)),
|
||||
if (proxyGroupNum == 0 && ruleNum == 0)
|
||||
if (proxyGroupNum == 0 && ruleNum == 0 && hasDefault)
|
||||
SliverFillRemaining(
|
||||
hasScrollBody: false,
|
||||
child: Align(
|
||||
@@ -97,7 +103,9 @@ class _CustomContent extends ConsumerWidget {
|
||||
actions: [
|
||||
CommonMinFilledButtonTheme(
|
||||
child: FilledButton.tonal(
|
||||
onPressed: _handleUseDefault,
|
||||
onPressed: () {
|
||||
_handleUseDefault(ref, profileId);
|
||||
},
|
||||
child: Text('一键填入'),
|
||||
),
|
||||
),
|
||||
@@ -105,517 +113,11 @@ class _CustomContent extends ConsumerWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
// SliverToBoxAdapter(child: SizedBox(height: 8)),
|
||||
// SliverToBoxAdapter(
|
||||
// child: Padding(
|
||||
// padding: EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||
// child: CommonCard(
|
||||
// radius: 18,
|
||||
// child: ListTile(
|
||||
// minTileHeight: 0,
|
||||
// minVerticalPadding: 0,
|
||||
// titleTextStyle: context.textTheme.bodyMedium?.toJetBrainsMono,
|
||||
// contentPadding: const EdgeInsets.symmetric(
|
||||
// horizontal: 16,
|
||||
// vertical: 16,
|
||||
// ),
|
||||
// title: Row(
|
||||
// crossAxisAlignment: CrossAxisAlignment.center,
|
||||
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
// children: [
|
||||
// Flexible(
|
||||
// child: Text('自定义规则', style: context.textTheme.bodyLarge),
|
||||
// ),
|
||||
// Icon(Icons.arrow_forward_ios, size: 18),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// onPressed: () {},
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CustomProxyGroupsView extends ConsumerWidget {
|
||||
final int profileId;
|
||||
|
||||
const _CustomProxyGroupsView(this.profileId);
|
||||
|
||||
void _handleReorder(WidgetRef ref, 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: 400,
|
||||
),
|
||||
builder: (context) {
|
||||
return _EditCustomProxyGroupNestedSheet(proxyGroup);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final proxyGroups = ref.watch(proxyGroupsProvider(profileId)).value ?? [];
|
||||
return CommonScaffold(
|
||||
title: '代理组',
|
||||
body: ReorderableListView.builder(
|
||||
buildDefaultDragHandles: false,
|
||||
padding: EdgeInsets.only(bottom: 16),
|
||||
itemBuilder: (_, index) {
|
||||
final proxyGroup = proxyGroups[index];
|
||||
return ReorderableDelayedDragStartListener(
|
||||
key: ValueKey(proxyGroup),
|
||||
index: index,
|
||||
child: Container(
|
||||
margin: EdgeInsets.symmetric(vertical: 4, horizontal: 16),
|
||||
child: CommonCard(
|
||||
radius: 16,
|
||||
padding: EdgeInsets.all(16),
|
||||
onPressed: () {
|
||||
_handleEditProxyGroup(context, proxyGroup);
|
||||
},
|
||||
child: ListTile(
|
||||
minTileHeight: 0,
|
||||
minVerticalPadding: 0,
|
||||
titleTextStyle: context.textTheme.bodyMedium?.toJetBrainsMono,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 4,
|
||||
vertical: 4,
|
||||
),
|
||||
title: Text(proxyGroup.name),
|
||||
subtitle: Text(proxyGroup.type.name),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
itemCount: proxyGroups.length,
|
||||
onReorder: (oldIndex, newIndex) {
|
||||
_handleReorder(ref, oldIndex, newIndex);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _EditCustomProxyGroupNestedSheet extends StatelessWidget {
|
||||
final ProxyGroup proxyGroup;
|
||||
|
||||
const _EditCustomProxyGroupNestedSheet(this.proxyGroup);
|
||||
|
||||
Future<void> _handlePop(
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final GlobalKey<NavigatorState> nestedNavigatorKey = GlobalKey();
|
||||
final nestedNavigator = Navigator(
|
||||
key: nestedNavigatorKey,
|
||||
onGenerateInitialRoutes: (navigator, initialRoute) {
|
||||
return [
|
||||
PagedSheetRoute(
|
||||
builder: (context) {
|
||||
return _EditCustomProxyGroupView(proxyGroup);
|
||||
},
|
||||
),
|
||||
];
|
||||
},
|
||||
);
|
||||
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 {
|
||||
_handlePop(context, nestedNavigatorKey.currentState);
|
||||
},
|
||||
),
|
||||
),
|
||||
SheetViewport(
|
||||
child: PagedSheet(
|
||||
decoration: MaterialSheetDecoration(
|
||||
size: SheetSize.stretch,
|
||||
borderRadius: sheetProvider.type == SheetType.bottomSheet
|
||||
? BorderRadius.vertical(top: Radius.circular(28))
|
||||
: BorderRadius.zero,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
),
|
||||
navigator: nestedNavigator,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _EditCustomProxyGroupView extends ConsumerStatefulWidget {
|
||||
final ProxyGroup proxyGroup;
|
||||
|
||||
const _EditCustomProxyGroupView(this.proxyGroup);
|
||||
|
||||
@override
|
||||
ConsumerState createState() => _EditCustomProxyGroupViewState();
|
||||
}
|
||||
|
||||
class _EditCustomProxyGroupViewState
|
||||
extends ConsumerState<_EditCustomProxyGroupView> {
|
||||
final _nameController = TextEditingController();
|
||||
final _hideController = ValueNotifier<bool>(false);
|
||||
final _disableUDPController = ValueNotifier<bool>(false);
|
||||
final _proxiesController = ValueNotifier<List<String>>([]);
|
||||
final _useController = ValueNotifier<List<String>>([]);
|
||||
final _typeController = ValueNotifier<GroupType>(GroupType.Selector);
|
||||
final _allProxiesController = ValueNotifier<bool>(false);
|
||||
final _allProviderController = ValueNotifier<bool>(false);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final proxyGroup = widget.proxyGroup;
|
||||
_nameController.text = proxyGroup.name;
|
||||
_hideController.value = proxyGroup.hidden ?? false;
|
||||
_disableUDPController.value = proxyGroup.disableUDP ?? false;
|
||||
_typeController.value = proxyGroup.type;
|
||||
_proxiesController.value = proxyGroup.proxies ?? [];
|
||||
_useController.value = proxyGroup.use ?? [];
|
||||
if (proxyGroup.includeAll == true) {
|
||||
_allProxiesController.value = true;
|
||||
_allProviderController.value = true;
|
||||
} else {
|
||||
_allProxiesController.value = proxyGroup.includeAllProxies ?? false;
|
||||
_allProviderController.value = proxyGroup.includeAllProviders ?? false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_nameController.dispose();
|
||||
_hideController.dispose();
|
||||
_disableUDPController.dispose();
|
||||
_typeController.dispose();
|
||||
_proxiesController.dispose();
|
||||
_useController.dispose();
|
||||
_allProxiesController.dispose();
|
||||
_allProviderController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _showTypeOptions() async {
|
||||
final value = await globalState.showCommonDialog<GroupType>(
|
||||
child: OptionsDialog<GroupType>(
|
||||
title: '类型',
|
||||
options: GroupType.values,
|
||||
textBuilder: (item) => item.name,
|
||||
value: _typeController.value,
|
||||
),
|
||||
);
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
_typeController.value = value;
|
||||
}
|
||||
|
||||
Widget _buildItem({
|
||||
required Widget title,
|
||||
Widget? trailing,
|
||||
final VoidCallback? onPressed,
|
||||
}) {
|
||||
return CommonInputListItem(
|
||||
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 _handleToProxies() {
|
||||
final isBottomSheet =
|
||||
SheetProvider.of(context)?.type == SheetType.bottomSheet;
|
||||
Navigator.of(context).push(
|
||||
PagedSheetRoute(
|
||||
builder: (context) => SizedBox(
|
||||
height: isBottomSheet
|
||||
? appController.viewSize.height * 0.85
|
||||
: double.maxFinite,
|
||||
child: AdaptiveSheetScaffold(
|
||||
title: '选择代理',
|
||||
body: Center(child: Text('123')),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isBottomSheet =
|
||||
SheetProvider.of(context)?.type == SheetType.bottomSheet;
|
||||
return AdaptiveSheetScaffold(
|
||||
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),
|
||||
children: [
|
||||
generateSectionV3(
|
||||
title: '通用',
|
||||
items: [
|
||||
_buildItem(
|
||||
title: Text('名称'),
|
||||
trailing: TextFormField(
|
||||
controller: _nameController,
|
||||
textAlign: TextAlign.end,
|
||||
decoration: InputDecoration.collapsed(
|
||||
border: NoInputBorder(),
|
||||
hintText: '输入代理组名称',
|
||||
),
|
||||
),
|
||||
),
|
||||
_buildItem(
|
||||
title: Text('类型'),
|
||||
onPressed: () {
|
||||
_showTypeOptions();
|
||||
},
|
||||
trailing: ValueListenableBuilder(
|
||||
valueListenable: _typeController,
|
||||
builder: (_, type, _) {
|
||||
return Text(type.name);
|
||||
},
|
||||
),
|
||||
),
|
||||
_buildItem(title: Text('图标')),
|
||||
_buildItem(
|
||||
title: Text('从列表中隐藏'),
|
||||
onPressed: () {
|
||||
_hideController.value = !_hideController.value;
|
||||
},
|
||||
trailing: ValueListenableBuilder(
|
||||
valueListenable: _hideController,
|
||||
builder: (_, value, _) {
|
||||
return Switch(
|
||||
value: value,
|
||||
onChanged: (value) {
|
||||
_hideController.value = value;
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
_buildItem(
|
||||
title: Text('禁用UDP'),
|
||||
onPressed: () {
|
||||
_disableUDPController.value = !_disableUDPController.value;
|
||||
},
|
||||
trailing: ValueListenableBuilder(
|
||||
valueListenable: _disableUDPController,
|
||||
builder: (_, value, _) {
|
||||
return Switch(
|
||||
value: value,
|
||||
onChanged: (value) {
|
||||
_disableUDPController.value = value;
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
generateSectionV3(
|
||||
title: '节点',
|
||||
items: [
|
||||
_buildItem(
|
||||
title: Text('选择代理'),
|
||||
trailing: ValueListenableBuilder(
|
||||
valueListenable: _proxiesController,
|
||||
builder: (_, proxies, _) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
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.arrow_forward_ios),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
onPressed: _handleToProxies,
|
||||
),
|
||||
_buildItem(
|
||||
title: Text('选择代理集'),
|
||||
trailing: Icon(Icons.arrow_forward_ios),
|
||||
),
|
||||
_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('删除'),
|
||||
onPressed: () {
|
||||
_disableUDPController.value = !_disableUDPController.value;
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
title: '编辑代理组',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CustomRulesView extends ConsumerStatefulWidget {
|
||||
final int profileId;
|
||||
|
||||
@@ -628,9 +130,16 @@ class _CustomRulesView extends ConsumerStatefulWidget {
|
||||
class _CustomRulesViewState extends ConsumerState<_CustomRulesView> {
|
||||
final _key = utils.id;
|
||||
|
||||
int get _profileId => widget.profileId;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
void _handleReorder(int oldIndex, int newIndex) {
|
||||
ref
|
||||
.read(profileCustomRulesProvider(widget.profileId).notifier)
|
||||
.read(profileCustomRulesProvider(_profileId).notifier)
|
||||
.order(oldIndex, newIndex);
|
||||
}
|
||||
|
||||
@@ -645,7 +154,7 @@ class _CustomRulesViewState extends ConsumerState<_CustomRulesView> {
|
||||
void _handleSelectAll() {
|
||||
final ids =
|
||||
ref
|
||||
.read(profileCustomRulesProvider(widget.profileId))
|
||||
.read(profileCustomRulesProvider(_profileId))
|
||||
.value
|
||||
?.map((item) => item.id)
|
||||
.toSet() ??
|
||||
@@ -667,20 +176,14 @@ class _CustomRulesViewState extends ConsumerState<_CustomRulesView> {
|
||||
}
|
||||
final selectedRules = ref.read(selectedItemsProvider(_key));
|
||||
ref
|
||||
.read(profileCustomRulesProvider(widget.profileId).notifier)
|
||||
.read(profileCustomRulesProvider(_profileId).notifier)
|
||||
.delAll(selectedRules.cast<int>());
|
||||
ref.read(selectedItemsProvider(_key).notifier).value = {};
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(context) {
|
||||
final rules =
|
||||
ref.watch(profileCustomRulesProvider(widget.profileId)).value ?? [];
|
||||
final rules = ref.watch(profileCustomRulesProvider(_profileId)).value ?? [];
|
||||
final selectedRules = ref.watch(selectedItemsProvider(_key));
|
||||
return CommonScaffold(
|
||||
title: appLocalizations.rule,
|
||||
|
||||
592
lib/views/profiles/overwrite/custom_proxies.dart
Normal file
592
lib/views/profiles/overwrite/custom_proxies.dart
Normal file
@@ -0,0 +1,592 @@
|
||||
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: 400,
|
||||
),
|
||||
builder: (context) {
|
||||
return ProxyGroupProvider(
|
||||
proxyGroup: proxyGroup,
|
||||
child: _EditProxyGroupNestedSheet(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final proxyGroups = ref.watch(proxyGroupsProvider(profileId)).value ?? [];
|
||||
return CommonScaffold(
|
||||
title: '代理组',
|
||||
body: ReorderableListView.builder(
|
||||
buildDefaultDragHandles: false,
|
||||
padding: EdgeInsets.only(bottom: 16),
|
||||
itemBuilder: (_, index) {
|
||||
final proxyGroup = proxyGroups[index];
|
||||
return ReorderableDelayedDragStartListener(
|
||||
key: ValueKey(proxyGroup),
|
||||
index: index,
|
||||
child: Container(
|
||||
margin: EdgeInsets.symmetric(vertical: 4, horizontal: 16),
|
||||
child: CommonCard(
|
||||
radius: 16,
|
||||
padding: EdgeInsets.all(16),
|
||||
onPressed: () {
|
||||
_handleEditProxyGroup(context, proxyGroup);
|
||||
},
|
||||
child: ListTile(
|
||||
minTileHeight: 0,
|
||||
minVerticalPadding: 0,
|
||||
titleTextStyle: context.textTheme.bodyMedium?.toJetBrainsMono,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 4,
|
||||
vertical: 4,
|
||||
),
|
||||
title: Text(proxyGroup.name),
|
||||
subtitle: Text(proxyGroup.type.name),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
itemCount: proxyGroups.length,
|
||||
onReorder: (oldIndex, newIndex) {
|
||||
_handleReorder(ref, profileId, oldIndex, newIndex);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _EditProxyGroupNestedSheet extends StatelessWidget {
|
||||
const _EditProxyGroupNestedSheet();
|
||||
|
||||
Future<void> _handlePop(
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@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 {
|
||||
_handlePop(context, nestedNavigatorKey.currentState);
|
||||
},
|
||||
),
|
||||
),
|
||||
SheetViewport(
|
||||
child: PagedSheet(
|
||||
decoration: MaterialSheetDecoration(
|
||||
size: SheetSize.stretch,
|
||||
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> {
|
||||
late ProxyGroup _proxyGroup;
|
||||
|
||||
final _nameController = TextEditingController();
|
||||
final _hideController = ValueNotifier<bool>(false);
|
||||
final _disableUDPController = ValueNotifier<bool>(false);
|
||||
final _proxiesController = ValueNotifier<List<String>>([]);
|
||||
final _useController = ValueNotifier<List<String>>([]);
|
||||
final _typeController = ValueNotifier<GroupType>(GroupType.Selector);
|
||||
final _allProxiesController = ValueNotifier<bool>(false);
|
||||
final _allProviderController = ValueNotifier<bool>(false);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_nameController.text = _proxyGroup.name;
|
||||
_hideController.value = _proxyGroup.hidden ?? false;
|
||||
_disableUDPController.value = _proxyGroup.disableUDP ?? false;
|
||||
_typeController.value = _proxyGroup.type;
|
||||
_proxiesController.value = _proxyGroup.proxies ?? [];
|
||||
_useController.value = _proxyGroup.use ?? [];
|
||||
if (_proxyGroup.includeAll == true) {
|
||||
_allProxiesController.value = true;
|
||||
_allProviderController.value = true;
|
||||
} else {
|
||||
_allProxiesController.value = _proxyGroup.includeAllProxies ?? false;
|
||||
_allProviderController.value = _proxyGroup.includeAllProviders ?? false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
_proxyGroup = ProxyGroupProvider.of(context)!.proxyGroup;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_nameController.dispose();
|
||||
_hideController.dispose();
|
||||
_disableUDPController.dispose();
|
||||
_typeController.dispose();
|
||||
_proxiesController.dispose();
|
||||
_useController.dispose();
|
||||
_allProxiesController.dispose();
|
||||
_allProviderController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _showTypeOptions() async {
|
||||
final value = await globalState.showCommonDialog<GroupType>(
|
||||
child: OptionsDialog<GroupType>(
|
||||
title: '类型',
|
||||
options: GroupType.values,
|
||||
textBuilder: (item) => item.name,
|
||||
value: _typeController.value,
|
||||
),
|
||||
);
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
_typeController.value = value;
|
||||
}
|
||||
|
||||
Widget _buildItem({
|
||||
required Widget title,
|
||||
Widget? trailing,
|
||||
final VoidCallback? onPressed,
|
||||
}) {
|
||||
return CommonInputListItem(
|
||||
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() {
|
||||
return _buildItem(
|
||||
title: Text('选择代理集'),
|
||||
trailing: ValueListenableBuilder(
|
||||
valueListenable: _allProviderController,
|
||||
builder: (_, allProviders, _) {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: _useController,
|
||||
builder: (_, use, _) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 2,
|
||||
children: [
|
||||
!allProviders
|
||||
? 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() {
|
||||
return _buildItem(
|
||||
title: Text('选择代理'),
|
||||
trailing: ValueListenableBuilder(
|
||||
valueListenable: _allProxiesController,
|
||||
builder: (_, allProxies, _) {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: _proxiesController,
|
||||
builder: (_, proxies, _) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 2,
|
||||
children: [
|
||||
!allProxies
|
||||
? 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 _buildGroupTypeItem() {
|
||||
return _buildItem(
|
||||
title: Text('类型'),
|
||||
onPressed: () {
|
||||
_showTypeOptions();
|
||||
},
|
||||
trailing: ValueListenableBuilder(
|
||||
valueListenable: _typeController,
|
||||
builder: (_, type, _) {
|
||||
return Text(type.name);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
_proxyGroup = ProxyGroupProvider.of(context)!.proxyGroup;
|
||||
final isBottomSheet =
|
||||
SheetProvider.of(context)?.type == SheetType.bottomSheet;
|
||||
return AdaptiveSheetScaffold(
|
||||
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),
|
||||
children: [
|
||||
generateSectionV3(
|
||||
title: '通用',
|
||||
items: [
|
||||
_buildItem(
|
||||
title: Text('名称'),
|
||||
trailing: TextFormField(
|
||||
controller: _nameController,
|
||||
textAlign: TextAlign.end,
|
||||
decoration: InputDecoration.collapsed(
|
||||
border: NoInputBorder(),
|
||||
hintText: '输入代理组名称',
|
||||
),
|
||||
),
|
||||
),
|
||||
_buildGroupTypeItem(),
|
||||
_buildItem(title: Text('图标')),
|
||||
_buildItem(
|
||||
title: Text('从列表中隐藏'),
|
||||
onPressed: () {
|
||||
_hideController.value = !_hideController.value;
|
||||
},
|
||||
trailing: ValueListenableBuilder(
|
||||
valueListenable: _hideController,
|
||||
builder: (_, value, _) {
|
||||
return Switch(
|
||||
value: value,
|
||||
onChanged: (value) {
|
||||
_hideController.value = value;
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
_buildItem(
|
||||
title: Text('禁用UDP'),
|
||||
onPressed: () {
|
||||
_disableUDPController.value = !_disableUDPController.value;
|
||||
},
|
||||
trailing: ValueListenableBuilder(
|
||||
valueListenable: _disableUDPController,
|
||||
builder: (_, value, _) {
|
||||
return Switch(
|
||||
value: value,
|
||||
onChanged: (value) {
|
||||
_disableUDPController.value = value;
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
generateSectionV3(
|
||||
title: '节点',
|
||||
items: [
|
||||
_buildProxiesItem(),
|
||||
_buildProvidersItem(),
|
||||
_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('删除'),
|
||||
onPressed: () {
|
||||
_disableUDPController.value = !_disableUDPController.value;
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
title: '编辑代理组',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _EditProxiesView extends StatefulWidget {
|
||||
const _EditProxiesView();
|
||||
|
||||
@override
|
||||
State<_EditProxiesView> createState() => _EditProxiesViewState();
|
||||
}
|
||||
|
||||
class _EditProxiesViewState extends State<_EditProxiesView> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isBottomSheet =
|
||||
SheetProvider.of(context)?.type == SheetType.bottomSheet;
|
||||
return SizedBox(
|
||||
height: isBottomSheet
|
||||
? appController.viewSize.height * 0.85
|
||||
: double.maxFinite,
|
||||
child: AdaptiveSheetScaffold(
|
||||
title: '选择代理',
|
||||
body: CustomScrollView(
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 0,
|
||||
).copyWith(top: 16),
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: CommonCard(
|
||||
radius: 20,
|
||||
type: CommonCardType.filled,
|
||||
child: ListItem.switchItem(
|
||||
minTileHeight: 54,
|
||||
title: Text('包含所有代理'),
|
||||
delegate: SwitchDelegate(value: false, onChanged: (_) {}),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:smooth_sheets/smooth_sheets.dart';
|
||||
|
||||
part 'custom.dart';
|
||||
part 'custom_proxies.dart';
|
||||
part 'script.dart';
|
||||
part 'standard.dart';
|
||||
part 'widgets.dart';
|
||||
|
||||
class OverwriteView extends ConsumerStatefulWidget {
|
||||
@@ -45,19 +48,20 @@ class _OverwriteViewState extends ConsumerState<OverwriteView> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CommonScaffold(
|
||||
title: appLocalizations.override,
|
||||
actions: [
|
||||
CommonMinFilledButtonTheme(
|
||||
child: FilledButton(
|
||||
onPressed: _handlePreview,
|
||||
child: Text(appLocalizations.preview),
|
||||
return ProfileIdProvider(
|
||||
profileId: widget.profileId,
|
||||
child: CommonScaffold(
|
||||
title: appLocalizations.override,
|
||||
actions: [
|
||||
CommonMinFilledButtonTheme(
|
||||
child: FilledButton(
|
||||
onPressed: _handlePreview,
|
||||
child: Text(appLocalizations.preview),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
],
|
||||
body: CustomScrollView(
|
||||
slivers: [_Title(widget.profileId), _Content(widget.profileId)],
|
||||
SizedBox(width: 8),
|
||||
],
|
||||
body: CustomScrollView(slivers: [_Title(), _Content()]),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -70,9 +74,7 @@ class _OverwriteViewState extends ConsumerState<OverwriteView> {
|
||||
}
|
||||
|
||||
class _Title extends ConsumerWidget {
|
||||
final int profileId;
|
||||
|
||||
const _Title(this.profileId);
|
||||
const _Title();
|
||||
|
||||
String _getTitle(OverwriteType type) {
|
||||
return switch (type) {
|
||||
@@ -98,7 +100,7 @@ class _Title extends ConsumerWidget {
|
||||
};
|
||||
}
|
||||
|
||||
void _handleChangeType(WidgetRef ref, OverwriteType type) {
|
||||
void _handleChangeType(WidgetRef ref, int profileId, OverwriteType type) {
|
||||
ref.read(profilesProvider.notifier).updateProfile(profileId, (state) {
|
||||
return state.copyWith(overwriteType: type);
|
||||
});
|
||||
@@ -106,6 +108,7 @@ class _Title extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(context, ref) {
|
||||
final profileId = ProfileIdProvider.of(context)!.profileId;
|
||||
final overwriteType = ref.watch(overwriteTypeProvider(profileId));
|
||||
return SliverToBoxAdapter(
|
||||
child: Column(
|
||||
@@ -122,7 +125,7 @@ class _Title extends ConsumerWidget {
|
||||
CommonCard(
|
||||
isSelected: overwriteType == type,
|
||||
onPressed: () {
|
||||
_handleChangeType(ref, type);
|
||||
_handleChangeType(ref, profileId, type);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
@@ -157,365 +160,17 @@ class _Title extends ConsumerWidget {
|
||||
}
|
||||
|
||||
class _Content extends ConsumerWidget {
|
||||
final int profileId;
|
||||
|
||||
const _Content(this.profileId);
|
||||
const _Content();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final profileId = ProfileIdProvider.of(context)!.profileId;
|
||||
final overwriteType = ref.watch(overwriteTypeProvider(profileId));
|
||||
ref.listen(clashConfigProvider(profileId), (_, _) {});
|
||||
return switch (overwriteType) {
|
||||
OverwriteType.standard => _StandardContent(profileId),
|
||||
OverwriteType.script => _ScriptContent(profileId),
|
||||
OverwriteType.custom => _CustomContent(profileId),
|
||||
OverwriteType.standard => _StandardContent(),
|
||||
OverwriteType.script => _ScriptContent(),
|
||||
OverwriteType.custom => _CustomContent(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class _StandardContent extends ConsumerStatefulWidget {
|
||||
final int profileId;
|
||||
|
||||
const _StandardContent(this.profileId);
|
||||
|
||||
@override
|
||||
ConsumerState createState() => _StandardContentState();
|
||||
}
|
||||
|
||||
class _StandardContentState extends ConsumerState<_StandardContent> {
|
||||
final _key = utils.id;
|
||||
|
||||
Future<void> _handleAddOrUpdate([Rule? rule]) async {
|
||||
final res = await globalState.showCommonDialog<Rule>(
|
||||
child: AddOrEditRuleDialog(rule: rule),
|
||||
);
|
||||
if (res == null) {
|
||||
return;
|
||||
}
|
||||
ref.read(profileAddedRulesProvider(widget.profileId).notifier).put(res);
|
||||
}
|
||||
|
||||
void _handleSelected(int ruleId) {
|
||||
ref.read(selectedItemsProvider(_key).notifier).update((selectedRules) {
|
||||
final newSelectedRules = Set<int>.from(selectedRules)
|
||||
..addOrRemove(ruleId);
|
||||
return newSelectedRules;
|
||||
});
|
||||
}
|
||||
|
||||
void _handleSelectAll() {
|
||||
final ids =
|
||||
ref
|
||||
.read(profileAddedRulesProvider(widget.profileId))
|
||||
.value
|
||||
?.map((item) => item.id)
|
||||
.toSet() ??
|
||||
{};
|
||||
ref.read(selectedItemsProvider(_key).notifier).update((selected) {
|
||||
return selected.containsAll(ids) ? {} : ids;
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _handleDelete() async {
|
||||
final res = await globalState.showMessage(
|
||||
title: appLocalizations.tip,
|
||||
message: TextSpan(
|
||||
text: appLocalizations.deleteMultipTip(appLocalizations.rule),
|
||||
),
|
||||
);
|
||||
if (res != true) {
|
||||
return;
|
||||
}
|
||||
final selectedRules = ref.read(selectedItemsProvider(_key));
|
||||
ref
|
||||
.read(profileAddedRulesProvider(widget.profileId).notifier)
|
||||
.delAll(selectedRules.cast<int>());
|
||||
ref.read(selectedItemsProvider(_key).notifier).value = {};
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final addedRules =
|
||||
ref.watch(profileAddedRulesProvider(widget.profileId)).value ?? [];
|
||||
final selectedRules = ref.watch(selectedItemsProvider(_key));
|
||||
return CommonPopScope(
|
||||
onPop: (_) {
|
||||
if (selectedRules.isNotEmpty) {
|
||||
ref.read(selectedItemsProvider(_key).notifier).value = {};
|
||||
return false;
|
||||
}
|
||||
Navigator.of(context).pop();
|
||||
return false;
|
||||
},
|
||||
child: SliverMainAxisGroup(
|
||||
slivers: [
|
||||
SliverToBoxAdapter(child: SizedBox(height: 24)),
|
||||
SliverToBoxAdapter(
|
||||
child: Column(
|
||||
children: [
|
||||
InfoHeader(
|
||||
info: Info(label: appLocalizations.addedRules),
|
||||
actions: [
|
||||
if (selectedRules.isNotEmpty) ...[
|
||||
CommonMinIconButtonTheme(
|
||||
child: IconButton.filledTonal(
|
||||
onPressed: () {
|
||||
_handleDelete();
|
||||
},
|
||||
icon: Icon(Icons.delete),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
],
|
||||
CommonMinFilledButtonTheme(
|
||||
child: selectedRules.isNotEmpty
|
||||
? FilledButton(
|
||||
onPressed: () {
|
||||
_handleSelectAll();
|
||||
},
|
||||
child: Text(appLocalizations.selectAll),
|
||||
)
|
||||
: FilledButton.tonal(
|
||||
onPressed: () {
|
||||
_handleAddOrUpdate();
|
||||
},
|
||||
child: Text(appLocalizations.add),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(child: SizedBox(height: 8)),
|
||||
Consumer(
|
||||
builder: (_, ref, _) {
|
||||
return SliverReorderableList(
|
||||
itemCount: addedRules.length,
|
||||
itemBuilder: (_, index) {
|
||||
final rule = addedRules[index];
|
||||
return ReorderableDelayedDragStartListener(
|
||||
key: ObjectKey(rule),
|
||||
index: index,
|
||||
child: RuleItem(
|
||||
isEditing: selectedRules.isNotEmpty,
|
||||
isSelected: selectedRules.contains(rule.id),
|
||||
rule: rule,
|
||||
onSelected: () {
|
||||
_handleSelected(rule.id);
|
||||
},
|
||||
onEdit: (rule) {
|
||||
_handleAddOrUpdate(rule);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
onReorder: ref
|
||||
.read(profileAddedRulesProvider(widget.profileId).notifier)
|
||||
.order,
|
||||
);
|
||||
},
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||
child: CommonCard(
|
||||
radius: 18,
|
||||
child: ListTile(
|
||||
minTileHeight: 0,
|
||||
minVerticalPadding: 0,
|
||||
titleTextStyle: context.textTheme.bodyMedium?.toJetBrainsMono,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 16,
|
||||
),
|
||||
title: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
appLocalizations.controlGlobalAddedRules,
|
||||
style: context.textTheme.bodyLarge,
|
||||
),
|
||||
),
|
||||
Icon(Icons.arrow_forward_ios, size: 18),
|
||||
],
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
BaseNavigator.push(
|
||||
context,
|
||||
_EditGlobalAddedRules(profileId: widget.profileId),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ScriptContent extends ConsumerWidget {
|
||||
final int profileId;
|
||||
|
||||
const _ScriptContent(this.profileId);
|
||||
|
||||
void _handleChange(WidgetRef ref, int scriptId) {
|
||||
ref.read(profilesProvider.notifier).updateProfile(profileId, (state) {
|
||||
return state.copyWith(
|
||||
scriptId: state.scriptId == scriptId ? null : scriptId,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final scriptId = ref.watch(
|
||||
profileProvider(profileId).select((state) => state?.scriptId),
|
||||
);
|
||||
final scripts = ref.watch(scriptsProvider).value ?? [];
|
||||
return SliverMainAxisGroup(
|
||||
slivers: [
|
||||
SliverToBoxAdapter(child: SizedBox(height: 24)),
|
||||
SliverToBoxAdapter(
|
||||
child: Column(
|
||||
children: [
|
||||
InfoHeader(info: Info(label: appLocalizations.overrideScript)),
|
||||
],
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(child: SizedBox(height: 8)),
|
||||
Consumer(
|
||||
builder: (_, ref, _) {
|
||||
return SliverPadding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||
sliver: SliverList.builder(
|
||||
itemCount: scripts.length,
|
||||
itemBuilder: (_, index) {
|
||||
final script = scripts[index];
|
||||
return Container(
|
||||
margin: EdgeInsets.symmetric(vertical: 4),
|
||||
child: CommonCard(
|
||||
type: CommonCardType.filled,
|
||||
radius: 18,
|
||||
child: ListTile(
|
||||
minLeadingWidth: 0,
|
||||
minTileHeight: 0,
|
||||
minVerticalPadding: 16,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 14,
|
||||
),
|
||||
title: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: Radio(
|
||||
materialTapTargetSize:
|
||||
MaterialTapTargetSize.shrinkWrap,
|
||||
visualDensity: VisualDensity.compact,
|
||||
toggleable: true,
|
||||
value: script.id,
|
||||
groupValue: scriptId,
|
||||
onChanged: (_) {
|
||||
_handleChange(ref, script.id);
|
||||
},
|
||||
),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Flexible(child: Text(script.label)),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
_handleChange(ref, script.id);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||
child: CommonCard(
|
||||
radius: 18,
|
||||
child: ListTile(
|
||||
minTileHeight: 0,
|
||||
minVerticalPadding: 0,
|
||||
titleTextStyle: context.textTheme.bodyMedium?.toJetBrainsMono,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 16,
|
||||
),
|
||||
title: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
appLocalizations.goToConfigureScript,
|
||||
style: context.textTheme.bodyLarge,
|
||||
),
|
||||
),
|
||||
Icon(Icons.arrow_forward_ios, size: 18),
|
||||
],
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
BaseNavigator.push(context, const ScriptsView());
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _EditGlobalAddedRules extends ConsumerWidget {
|
||||
final int profileId;
|
||||
|
||||
const _EditGlobalAddedRules({required this.profileId});
|
||||
|
||||
void _handleChange(WidgetRef ref, bool status, int ruleId) {
|
||||
if (status) {
|
||||
ref.read(profileDisabledRuleIdsProvider(profileId).notifier).put(ruleId);
|
||||
} else {
|
||||
ref.read(profileDisabledRuleIdsProvider(profileId).notifier).del(ruleId);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final disabledRuleIds =
|
||||
ref.watch(profileDisabledRuleIdsProvider(profileId)).value ?? [];
|
||||
final rules = ref.watch(globalRulesProvider).value ?? [];
|
||||
return BaseScaffold(
|
||||
title: appLocalizations.editGlobalRules,
|
||||
body: rules.isEmpty
|
||||
? NullStatus(
|
||||
label: appLocalizations.nullTip(appLocalizations.rule),
|
||||
illustration: RuleEmptyIllustration(),
|
||||
)
|
||||
: ListView.builder(
|
||||
padding: EdgeInsets.all(16),
|
||||
itemBuilder: (context, index) {
|
||||
final rule = rules[index];
|
||||
return RuleStatusItem(
|
||||
status: !disabledRuleIds.contains(rule.id),
|
||||
rule: rule,
|
||||
onChange: (status) {
|
||||
_handleChange(ref, !status, rule.id);
|
||||
},
|
||||
);
|
||||
},
|
||||
itemCount: rules.length,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
120
lib/views/profiles/overwrite/script.dart
Normal file
120
lib/views/profiles/overwrite/script.dart
Normal file
@@ -0,0 +1,120 @@
|
||||
part of 'overwrite.dart';
|
||||
|
||||
class _ScriptContent extends ConsumerWidget {
|
||||
const _ScriptContent();
|
||||
|
||||
void _handleChange(WidgetRef ref, int profileId, int scriptId) {
|
||||
ref.read(profilesProvider.notifier).updateProfile(profileId, (state) {
|
||||
return state.copyWith(
|
||||
scriptId: state.scriptId == scriptId ? null : scriptId,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final profileId = ProfileIdProvider.of(context)!.profileId;
|
||||
final scriptId = ref.watch(
|
||||
profileProvider(profileId).select((state) => state?.scriptId),
|
||||
);
|
||||
final scripts = ref.watch(scriptsProvider).value ?? [];
|
||||
return SliverMainAxisGroup(
|
||||
slivers: [
|
||||
SliverToBoxAdapter(child: SizedBox(height: 24)),
|
||||
SliverToBoxAdapter(
|
||||
child: Column(
|
||||
children: [
|
||||
InfoHeader(info: Info(label: appLocalizations.overrideScript)),
|
||||
],
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(child: SizedBox(height: 8)),
|
||||
Consumer(
|
||||
builder: (_, ref, _) {
|
||||
return SliverPadding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||
sliver: SliverList.builder(
|
||||
itemCount: scripts.length,
|
||||
itemBuilder: (_, index) {
|
||||
final script = scripts[index];
|
||||
return Container(
|
||||
margin: EdgeInsets.symmetric(vertical: 4),
|
||||
child: CommonCard(
|
||||
type: CommonCardType.filled,
|
||||
radius: 18,
|
||||
child: ListTile(
|
||||
minLeadingWidth: 0,
|
||||
minTileHeight: 0,
|
||||
minVerticalPadding: 16,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 14,
|
||||
),
|
||||
title: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: Radio(
|
||||
materialTapTargetSize:
|
||||
MaterialTapTargetSize.shrinkWrap,
|
||||
visualDensity: VisualDensity.compact,
|
||||
toggleable: true,
|
||||
value: script.id,
|
||||
groupValue: scriptId,
|
||||
onChanged: (_) {
|
||||
_handleChange(ref, profileId, script.id);
|
||||
},
|
||||
),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Flexible(child: Text(script.label)),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
_handleChange(ref, profileId, script.id);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||
child: CommonCard(
|
||||
radius: 18,
|
||||
child: ListTile(
|
||||
minTileHeight: 0,
|
||||
minVerticalPadding: 0,
|
||||
titleTextStyle: context.textTheme.bodyMedium?.toJetBrainsMono,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 16,
|
||||
),
|
||||
title: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
appLocalizations.goToConfigureScript,
|
||||
style: context.textTheme.bodyLarge,
|
||||
),
|
||||
),
|
||||
Icon(Icons.arrow_forward_ios, size: 18),
|
||||
],
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
BaseNavigator.push(context, const ScriptsView());
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
234
lib/views/profiles/overwrite/standard.dart
Normal file
234
lib/views/profiles/overwrite/standard.dart
Normal file
@@ -0,0 +1,234 @@
|
||||
part of 'overwrite.dart';
|
||||
|
||||
class _StandardContent extends ConsumerStatefulWidget {
|
||||
const _StandardContent();
|
||||
|
||||
@override
|
||||
ConsumerState createState() => _StandardContentState();
|
||||
}
|
||||
|
||||
class _StandardContentState extends ConsumerState<_StandardContent> {
|
||||
final _key = utils.id;
|
||||
late int _profileId;
|
||||
|
||||
Future<void> _handleAddOrUpdate([Rule? rule]) async {
|
||||
final res = await globalState.showCommonDialog<Rule>(
|
||||
child: AddOrEditRuleDialog(rule: rule),
|
||||
);
|
||||
if (res == null) {
|
||||
return;
|
||||
}
|
||||
ref.read(profileAddedRulesProvider(_profileId).notifier).put(res);
|
||||
}
|
||||
|
||||
void _handleSelected(int ruleId) {
|
||||
ref.read(selectedItemsProvider(_key).notifier).update((selectedRules) {
|
||||
final newSelectedRules = Set<int>.from(selectedRules)
|
||||
..addOrRemove(ruleId);
|
||||
return newSelectedRules;
|
||||
});
|
||||
}
|
||||
|
||||
void _handleSelectAll() {
|
||||
final ids =
|
||||
ref
|
||||
.read(profileAddedRulesProvider(_profileId))
|
||||
.value
|
||||
?.map((item) => item.id)
|
||||
.toSet() ??
|
||||
{};
|
||||
ref.read(selectedItemsProvider(_key).notifier).update((selected) {
|
||||
return selected.containsAll(ids) ? {} : ids;
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _handleDelete() async {
|
||||
final res = await globalState.showMessage(
|
||||
title: appLocalizations.tip,
|
||||
message: TextSpan(
|
||||
text: appLocalizations.deleteMultipTip(appLocalizations.rule),
|
||||
),
|
||||
);
|
||||
if (res != true) {
|
||||
return;
|
||||
}
|
||||
final selectedRules = ref.read(selectedItemsProvider(_key));
|
||||
ref
|
||||
.read(profileAddedRulesProvider(_profileId).notifier)
|
||||
.delAll(selectedRules.cast<int>());
|
||||
ref.read(selectedItemsProvider(_key).notifier).value = {};
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
_profileId = ProfileIdProvider.of(context)!.profileId;
|
||||
}
|
||||
|
||||
void _handleToEditGlobalAddedRules() {
|
||||
BaseNavigator.push(context, _EditGlobalAddedRules(_profileId));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
_profileId = ProfileIdProvider.of(context)!.profileId;
|
||||
final addedRules =
|
||||
ref.watch(profileAddedRulesProvider(_profileId)).value ?? [];
|
||||
final selectedRules = ref.watch(selectedItemsProvider(_key));
|
||||
return CommonPopScope(
|
||||
onPop: (_) {
|
||||
if (selectedRules.isNotEmpty) {
|
||||
ref.read(selectedItemsProvider(_key).notifier).value = {};
|
||||
return false;
|
||||
}
|
||||
Navigator.of(context).pop();
|
||||
return false;
|
||||
},
|
||||
child: SliverMainAxisGroup(
|
||||
slivers: [
|
||||
SliverToBoxAdapter(child: SizedBox(height: 24)),
|
||||
SliverToBoxAdapter(
|
||||
child: Column(
|
||||
children: [
|
||||
InfoHeader(
|
||||
info: Info(label: appLocalizations.addedRules),
|
||||
actions: [
|
||||
if (selectedRules.isNotEmpty) ...[
|
||||
CommonMinIconButtonTheme(
|
||||
child: IconButton.filledTonal(
|
||||
onPressed: () {
|
||||
_handleDelete();
|
||||
},
|
||||
icon: Icon(Icons.delete),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
],
|
||||
CommonMinFilledButtonTheme(
|
||||
child: selectedRules.isNotEmpty
|
||||
? FilledButton(
|
||||
onPressed: () {
|
||||
_handleSelectAll();
|
||||
},
|
||||
child: Text(appLocalizations.selectAll),
|
||||
)
|
||||
: FilledButton.tonal(
|
||||
onPressed: () {
|
||||
_handleAddOrUpdate();
|
||||
},
|
||||
child: Text(appLocalizations.add),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(child: SizedBox(height: 8)),
|
||||
Consumer(
|
||||
builder: (_, ref, _) {
|
||||
return SliverReorderableList(
|
||||
itemCount: addedRules.length,
|
||||
itemBuilder: (_, index) {
|
||||
final rule = addedRules[index];
|
||||
return ReorderableDelayedDragStartListener(
|
||||
key: ObjectKey(rule),
|
||||
index: index,
|
||||
child: RuleItem(
|
||||
isEditing: selectedRules.isNotEmpty,
|
||||
isSelected: selectedRules.contains(rule.id),
|
||||
rule: rule,
|
||||
onSelected: () {
|
||||
_handleSelected(rule.id);
|
||||
},
|
||||
onEdit: (rule) {
|
||||
_handleAddOrUpdate(rule);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
onReorder: ref
|
||||
.read(profileAddedRulesProvider(_profileId).notifier)
|
||||
.order,
|
||||
);
|
||||
},
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||
child: CommonCard(
|
||||
radius: 18,
|
||||
onPressed: _handleToEditGlobalAddedRules,
|
||||
child: ListTile(
|
||||
minTileHeight: 0,
|
||||
minVerticalPadding: 0,
|
||||
titleTextStyle: context.textTheme.bodyMedium?.toJetBrainsMono,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 16,
|
||||
),
|
||||
title: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
appLocalizations.controlGlobalAddedRules,
|
||||
style: context.textTheme.bodyLarge,
|
||||
),
|
||||
),
|
||||
Icon(Icons.arrow_forward_ios, size: 18),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _EditGlobalAddedRules extends ConsumerWidget {
|
||||
final int profileId;
|
||||
|
||||
const _EditGlobalAddedRules(this.profileId);
|
||||
|
||||
void _handleChange(WidgetRef ref, int profileId, bool status, int ruleId) {
|
||||
if (status) {
|
||||
ref.read(profileDisabledRuleIdsProvider(profileId).notifier).put(ruleId);
|
||||
} else {
|
||||
ref.read(profileDisabledRuleIdsProvider(profileId).notifier).del(ruleId);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final disabledRuleIds =
|
||||
ref.watch(profileDisabledRuleIdsProvider(profileId)).value ?? [];
|
||||
final rules = ref.watch(globalRulesProvider).value ?? [];
|
||||
return BaseScaffold(
|
||||
title: appLocalizations.editGlobalRules,
|
||||
body: rules.isEmpty
|
||||
? NullStatus(
|
||||
label: appLocalizations.nullTip(appLocalizations.rule),
|
||||
illustration: RuleEmptyIllustration(),
|
||||
)
|
||||
: ListView.builder(
|
||||
padding: EdgeInsets.all(16),
|
||||
itemBuilder: (context, index) {
|
||||
final rule = rules[index];
|
||||
return RuleStatusItem(
|
||||
status: !disabledRuleIds.contains(rule.id),
|
||||
rule: rule,
|
||||
onChange: (status) {
|
||||
_handleChange(ref, profileId, !status, rule.id);
|
||||
},
|
||||
);
|
||||
},
|
||||
itemCount: rules.length,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -29,3 +29,39 @@ class _MoreActionButton extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ProfileIdProvider extends InheritedWidget {
|
||||
final int profileId;
|
||||
|
||||
const ProfileIdProvider({
|
||||
super.key,
|
||||
required this.profileId,
|
||||
required super.child,
|
||||
});
|
||||
|
||||
static ProfileIdProvider? of(BuildContext context) {
|
||||
return context.dependOnInheritedWidgetOfExactType<ProfileIdProvider>();
|
||||
}
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(ProfileIdProvider oldWidget) =>
|
||||
profileId != oldWidget.profileId;
|
||||
}
|
||||
|
||||
class ProxyGroupProvider extends InheritedWidget {
|
||||
final ProxyGroup proxyGroup;
|
||||
|
||||
const ProxyGroupProvider({
|
||||
super.key,
|
||||
required this.proxyGroup,
|
||||
required super.child,
|
||||
});
|
||||
|
||||
static ProxyGroupProvider? of(BuildContext context) {
|
||||
return context.dependOnInheritedWidgetOfExactType<ProxyGroupProvider>();
|
||||
}
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(ProxyGroupProvider oldWidget) =>
|
||||
proxyGroup != oldWidget.proxyGroup;
|
||||
}
|
||||
|
||||
@@ -220,7 +220,7 @@ class _AdaptiveSheetScaffoldState extends State<AdaptiveSheetScaffold> {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 4),
|
||||
padding: EdgeInsets.only(top: 6),
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
height: handleSize.height,
|
||||
|
||||
Reference in New Issue
Block a user