Files
MWClash/lib/features/overwrite/rule.dart
chen08209 6e404ab19c Fix windows some issues
Optimize overwrite handle

Optimize access control page

Optimize some details
2025-12-12 14:33:03 +08:00

304 lines
9.4 KiB
Dart

library;
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/clash_config.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/card.dart';
import 'package:fl_clash/widgets/dialog.dart';
import 'package:fl_clash/widgets/input.dart';
import 'package:fl_clash/widgets/list.dart';
import 'package:flutter/material.dart';
class RuleItem extends StatelessWidget {
final bool isSelected;
final bool isEditing;
final Rule rule;
final void Function(String id) onSelected;
final void Function(Rule rule) onEdit;
const RuleItem({
super.key,
required this.isSelected,
required this.rule,
required this.onSelected,
required this.onEdit,
this.isEditing = false,
});
@override
Widget build(BuildContext context) {
return CommonSelectedListItem(
isSelected: isSelected,
onSelected: () {
onSelected(rule.id);
},
title: Text(
rule.value,
style: context.textTheme.bodyMedium?.toJetBrainsMono,
),
onPressed: () {
onEdit(rule);
},
);
}
}
class RuleStatusItem extends StatelessWidget {
final bool status;
final Rule rule;
final void Function(bool) onChange;
const RuleStatusItem({
super.key,
required this.status,
required this.rule,
required this.onChange,
});
@override
Widget build(BuildContext context) {
return Material(
color: Colors.transparent,
child: Container(
margin: EdgeInsets.symmetric(vertical: 4),
child: CommonCard(
padding: EdgeInsets.zero,
radius: 18,
type: CommonCardType.filled,
onPressed: () {
onChange(!status);
},
child: ListTile(
minTileHeight: 0,
minVerticalPadding: 0,
titleTextStyle: context.textTheme.bodyMedium?.toJetBrainsMono,
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 16,
),
trailing: Switch(value: status, onChanged: onChange),
title: Text(rule.value),
),
),
),
);
}
}
class AddOrEditRuleDialog extends StatefulWidget {
final Rule? rule;
const AddOrEditRuleDialog({super.key, this.rule});
@override
State<AddOrEditRuleDialog> createState() => _AddOrEditRuleDialogState();
}
class _AddOrEditRuleDialogState extends State<AddOrEditRuleDialog> {
late RuleAction _ruleAction;
final _ruleTargetController = TextEditingController();
final _contentController = TextEditingController();
bool _noResolve = false;
bool _src = false;
List<DropdownMenuEntry> _targetItems = [];
final _formKey = GlobalKey<FormState>();
@override
void initState() {
_initState();
super.initState();
}
void _initState() {
_targetItems = [
...RuleTarget.values.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 ?? '';
_noResolve = parsedRule.noResolve;
_src = parsedRule.src;
return;
}
_ruleAction = RuleAction.addedRuleActions.first;
if (_targetItems.isNotEmpty) {
_ruleTargetController.text = _targetItems.first.value;
}
}
@override
void didUpdateWidget(AddOrEditRuleDialog 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,
ruleTarget: _ruleTargetController.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: widget.rule != null
? appLocalizations.editRule
: 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>(
filter: false,
child: OptionsDialog<RuleAction>(
title: appLocalizations.ruleName,
options: RuleAction.addedRuleActions,
textBuilder: (item) => item.value,
value: _ruleAction,
),
) ??
_ruleAction;
setState(() {});
},
child: Text(_ruleAction.value),
),
SizedBox(height: 24),
TextFormField(
keyboardType: TextInputType.text,
onFieldSubmitted: (_) {
_handleSubmit();
},
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),
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),
],
);
},
),
),
),
);
}
}