876 lines
29 KiB
Dart
876 lines
29 KiB
Dart
import 'package:fl_clash/common/common.dart';
|
|
import 'package:fl_clash/enum/enum.dart';
|
|
import 'package:fl_clash/models/models.dart';
|
|
import 'package:fl_clash/providers/providers.dart';
|
|
import 'package:fl_clash/state.dart';
|
|
import 'package:fl_clash/widgets/widgets.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
Future<void> _handleAddOrUpdate(WidgetRef ref, [Rule? rule]) async {
|
|
final snippet = ref.read(
|
|
profileOverrideStateProvider.select((state) => state?.snippet),
|
|
);
|
|
if (snippet == null) {
|
|
return;
|
|
}
|
|
final res = await globalState.showCommonDialog<Rule>(
|
|
child: AddRuleDialog(rule: rule, snippet: snippet),
|
|
);
|
|
if (res == null) {
|
|
return;
|
|
}
|
|
ref.read(profileOverrideStateProvider.notifier).updateState((state) {
|
|
if (state == null) {
|
|
return state;
|
|
}
|
|
final model = state.copyWith.overrideData!(
|
|
rule: state.overrideData!.rule.updateRules((rules) {
|
|
final index = rules.indexWhere((item) => item.id == res.id);
|
|
if (index == -1) {
|
|
return List.from([res, ...rules]);
|
|
}
|
|
return List.from(rules)..[index] = res;
|
|
}),
|
|
);
|
|
return model;
|
|
});
|
|
}
|
|
|
|
class OverrideProfileView extends ConsumerStatefulWidget {
|
|
final String profileId;
|
|
|
|
const OverrideProfileView({super.key, required this.profileId});
|
|
|
|
@override
|
|
ConsumerState<OverrideProfileView> createState() =>
|
|
_OverrideProfileViewState();
|
|
}
|
|
|
|
class _OverrideProfileViewState extends ConsumerState<OverrideProfileView> {
|
|
final _controller = ScrollController();
|
|
double _currentMaxWidth = 0;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
globalState.appState = globalState.appState.copyWith(
|
|
profileOverrideModel: null,
|
|
);
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
Future.delayed(Duration(milliseconds: 300), () async {
|
|
final rawConfig = await globalState.getProfileConfig(widget.profileId);
|
|
final snippet = ClashConfigSnippet.fromJson(rawConfig);
|
|
final overrideData = ref.read(
|
|
getProfileOverrideDataProvider(widget.profileId),
|
|
);
|
|
ref.read(profileOverrideStateProvider.notifier).value =
|
|
ProfileOverrideModel(snippet: snippet, overrideData: overrideData);
|
|
});
|
|
});
|
|
}
|
|
|
|
void _handleSave(OverrideData overrideData) {
|
|
ref
|
|
.read(profilesProvider.notifier)
|
|
.updateProfile(
|
|
widget.profileId,
|
|
(state) => state.copyWith(overrideData: overrideData),
|
|
);
|
|
globalState.appController.setupClashConfigDebounce();
|
|
}
|
|
|
|
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(
|
|
profileOverrideStateProvider.select(
|
|
(state) => state?.selectedRules ?? {},
|
|
),
|
|
);
|
|
ref.read(profileOverrideStateProvider.notifier).updateState((state) {
|
|
final overrideData = state?.overrideData;
|
|
if (overrideData == null) {
|
|
return null;
|
|
}
|
|
final overrideRule = overrideData.rule.updateRules(
|
|
(rules) =>
|
|
List.from(rules.where((item) => !selectedRules.contains(item.id))),
|
|
);
|
|
|
|
return state?.copyWith(
|
|
overrideData: overrideData.copyWith(rule: overrideRule),
|
|
);
|
|
});
|
|
ref
|
|
.read(profileOverrideStateProvider.notifier)
|
|
.updateState((state) => state?.copyWith(selectedRules: {}));
|
|
}
|
|
|
|
Widget _buildContent() {
|
|
return Consumer(
|
|
builder: (_, ref, child) {
|
|
final isInit = ref.watch(
|
|
profileOverrideStateProvider.select((state) => state != null),
|
|
);
|
|
if (!isInit) {
|
|
return Center(child: CircularProgressIndicator());
|
|
}
|
|
return child!;
|
|
},
|
|
child: LayoutBuilder(
|
|
builder: (_, constraints) {
|
|
_currentMaxWidth = constraints.maxWidth - 104;
|
|
return CommonScrollBar(
|
|
controller: _controller,
|
|
child: CustomScrollView(
|
|
controller: _controller,
|
|
slivers: [
|
|
SliverToBoxAdapter(child: SizedBox(height: 8)),
|
|
SliverToBoxAdapter(
|
|
child: Consumer(
|
|
builder: (_, ref, child) {
|
|
// final scriptMode = ref.watch(
|
|
// scriptStateProvider.select(
|
|
// (state) => state.realId != null,
|
|
// ),
|
|
// );
|
|
// if (!scriptMode) {
|
|
// return SizedBox();
|
|
// }
|
|
return child!;
|
|
},
|
|
child: ListItem(
|
|
padding: EdgeInsets.symmetric(
|
|
horizontal: 20,
|
|
vertical: 0,
|
|
),
|
|
title: Row(
|
|
spacing: 8,
|
|
children: [
|
|
Icon(Icons.info),
|
|
Text(appLocalizations.overrideInvalidTip),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
SliverToBoxAdapter(child: SizedBox(height: 8)),
|
|
SliverPadding(
|
|
padding: EdgeInsets.symmetric(horizontal: 16),
|
|
sliver: SliverToBoxAdapter(child: OverrideSwitch()),
|
|
),
|
|
SliverToBoxAdapter(
|
|
child: Padding(
|
|
padding: EdgeInsets.only(left: 8, right: 8),
|
|
child: RuleTitle(profileId: widget.profileId),
|
|
),
|
|
),
|
|
SliverPadding(
|
|
padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 0),
|
|
sliver: RuleContent(maxWidth: _currentMaxWidth),
|
|
),
|
|
SliverToBoxAdapter(child: SizedBox(height: 16)),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final editCount = ref.watch(
|
|
profileOverrideStateProvider.select(
|
|
(state) => state?.selectedRules.length ?? 0,
|
|
),
|
|
);
|
|
final isEdit = editCount != 0;
|
|
return CommonScaffold(
|
|
title: appLocalizations.override,
|
|
body: _buildContent(),
|
|
actions: [
|
|
if (!isEdit)
|
|
Consumer(
|
|
builder: (_, ref, child) {
|
|
final overrideData = ref.watch(
|
|
getProfileOverrideDataProvider(widget.profileId),
|
|
);
|
|
final newOverrideData = ref.watch(
|
|
profileOverrideStateProvider.select(
|
|
(state) => state?.overrideData,
|
|
),
|
|
);
|
|
final equals = overrideData == newOverrideData;
|
|
if (equals || newOverrideData == null) {
|
|
return SizedBox();
|
|
}
|
|
return CommonPopScope(
|
|
onPop: (context) async {
|
|
if (equals) {
|
|
return true;
|
|
}
|
|
final res = await globalState.showMessage(
|
|
message: TextSpan(text: appLocalizations.saveChanges),
|
|
confirmText: appLocalizations.save,
|
|
);
|
|
if (!context.mounted || res != true) {
|
|
return true;
|
|
}
|
|
_handleSave(newOverrideData);
|
|
return true;
|
|
},
|
|
child: IconButton(
|
|
onPressed: () async {
|
|
final res = await globalState.showMessage(
|
|
message: TextSpan(text: appLocalizations.saveTip),
|
|
confirmText: appLocalizations.save,
|
|
);
|
|
if (res != true) {
|
|
return;
|
|
}
|
|
_handleSave(newOverrideData);
|
|
},
|
|
icon: Icon(Icons.save),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
if (editCount == 1)
|
|
IconButton(
|
|
onPressed: () {
|
|
final rule = ref.read(
|
|
profileOverrideStateProvider.select((state) {
|
|
return state?.overrideData?.rule.rules.firstWhere(
|
|
(item) => item.id == state.selectedRules.first,
|
|
);
|
|
}),
|
|
);
|
|
if (rule == null) {
|
|
return;
|
|
}
|
|
_handleAddOrUpdate(ref, rule);
|
|
},
|
|
icon: Icon(Icons.edit),
|
|
),
|
|
if (editCount > 0)
|
|
IconButton(
|
|
onPressed: () {
|
|
_handleDelete();
|
|
},
|
|
icon: Icon(Icons.delete),
|
|
),
|
|
],
|
|
editState: AppBarEditState(
|
|
editCount: editCount,
|
|
onExit: () {
|
|
ref
|
|
.read(profileOverrideStateProvider.notifier)
|
|
.updateState((state) => state?.copyWith(selectedRules: {}));
|
|
},
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class OverrideSwitch extends ConsumerWidget {
|
|
const OverrideSwitch({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final enable = ref.watch(
|
|
profileOverrideStateProvider.select(
|
|
(state) => state?.overrideData?.enable,
|
|
),
|
|
);
|
|
return CommonCard(
|
|
onPressed: () {},
|
|
type: CommonCardType.filled,
|
|
radius: 18,
|
|
child: ListItem.switchItem(
|
|
padding: const EdgeInsets.only(left: 16, right: 16),
|
|
title: Text(appLocalizations.enableOverride),
|
|
delegate: SwitchDelegate(
|
|
value: enable ?? false,
|
|
onChanged: (value) {
|
|
ref
|
|
.read(profileOverrideStateProvider.notifier)
|
|
.updateState(
|
|
(state) => state?.copyWith.overrideData!(enable: value),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class RuleTitle extends ConsumerWidget {
|
|
final String profileId;
|
|
|
|
const RuleTitle({super.key, required this.profileId});
|
|
|
|
void _handleChangeType(WidgetRef ref, isOverrideRule) {
|
|
ref
|
|
.read(profileOverrideStateProvider.notifier)
|
|
.updateState(
|
|
(state) => state?.copyWith.overrideData!.rule(
|
|
type: isOverrideRule
|
|
? OverrideRuleType.added
|
|
: OverrideRuleType.override,
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context, ref) {
|
|
final vm3 = ref.watch(
|
|
profileOverrideStateProvider.select((state) {
|
|
final overrideRule = state?.overrideData?.rule;
|
|
return VM3(
|
|
a: state?.selectedRules.isNotEmpty ?? false,
|
|
b:
|
|
state?.selectedRules.containsAll(
|
|
overrideRule?.rules.map((item) => item.id).toSet() ?? {},
|
|
) ??
|
|
false,
|
|
c: overrideRule?.type == OverrideRuleType.override,
|
|
);
|
|
}),
|
|
);
|
|
final isEdit = vm3.a;
|
|
final isSelectAll = vm3.b;
|
|
final isOverrideRule = vm3.c;
|
|
return FilledButtonTheme(
|
|
data: FilledButtonThemeData(
|
|
style: FilledButton.styleFrom(
|
|
padding: EdgeInsets.symmetric(horizontal: 8),
|
|
visualDensity: VisualDensity.compact,
|
|
),
|
|
),
|
|
child: IconButtonTheme(
|
|
data: IconButtonThemeData(
|
|
style: IconButton.styleFrom(
|
|
padding: EdgeInsets.zero,
|
|
visualDensity: VisualDensity.compact,
|
|
iconSize: 20,
|
|
),
|
|
),
|
|
child: ListHeader(
|
|
title: appLocalizations.rule,
|
|
subTitle: isOverrideRule
|
|
? appLocalizations.overrideOriginRules
|
|
: appLocalizations.addedOriginRules,
|
|
space: 8,
|
|
actions: [
|
|
if (!isEdit)
|
|
FadeRotationScaleBox(
|
|
child: isOverrideRule
|
|
? IconButton.filledTonal(
|
|
key: ValueKey(true),
|
|
icon: Icon(Icons.edit_document),
|
|
onPressed: () {
|
|
_handleChangeType(ref, true);
|
|
},
|
|
)
|
|
: IconButton.filledTonal(
|
|
key: ValueKey(false),
|
|
icon: Icon(Icons.note_add),
|
|
onPressed: () {
|
|
_handleChangeType(ref, false);
|
|
},
|
|
),
|
|
),
|
|
!isEdit
|
|
? FilledButton.tonal(
|
|
onPressed: () {
|
|
_handleAddOrUpdate(ref);
|
|
},
|
|
child: Text(appLocalizations.add),
|
|
)
|
|
: isSelectAll
|
|
? FilledButton(
|
|
onPressed: () {
|
|
ref
|
|
.read(profileOverrideStateProvider.notifier)
|
|
.updateState(
|
|
(state) => state?.copyWith(selectedRules: {}),
|
|
);
|
|
},
|
|
child: Text(appLocalizations.selectAll),
|
|
)
|
|
: FilledButton.tonal(
|
|
onPressed: () {
|
|
ref
|
|
.read(profileOverrideStateProvider.notifier)
|
|
.updateState(
|
|
(state) => state?.copyWith(
|
|
selectedRules:
|
|
state.overrideData?.rule.rules
|
|
.map((item) => item.id)
|
|
.toSet() ??
|
|
{},
|
|
),
|
|
);
|
|
},
|
|
child: Text(appLocalizations.selectAll),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class RuleContent extends ConsumerWidget {
|
|
final double maxWidth;
|
|
|
|
const RuleContent({super.key, required this.maxWidth});
|
|
|
|
Widget _buildItem({
|
|
required Rule rule,
|
|
required bool isSelected,
|
|
required VoidCallback onTab,
|
|
required BuildContext context,
|
|
}) {
|
|
return Material(
|
|
color: Colors.transparent,
|
|
child: Container(
|
|
margin: EdgeInsets.symmetric(vertical: 4),
|
|
child: CommonCard(
|
|
padding: EdgeInsets.zero,
|
|
radius: 18,
|
|
type: CommonCardType.filled,
|
|
isSelected: isSelected,
|
|
// decoration: BoxDecoration(
|
|
// color: isSelected
|
|
// ? context.colorScheme.secondaryContainer.opacity80
|
|
// : context.colorScheme.surfaceContainer,
|
|
// borderRadius: BorderRadius.circular(18),
|
|
// ),
|
|
onPressed: () {
|
|
onTab();
|
|
},
|
|
child: ListTile(
|
|
minTileHeight: 0,
|
|
minVerticalPadding: 0,
|
|
titleTextStyle: context.textTheme.bodyMedium?.toJetBrainsMono,
|
|
contentPadding: const EdgeInsets.symmetric(
|
|
horizontal: 16,
|
|
vertical: 16,
|
|
),
|
|
trailing: SizedBox(
|
|
width: 24,
|
|
height: 24,
|
|
child: CommonCheckBox(
|
|
value: isSelected,
|
|
isCircle: true,
|
|
onChanged: (_) {
|
|
onTab();
|
|
},
|
|
),
|
|
),
|
|
title: Text(rule.value),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
void _handleSelect(WidgetRef ref, String ruleId) {
|
|
ref.read(profileOverrideStateProvider.notifier).updateState((state) {
|
|
final newSelectedRules = Set<String>.from(state?.selectedRules ?? {});
|
|
if (newSelectedRules.contains(ruleId)) {
|
|
newSelectedRules.remove(ruleId);
|
|
} else {
|
|
newSelectedRules.add(ruleId);
|
|
}
|
|
return state?.copyWith(selectedRules: newSelectedRules);
|
|
});
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context, ref) {
|
|
final vm3 = ref.watch(
|
|
profileOverrideStateProvider.select((state) {
|
|
final overrideRule = state?.overrideData?.rule;
|
|
return VM3(
|
|
a: overrideRule?.rules ?? [],
|
|
b: overrideRule?.type ?? OverrideRuleType.added,
|
|
c: state?.selectedRules ?? {},
|
|
);
|
|
}),
|
|
);
|
|
final rules = vm3.a;
|
|
final type = vm3.b;
|
|
final selectedRules = vm3.c;
|
|
if (rules.isEmpty) {
|
|
return SliverToBoxAdapter(
|
|
child: SizedBox(
|
|
height: 300,
|
|
child: Center(
|
|
child: type == OverrideRuleType.added
|
|
? Text(appLocalizations.noData)
|
|
: FilledButton(
|
|
onPressed: () {
|
|
final rules = ref.read(
|
|
profileOverrideStateProvider.select(
|
|
(state) => state?.snippet.rule ?? [],
|
|
),
|
|
);
|
|
ref
|
|
.read(profileOverrideStateProvider.notifier)
|
|
.updateState((state) {
|
|
return state?.copyWith.overrideData!.rule(
|
|
overrideRules: rules,
|
|
);
|
|
});
|
|
},
|
|
child: Text(appLocalizations.getOriginRules),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
return CacheItemExtentSliverReorderableList(
|
|
tag: CacheTag.rules,
|
|
itemBuilder: (context, index) {
|
|
final rule = rules[index];
|
|
return ReorderableDelayedDragStartListener(
|
|
key: ObjectKey(rule),
|
|
index: index,
|
|
child: _buildItem(
|
|
rule: rule,
|
|
isSelected: selectedRules.contains(rule.id),
|
|
onTab: () {
|
|
_handleSelect(ref, rule.id);
|
|
},
|
|
context: context,
|
|
),
|
|
);
|
|
},
|
|
proxyDecorator: commonProxyDecorator,
|
|
itemCount: rules.length,
|
|
onReorder: (oldIndex, newIndex) {
|
|
if (oldIndex < newIndex) {
|
|
newIndex -= 1;
|
|
}
|
|
final newRules = List<Rule>.from(rules);
|
|
final item = newRules.removeAt(oldIndex);
|
|
newRules.insert(newIndex, item);
|
|
ref
|
|
.read(profileOverrideStateProvider.notifier)
|
|
.updateState(
|
|
(state) => state?.copyWith.overrideData!(
|
|
rule: state.overrideData!.rule.updateRules((_) => newRules),
|
|
),
|
|
);
|
|
},
|
|
keyBuilder: (int index) {
|
|
return rules[index].value;
|
|
},
|
|
itemExtentBuilder: (index) {
|
|
final rule = rules[index];
|
|
return 40 +
|
|
globalState.measure
|
|
.computeTextSize(
|
|
Text(
|
|
rule.value,
|
|
style: context.textTheme.bodyMedium?.toJetBrainsMono,
|
|
),
|
|
maxWidth: maxWidth,
|
|
)
|
|
.height;
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
class AddRuleDialog extends StatefulWidget {
|
|
final ClashConfigSnippet snippet;
|
|
final Rule? rule;
|
|
|
|
const AddRuleDialog({super.key, required this.snippet, this.rule});
|
|
|
|
@override
|
|
State<AddRuleDialog> createState() => _AddRuleDialogState();
|
|
}
|
|
|
|
class _AddRuleDialogState extends State<AddRuleDialog> {
|
|
late RuleAction _ruleAction;
|
|
final _ruleTargetController = TextEditingController();
|
|
final _contentController = TextEditingController();
|
|
final _ruleProviderController = TextEditingController();
|
|
final _subRuleController = TextEditingController();
|
|
bool _noResolve = false;
|
|
bool _src = false;
|
|
List<DropdownMenuEntry> _targetItems = [];
|
|
List<DropdownMenuEntry> _ruleProviderItems = [];
|
|
List<DropdownMenuEntry> _subRuleItems = [];
|
|
final _formKey = GlobalKey<FormState>();
|
|
|
|
@override
|
|
void initState() {
|
|
_initState();
|
|
super.initState();
|
|
}
|
|
|
|
void _initState() {
|
|
_targetItems = [
|
|
...widget.snippet.proxyGroups.map(
|
|
(item) => DropdownMenuEntry(value: item.name, label: item.name),
|
|
),
|
|
...RuleTarget.values.map(
|
|
(item) => DropdownMenuEntry(value: item.name, label: item.name),
|
|
),
|
|
];
|
|
_ruleProviderItems = [
|
|
...widget.snippet.ruleProvider.map(
|
|
(item) => DropdownMenuEntry(value: item.name, label: item.name),
|
|
),
|
|
];
|
|
_subRuleItems = [
|
|
...widget.snippet.subRules.map(
|
|
(item) => DropdownMenuEntry(value: item.name, label: item.name),
|
|
),
|
|
];
|
|
if (widget.rule != null) {
|
|
final parsedRule = ParsedRule.parseString(widget.rule!.value);
|
|
_ruleAction = parsedRule.ruleAction;
|
|
_contentController.text = parsedRule.content ?? '';
|
|
_ruleTargetController.text = parsedRule.ruleTarget ?? '';
|
|
_ruleProviderController.text = parsedRule.ruleProvider ?? '';
|
|
_subRuleController.text = parsedRule.subRule ?? '';
|
|
_noResolve = parsedRule.noResolve;
|
|
_src = parsedRule.src;
|
|
return;
|
|
}
|
|
_ruleAction = RuleAction.values.first;
|
|
if (_targetItems.isNotEmpty) {
|
|
_ruleTargetController.text = _targetItems.first.value;
|
|
}
|
|
if (_ruleProviderItems.isNotEmpty) {
|
|
_ruleProviderController.text = _ruleProviderItems.first.value;
|
|
}
|
|
if (_subRuleItems.isNotEmpty) {
|
|
_subRuleController.text = _subRuleItems.first.value;
|
|
}
|
|
}
|
|
|
|
@override
|
|
void didUpdateWidget(AddRuleDialog oldWidget) {
|
|
super.didUpdateWidget(oldWidget);
|
|
if (oldWidget.rule != widget.rule) {
|
|
_initState();
|
|
}
|
|
}
|
|
|
|
void _handleSubmit() {
|
|
final res = _formKey.currentState?.validate();
|
|
if (res == false) {
|
|
return;
|
|
}
|
|
final parsedRule = ParsedRule(
|
|
ruleAction: _ruleAction,
|
|
content: _contentController.text,
|
|
ruleProvider: _ruleProviderController.text,
|
|
ruleTarget: _ruleTargetController.text,
|
|
subRule: _subRuleController.text,
|
|
noResolve: _noResolve,
|
|
src: _src,
|
|
);
|
|
final rule = widget.rule != null
|
|
? widget.rule!.copyWith(value: parsedRule.value)
|
|
: Rule.value(parsedRule.value);
|
|
Navigator.of(context).pop(rule);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return CommonDialog(
|
|
title: appLocalizations.addRule,
|
|
actions: [
|
|
TextButton(
|
|
onPressed: _handleSubmit,
|
|
child: Text(appLocalizations.confirm),
|
|
),
|
|
],
|
|
child: DropdownMenuTheme(
|
|
data: DropdownMenuThemeData(
|
|
inputDecorationTheme: InputDecorationTheme(
|
|
border: OutlineInputBorder(),
|
|
labelStyle: context.textTheme.bodyLarge?.copyWith(
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
),
|
|
),
|
|
child: Form(
|
|
key: _formKey,
|
|
child: LayoutBuilder(
|
|
builder: (_, constraints) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
FilledButton.tonal(
|
|
onPressed: () async {
|
|
_ruleAction =
|
|
await globalState.showCommonDialog<RuleAction>(
|
|
child: OptionsDialog<RuleAction>(
|
|
title: appLocalizations.ruleName,
|
|
options: RuleAction.values,
|
|
textBuilder: (item) => item.value,
|
|
value: _ruleAction,
|
|
),
|
|
) ??
|
|
_ruleAction;
|
|
setState(() {});
|
|
},
|
|
child: Text(_ruleAction.name),
|
|
),
|
|
SizedBox(height: 24),
|
|
_ruleAction == RuleAction.RULE_SET
|
|
? FormField(
|
|
validator: (_) {
|
|
if (_ruleProviderController.text.isEmpty) {
|
|
return appLocalizations.emptyTip(
|
|
appLocalizations.ruleProviders,
|
|
);
|
|
}
|
|
return null;
|
|
},
|
|
builder: (field) {
|
|
return DropdownMenu(
|
|
expandedInsets: EdgeInsets.zero,
|
|
controller: _ruleProviderController,
|
|
label: Text(appLocalizations.ruleProviders),
|
|
menuHeight: 250,
|
|
errorText: field.errorText,
|
|
dropdownMenuEntries: _ruleProviderItems,
|
|
);
|
|
},
|
|
)
|
|
: TextFormField(
|
|
controller: _contentController,
|
|
decoration: InputDecoration(
|
|
border: const OutlineInputBorder(),
|
|
labelText: appLocalizations.content,
|
|
),
|
|
validator: (_) {
|
|
if (_contentController.text.isEmpty) {
|
|
return appLocalizations.emptyTip(
|
|
appLocalizations.content,
|
|
);
|
|
}
|
|
return null;
|
|
},
|
|
),
|
|
SizedBox(height: 24),
|
|
_ruleAction == RuleAction.SUB_RULE
|
|
? FormField(
|
|
validator: (_) {
|
|
if (_subRuleController.text.isEmpty) {
|
|
return appLocalizations.emptyTip(
|
|
appLocalizations.subRule,
|
|
);
|
|
}
|
|
return null;
|
|
},
|
|
builder: (filed) {
|
|
return DropdownMenu(
|
|
width: 200,
|
|
enableFilter: false,
|
|
enableSearch: false,
|
|
controller: _subRuleController,
|
|
label: Text(appLocalizations.subRule),
|
|
menuHeight: 250,
|
|
dropdownMenuEntries: _subRuleItems,
|
|
);
|
|
},
|
|
)
|
|
: FormField<String>(
|
|
validator: (_) {
|
|
if (_ruleTargetController.text.isEmpty) {
|
|
return appLocalizations.emptyTip(
|
|
appLocalizations.ruleTarget,
|
|
);
|
|
}
|
|
return null;
|
|
},
|
|
builder: (filed) {
|
|
return DropdownMenu(
|
|
controller: _ruleTargetController,
|
|
label: Text(appLocalizations.ruleTarget),
|
|
width: 200,
|
|
menuHeight: 250,
|
|
enableFilter: false,
|
|
enableSearch: false,
|
|
dropdownMenuEntries: _targetItems,
|
|
errorText: filed.errorText,
|
|
);
|
|
},
|
|
),
|
|
if (_ruleAction.hasParams) ...[
|
|
SizedBox(height: 20),
|
|
Wrap(
|
|
spacing: 8,
|
|
children: [
|
|
CommonCard(
|
|
radius: 8,
|
|
isSelected: _src,
|
|
child: Padding(
|
|
padding: EdgeInsets.symmetric(
|
|
horizontal: 8,
|
|
vertical: 8,
|
|
),
|
|
child: Text(
|
|
appLocalizations.sourceIp,
|
|
style: context.textTheme.bodyMedium,
|
|
),
|
|
),
|
|
onPressed: () {
|
|
setState(() {
|
|
_src = !_src;
|
|
});
|
|
},
|
|
),
|
|
CommonCard(
|
|
radius: 8,
|
|
isSelected: _noResolve,
|
|
child: Padding(
|
|
padding: EdgeInsets.symmetric(
|
|
horizontal: 8,
|
|
vertical: 8,
|
|
),
|
|
child: Text(
|
|
appLocalizations.noResolve,
|
|
style: context.textTheme.bodyMedium,
|
|
),
|
|
),
|
|
onPressed: () {
|
|
setState(() {
|
|
_noResolve = !_noResolve;
|
|
});
|
|
},
|
|
),
|
|
],
|
|
),
|
|
],
|
|
SizedBox(height: 20),
|
|
],
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|