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 createState() => _AddOrEditRuleDialogState(); } class _AddOrEditRuleDialogState extends State { late RuleAction _ruleAction; final _ruleTargetController = TextEditingController(); final _contentController = TextEditingController(); bool _noResolve = false; bool _src = false; List _targetItems = []; final _formKey = GlobalKey(); @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( filter: false, child: OptionsDialog( 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( 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), ], ); }, ), ), ), ); } }