mirror of
https://github.com/JGeek00/adguard-home-manager.git
synced 2025-04-20 22:09:11 +00:00
Added edit custom rules
This commit is contained in:
parent
2fca44f195
commit
c150d2ba23
10 changed files with 789 additions and 436 deletions
|
@ -764,5 +764,10 @@
|
||||||
"subnetPrefixLengthIpv6": "Subnet prefix length for IPv6",
|
"subnetPrefixLengthIpv6": "Subnet prefix length for IPv6",
|
||||||
"rateLimitAllowlist": "Rate limit allowlist",
|
"rateLimitAllowlist": "Rate limit allowlist",
|
||||||
"rateLimitAllowlistDescription": "IP addresses excluded from rate limiting",
|
"rateLimitAllowlistDescription": "IP addresses excluded from rate limiting",
|
||||||
"dnsOptions": "DNS options"
|
"dnsOptions": "DNS options",
|
||||||
|
"editor": "Editor",
|
||||||
|
"editCustomRules": "Edit custom rules",
|
||||||
|
"savingCustomRules": "Saving custom rules...",
|
||||||
|
"customRulesUpdatedSuccessfully": "Custom rules updated successfully",
|
||||||
|
"customRulesNotUpdated": "Custom rules could not be updated"
|
||||||
}
|
}
|
|
@ -764,5 +764,10 @@
|
||||||
"subnetPrefixLengthIpv6": "Longitud del prefijo de subred para IPv6",
|
"subnetPrefixLengthIpv6": "Longitud del prefijo de subred para IPv6",
|
||||||
"rateLimitAllowlist": "Lista de permitidos de limitación de velocidad",
|
"rateLimitAllowlist": "Lista de permitidos de limitación de velocidad",
|
||||||
"rateLimitAllowlistDescription": "Direcciones IP excluidas de la limitación de velocidad",
|
"rateLimitAllowlistDescription": "Direcciones IP excluidas de la limitación de velocidad",
|
||||||
"dnsOptions": "Opciones de DNS"
|
"dnsOptions": "Opciones de DNS",
|
||||||
|
"editor": "Editor",
|
||||||
|
"editCustomRules": "Editar reglas personalizadas",
|
||||||
|
"savingCustomRules": "Guardando reglas personalizadas...",
|
||||||
|
"customRulesUpdatedSuccessfully": "Reglas personalizadas actualizadas correctamente",
|
||||||
|
"customRulesNotUpdated": "Las reglas personalizadas no pudieron ser actualizadas"
|
||||||
}
|
}
|
|
@ -289,6 +289,23 @@ class FilteringProvider with ChangeNotifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<bool> setCustomRules(List<String> rules) async {
|
||||||
|
final newRules = rules.where((r) => r != " " && r != "").toList();
|
||||||
|
final result = await _serversProvider!.apiClient2!.setCustomRules(rules: newRules);
|
||||||
|
|
||||||
|
if (result.successful == true) {
|
||||||
|
Filtering filteringData = filtering!;
|
||||||
|
filteringData.userRules = newRules;
|
||||||
|
_filtering = filteringData;
|
||||||
|
notifyListeners();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
notifyListeners();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<Map<String, dynamic>> addList({required String name, required String url, required String type}) async {
|
Future<Map<String, dynamic>> addList({required String name, required String url, required String type}) async {
|
||||||
final result1 = await _serversProvider!.apiClient2!.addFilteringList(
|
final result1 = await _serversProvider!.apiClient2!.addFilteringList(
|
||||||
data: {
|
data: {
|
||||||
|
|
|
@ -6,7 +6,8 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
import 'package:adguard_home_manager/screens/filters/modals/add_custom_rule.dart';
|
import 'package:adguard_home_manager/screens/filters/modals/add_custom_rule/edit_custom_rules.dart';
|
||||||
|
import 'package:adguard_home_manager/screens/filters/modals/add_custom_rule/add_custom_rule.dart';
|
||||||
import 'package:adguard_home_manager/screens/filters/details/add_list_modal.dart';
|
import 'package:adguard_home_manager/screens/filters/details/add_list_modal.dart';
|
||||||
|
|
||||||
import 'package:adguard_home_manager/providers/filtering_provider.dart';
|
import 'package:adguard_home_manager/providers/filtering_provider.dart';
|
||||||
|
@ -19,10 +20,10 @@ class AddFiltersButton extends StatelessWidget {
|
||||||
final Widget Function(void Function()) widget;
|
final Widget Function(void Function()) widget;
|
||||||
|
|
||||||
const AddFiltersButton({
|
const AddFiltersButton({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.type,
|
required this.type,
|
||||||
required this.widget
|
required this.widget
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -56,6 +57,31 @@ class AddFiltersButton extends StatelessWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void confirmEditCustomRules(List<String> rules) async {
|
||||||
|
ProcessModal processModal = ProcessModal();
|
||||||
|
processModal.open(AppLocalizations.of(context)!.savingCustomRules);
|
||||||
|
|
||||||
|
final result = await filteringProvider.setCustomRules(rules);
|
||||||
|
|
||||||
|
processModal.close();
|
||||||
|
|
||||||
|
if (!context.mounted) return;
|
||||||
|
if (result == true) {
|
||||||
|
showSnacbkar(
|
||||||
|
appConfigProvider: appConfigProvider,
|
||||||
|
label: AppLocalizations.of(context)!.customRulesUpdatedSuccessfully,
|
||||||
|
color: Colors.green
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
showSnacbkar(
|
||||||
|
appConfigProvider: appConfigProvider,
|
||||||
|
label: AppLocalizations.of(context)!.customRulesNotUpdated,
|
||||||
|
color: Colors.red
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void openAddCustomRule() {
|
void openAddCustomRule() {
|
||||||
showGeneralDialog(
|
showGeneralDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
@ -83,6 +109,33 @@ class AddFiltersButton extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void openEditCustomRule() {
|
||||||
|
showGeneralDialog(
|
||||||
|
context: context,
|
||||||
|
barrierColor: !(width > 700 || !(Platform.isAndroid || Platform.isIOS))
|
||||||
|
?Colors.transparent
|
||||||
|
: Colors.black54,
|
||||||
|
transitionBuilder: (context, anim1, anim2, child) {
|
||||||
|
return SlideTransition(
|
||||||
|
position: Tween(
|
||||||
|
begin: const Offset(0, 1),
|
||||||
|
end: const Offset(0, 0)
|
||||||
|
).animate(
|
||||||
|
CurvedAnimation(
|
||||||
|
parent: anim1,
|
||||||
|
curve: Curves.easeInOutCubicEmphasized
|
||||||
|
)
|
||||||
|
),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
pageBuilder: (context, animation, secondaryAnimation) => EditCustomRules(
|
||||||
|
fullScreen: !(width > 700 || !(Platform.isAndroid || Platform.isIOS)),
|
||||||
|
onConfirm: confirmEditCustomRules,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
void confirmAddList({required String name, required String url, required String type}) async {
|
void confirmAddList({required String name, required String url, required String type}) async {
|
||||||
ProcessModal processModal = ProcessModal();
|
ProcessModal processModal = ProcessModal();
|
||||||
processModal.open(AppLocalizations.of(context)!.addingList);
|
processModal.open(AppLocalizations.of(context)!.addingList);
|
||||||
|
@ -147,10 +200,25 @@ class AddFiltersButton extends StatelessWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return widget(
|
switch (type) {
|
||||||
type == 'blacklist' || type == 'whitelist'
|
case 'blacklist':
|
||||||
? () => openAddWhitelistBlacklist()
|
case 'whitelist':
|
||||||
: () => openAddCustomRule(),
|
return widget(
|
||||||
);
|
() => openAddWhitelistBlacklist(),
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'add_custom_rule':
|
||||||
|
return widget(
|
||||||
|
() => openAddCustomRule(),
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'edit_custom_rule':
|
||||||
|
return widget(
|
||||||
|
() => openEditCustomRule(),
|
||||||
|
);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -20,12 +20,12 @@ class CustomRulesList extends StatefulWidget {
|
||||||
final void Function(String) onRemoveCustomRule;
|
final void Function(String) onRemoveCustomRule;
|
||||||
|
|
||||||
const CustomRulesList({
|
const CustomRulesList({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.loadStatus,
|
required this.loadStatus,
|
||||||
required this.scrollController,
|
required this.scrollController,
|
||||||
required this.data,
|
required this.data,
|
||||||
required this.onRemoveCustomRule
|
required this.onRemoveCustomRule
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<CustomRulesList> createState() => _CustomRulesListState();
|
State<CustomRulesList> createState() => _CustomRulesListState();
|
||||||
|
@ -210,13 +210,26 @@ class _CustomRulesListState extends State<CustomRulesList> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
fab: AddFiltersButton(
|
fab: Column(
|
||||||
type: 'custom_rule',
|
children: [
|
||||||
widget: (fn) => FloatingActionButton(
|
AddFiltersButton(
|
||||||
onPressed: fn,
|
type: 'edit_custom_rule',
|
||||||
child: const Icon(Icons.add),
|
widget: (fn) => FloatingActionButton.small(
|
||||||
),
|
onPressed: fn,
|
||||||
|
child: const Icon(Icons.edit_rounded),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
AddFiltersButton(
|
||||||
|
type: 'add_custom_rule',
|
||||||
|
widget: (fn) => FloatingActionButton(
|
||||||
|
onPressed: fn,
|
||||||
|
child: const Icon(Icons.add),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
|
heightFabHidden: -120,
|
||||||
fabVisible: isVisible,
|
fabVisible: isVisible,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,416 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:segmented_button_slide/segmented_button_slide.dart';
|
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
|
||||||
|
|
||||||
import 'package:adguard_home_manager/functions/open_url.dart';
|
|
||||||
import 'package:adguard_home_manager/constants/urls.dart';
|
|
||||||
|
|
||||||
class AddCustomRule extends StatefulWidget {
|
|
||||||
final void Function(String) onConfirm;
|
|
||||||
final bool fullScreen;
|
|
||||||
|
|
||||||
const AddCustomRule({
|
|
||||||
super.key,
|
|
||||||
required this.onConfirm,
|
|
||||||
required this.fullScreen
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<AddCustomRule> createState() => _AddCustomRuleState();
|
|
||||||
}
|
|
||||||
|
|
||||||
enum BlockingPresets { block, unblock, custom }
|
|
||||||
|
|
||||||
class _AddCustomRuleState extends State<AddCustomRule> {
|
|
||||||
final TextEditingController domainController = TextEditingController();
|
|
||||||
String? domainError;
|
|
||||||
|
|
||||||
BlockingPresets preset = BlockingPresets.block;
|
|
||||||
|
|
||||||
bool addImportant = false;
|
|
||||||
|
|
||||||
bool checkValidValues() {
|
|
||||||
if (
|
|
||||||
domainController.text != '' &&
|
|
||||||
domainError == null
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void validateDomain(String value) {
|
|
||||||
final domainRegex = RegExp(r'^(([a-zA-Z]{1})|([a-zA-Z]{1}[a-zA-Z]{1})|([a-zA-Z]{1}[0-9]{1})|([0-9]{1}[a-zA-Z]{1})|([a-zA-Z0-9][a-zA-Z0-9-_]{1,61}[a-zA-Z0-9]))\.([a-zA-Z]{2,6}|[a-zA-Z0-9-]{2,30}\.[a-zA-Z]{2,3})$');
|
|
||||||
if (domainRegex.hasMatch(value)) {
|
|
||||||
setState(() => domainError = null);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
setState(() => domainError = AppLocalizations.of(context)!.domainNotValid);
|
|
||||||
}
|
|
||||||
checkValidValues();
|
|
||||||
}
|
|
||||||
|
|
||||||
String buildRule({String?value}) {
|
|
||||||
String rule = "";
|
|
||||||
|
|
||||||
String fieldValue = value ?? domainController.text;
|
|
||||||
|
|
||||||
if (preset == BlockingPresets.block) {
|
|
||||||
rule = "||${fieldValue.trim()}^";
|
|
||||||
}
|
|
||||||
else if (preset == BlockingPresets.unblock) {
|
|
||||||
rule = "@@||${fieldValue.trim()}^";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
rule = fieldValue.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (addImportant == true) {
|
|
||||||
rule = "$rule\$important";
|
|
||||||
}
|
|
||||||
|
|
||||||
return rule;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
|
|
||||||
List<Widget> content() {
|
|
||||||
return [
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 10,
|
|
||||||
vertical: 5
|
|
||||||
),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
|
|
||||||
borderRadius: BorderRadius.circular(30),
|
|
||||||
border: Border.all(
|
|
||||||
color: Theme.of(context).colorScheme.primary
|
|
||||||
)
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
buildRule(),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
fontWeight: FontWeight.w500
|
|
||||||
),
|
|
||||||
)
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Container(height: 30),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
|
||||||
child: TextFormField(
|
|
||||||
controller: domainController,
|
|
||||||
onChanged: (value) => setState(() => {}),
|
|
||||||
decoration: InputDecoration(
|
|
||||||
prefixIcon: const Icon(Icons.link_rounded),
|
|
||||||
border: const OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(
|
|
||||||
Radius.circular(10)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
errorText: domainError,
|
|
||||||
labelText: AppLocalizations.of(context)!.domain,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(height: 30),
|
|
||||||
SegmentedButtonSlide(
|
|
||||||
entries: [
|
|
||||||
SegmentedButtonSlideEntry(label: AppLocalizations.of(context)!.block),
|
|
||||||
SegmentedButtonSlideEntry(label: AppLocalizations.of(context)!.unblock),
|
|
||||||
SegmentedButtonSlideEntry(label: AppLocalizations.of(context)!.custom),
|
|
||||||
],
|
|
||||||
selectedEntry: preset.index,
|
|
||||||
onChange: (v) => setState(() => preset = BlockingPresets.values[v]),
|
|
||||||
colors: SegmentedButtonSlideColors(
|
|
||||||
barColor: Theme.of(context).colorScheme.primary.withOpacity(0.2),
|
|
||||||
backgroundSelectedColor: Theme.of(context).colorScheme.primary,
|
|
||||||
foregroundSelectedColor: Theme.of(context).colorScheme.onPrimary,
|
|
||||||
foregroundUnselectedColor: Theme.of(context).colorScheme.onSurface,
|
|
||||||
hoverColor: Theme.of(context).colorScheme.onSurfaceVariant,
|
|
||||||
),
|
|
||||||
textOverflow: TextOverflow.ellipsis,
|
|
||||||
fontSize: 14,
|
|
||||||
height: 40,
|
|
||||||
margin: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 24,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(height: 20),
|
|
||||||
Material(
|
|
||||||
color: Colors.transparent,
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () => setState(() => addImportant = !addImportant),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 28),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 10),
|
|
||||||
child: Text(
|
|
||||||
AppLocalizations.of(context)!.addImportant,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
color: Theme.of(context).colorScheme.onSurface
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Switch(
|
|
||||||
value: addImportant,
|
|
||||||
onChanged: (value) => setState(() => addImportant = value),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(height: 20),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
|
||||||
child: Card(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(20),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
Icons.info,
|
|
||||||
color: Theme.of(context).colorScheme.onSurface
|
|
||||||
),
|
|
||||||
const SizedBox(width: 20),
|
|
||||||
Text(
|
|
||||||
AppLocalizations.of(context)!.examples,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 18,
|
|
||||||
color: Theme.of(context).colorScheme.onSurface
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
SizedBox(
|
|
||||||
width: double.maxFinite,
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
"||example.org^",
|
|
||||||
textAlign: TextAlign.left,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
color: Theme.of(context).colorScheme.primary
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 5),
|
|
||||||
Text(
|
|
||||||
AppLocalizations.of(context)!.example1,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
color: Theme.of(context).colorScheme.primary
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
Text(
|
|
||||||
"@@||example.org^",
|
|
||||||
textAlign: TextAlign.left,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
color: Theme.of(context).colorScheme.primary
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 5),
|
|
||||||
Text(
|
|
||||||
AppLocalizations.of(context)!.example2,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
color: Theme.of(context).colorScheme.primary
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
Text(
|
|
||||||
"! Here goes a comment",
|
|
||||||
textAlign: TextAlign.left,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
color: Theme.of(context).colorScheme.primary
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
"# Also a comment",
|
|
||||||
textAlign: TextAlign.left,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
color: Theme.of(context).colorScheme.primary
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 5),
|
|
||||||
Text(
|
|
||||||
AppLocalizations.of(context)!.example3,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
color: Theme.of(context).colorScheme.primary
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
Text(
|
|
||||||
"/REGEX/",
|
|
||||||
textAlign: TextAlign.left,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
color: Theme.of(context).colorScheme.primary
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 5),
|
|
||||||
Text(
|
|
||||||
AppLocalizations.of(context)!.example4,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
color: Theme.of(context).colorScheme.primary
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(height: 20),
|
|
||||||
Material(
|
|
||||||
color: Colors.transparent,
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () => openUrl(Urls.customRuleDocs),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 10),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 10),
|
|
||||||
child: Text(
|
|
||||||
AppLocalizations.of(context)!.moreInformation,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
color: Theme.of(context).colorScheme.onSurface
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(right: 15),
|
|
||||||
child: Icon(
|
|
||||||
Icons.open_in_new,
|
|
||||||
color: Theme.of(context).colorScheme.onSurface
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(height: 20)
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (widget.fullScreen == true) {
|
|
||||||
return Dialog.fullscreen(
|
|
||||||
child: Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
leading: CloseButton(onPressed: () => Navigator.pop(context)),
|
|
||||||
title: Text(AppLocalizations.of(context)!.addCustomRule),
|
|
||||||
actions: [
|
|
||||||
IconButton(
|
|
||||||
onPressed: checkValidValues() == true
|
|
||||||
? () {
|
|
||||||
Navigator.pop(context);
|
|
||||||
widget.onConfirm(buildRule());
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
icon: const Icon(Icons.check)
|
|
||||||
),
|
|
||||||
const SizedBox(width: 10)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
body: SafeArea(
|
|
||||||
child: ListView(
|
|
||||||
children: content(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return Dialog(
|
|
||||||
child: ConstrainedBox(
|
|
||||||
constraints: const BoxConstraints(
|
|
||||||
maxWidth: 500
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
IconButton(
|
|
||||||
onPressed: () => Navigator.pop(context),
|
|
||||||
icon: const Icon(Icons.clear_rounded),
|
|
||||||
tooltip: AppLocalizations.of(context)!.close,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Text(
|
|
||||||
AppLocalizations.of(context)!.addCustomRule,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 22
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
onPressed: checkValidValues() == true
|
|
||||||
? () {
|
|
||||||
Navigator.pop(context);
|
|
||||||
widget.onConfirm(buildRule());
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
icon: const Icon(Icons.check)
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Flexible(
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
child: Wrap(
|
|
||||||
alignment: WrapAlignment.center,
|
|
||||||
children: content(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
326
lib/screens/filters/modals/add_custom_rule/add_custom_rule.dart
Normal file
326
lib/screens/filters/modals/add_custom_rule/add_custom_rule.dart
Normal file
|
@ -0,0 +1,326 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:segmented_button_slide/segmented_button_slide.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
import 'package:adguard_home_manager/screens/filters/modals/add_custom_rule/custom_rule_docs.dart';
|
||||||
|
|
||||||
|
enum _BlockingPresets { block, unblock, custom }
|
||||||
|
|
||||||
|
class AddCustomRule extends StatefulWidget {
|
||||||
|
final void Function(String) onConfirm;
|
||||||
|
final bool fullScreen;
|
||||||
|
|
||||||
|
const AddCustomRule({
|
||||||
|
super.key,
|
||||||
|
required this.onConfirm,
|
||||||
|
required this.fullScreen
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<AddCustomRule> createState() => _AddCustomRuleState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AddCustomRuleState extends State<AddCustomRule> {
|
||||||
|
final TextEditingController _domainController = TextEditingController();
|
||||||
|
String? _domainError;
|
||||||
|
|
||||||
|
_BlockingPresets _preset = _BlockingPresets.block;
|
||||||
|
|
||||||
|
bool _addImportant = false;
|
||||||
|
|
||||||
|
bool _checkValidValues() {
|
||||||
|
if (
|
||||||
|
_domainController.text != '' &&
|
||||||
|
_domainError == null
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void validateDomain(String value) {
|
||||||
|
final domainRegex = RegExp(r'^(([a-zA-Z]{1})|([a-zA-Z]{1}[a-zA-Z]{1})|([a-zA-Z]{1}[0-9]{1})|([0-9]{1}[a-zA-Z]{1})|([a-zA-Z0-9][a-zA-Z0-9-_]{1,61}[a-zA-Z0-9]))\.([a-zA-Z]{2,6}|[a-zA-Z0-9-]{2,30}\.[a-zA-Z]{2,3})$');
|
||||||
|
if (domainRegex.hasMatch(value)) {
|
||||||
|
setState(() => _domainError = null);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setState(() => _domainError = AppLocalizations.of(context)!.domainNotValid);
|
||||||
|
}
|
||||||
|
_checkValidValues();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (widget.fullScreen == true) {
|
||||||
|
return Dialog.fullscreen(
|
||||||
|
child: Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
leading: CloseButton(onPressed: () => Navigator.pop(context)),
|
||||||
|
title: Text(AppLocalizations.of(context)!.addCustomRule),
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: _checkValidValues() == true
|
||||||
|
? () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
widget.onConfirm(
|
||||||
|
_buildRule(
|
||||||
|
domainController: _domainController,
|
||||||
|
important: _addImportant,
|
||||||
|
preset: _preset
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
icon: const Icon(Icons.check)
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: SafeArea(
|
||||||
|
child: ListView(
|
||||||
|
children: [
|
||||||
|
_CustomRuleEditor(
|
||||||
|
domainController: _domainController,
|
||||||
|
domainError: _domainError,
|
||||||
|
important: _addImportant,
|
||||||
|
preset: _preset,
|
||||||
|
setImportant: (v) => setState(() => _addImportant = v),
|
||||||
|
setPreset: (v) => setState(() => _preset = v),
|
||||||
|
validateDomain: validateDomain
|
||||||
|
)
|
||||||
|
]
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return Dialog(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
maxWidth: 500
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
icon: const Icon(Icons.clear_rounded),
|
||||||
|
tooltip: AppLocalizations.of(context)!.close,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context)!.addCustomRule,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 22
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed: _checkValidValues() == true
|
||||||
|
? () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
widget.onConfirm(
|
||||||
|
_buildRule(
|
||||||
|
domainController: _domainController,
|
||||||
|
important: _addImportant,
|
||||||
|
preset: _preset
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
icon: const Icon(Icons.check)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Flexible(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Wrap(
|
||||||
|
alignment: WrapAlignment.center,
|
||||||
|
children: [
|
||||||
|
_CustomRuleEditor(
|
||||||
|
domainController: _domainController,
|
||||||
|
domainError: _domainError,
|
||||||
|
important: _addImportant,
|
||||||
|
preset: _preset,
|
||||||
|
setImportant: (v) => setState(() => _addImportant = v),
|
||||||
|
setPreset: (v) => setState(() => _preset = v),
|
||||||
|
validateDomain: validateDomain
|
||||||
|
)
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CustomRuleEditor extends StatelessWidget {
|
||||||
|
final TextEditingController domainController;
|
||||||
|
final String? domainError;
|
||||||
|
final bool important;
|
||||||
|
final void Function(bool) setImportant;
|
||||||
|
final _BlockingPresets preset;
|
||||||
|
final void Function(_BlockingPresets) setPreset;
|
||||||
|
final void Function(String) validateDomain;
|
||||||
|
|
||||||
|
const _CustomRuleEditor({
|
||||||
|
required this.domainController,
|
||||||
|
required this.domainError,
|
||||||
|
required this.important,
|
||||||
|
required this.setImportant,
|
||||||
|
required this.preset,
|
||||||
|
required this.setPreset,
|
||||||
|
required this.validateDomain,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 10,
|
||||||
|
vertical: 5
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(30),
|
||||||
|
border: Border.all(
|
||||||
|
color: Theme.of(context).colorScheme.primary
|
||||||
|
)
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
_buildRule(
|
||||||
|
domainController: domainController,
|
||||||
|
important: important,
|
||||||
|
preset: preset,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
fontWeight: FontWeight.w500
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Container(height: 30),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
child: TextFormField(
|
||||||
|
controller: domainController,
|
||||||
|
onChanged: validateDomain,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
prefixIcon: const Icon(Icons.link_rounded),
|
||||||
|
border: const OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(10)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
errorText: domainError,
|
||||||
|
labelText: AppLocalizations.of(context)!.domain,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(height: 30),
|
||||||
|
SegmentedButtonSlide(
|
||||||
|
entries: [
|
||||||
|
SegmentedButtonSlideEntry(label: AppLocalizations.of(context)!.block),
|
||||||
|
SegmentedButtonSlideEntry(label: AppLocalizations.of(context)!.unblock),
|
||||||
|
SegmentedButtonSlideEntry(label: AppLocalizations.of(context)!.custom),
|
||||||
|
],
|
||||||
|
selectedEntry: preset.index,
|
||||||
|
onChange: (v) => setPreset(_BlockingPresets.values[v]),
|
||||||
|
colors: SegmentedButtonSlideColors(
|
||||||
|
barColor: Theme.of(context).colorScheme.primary.withOpacity(0.2),
|
||||||
|
backgroundSelectedColor: Theme.of(context).colorScheme.primary,
|
||||||
|
foregroundSelectedColor: Theme.of(context).colorScheme.onPrimary,
|
||||||
|
foregroundUnselectedColor: Theme.of(context).colorScheme.onSurface,
|
||||||
|
hoverColor: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
textOverflow: TextOverflow.ellipsis,
|
||||||
|
fontSize: 14,
|
||||||
|
height: 40,
|
||||||
|
margin: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 24,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(height: 20),
|
||||||
|
Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () => setImportant(!important),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 28),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 10),
|
||||||
|
child: Text(
|
||||||
|
AppLocalizations.of(context)!.addImportant,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Switch(
|
||||||
|
value: important,
|
||||||
|
onChanged: setImportant,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(height: 20),
|
||||||
|
const CustomRuleDocs(),
|
||||||
|
Container(height: 20)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _buildRule({
|
||||||
|
String? value,
|
||||||
|
required TextEditingController domainController,
|
||||||
|
required _BlockingPresets preset,
|
||||||
|
required bool important
|
||||||
|
}) {
|
||||||
|
String rule = "";
|
||||||
|
String fieldValue = value ?? domainController.text;
|
||||||
|
if (preset == _BlockingPresets.block) {
|
||||||
|
rule = "||${fieldValue.trim()}^";
|
||||||
|
}
|
||||||
|
else if (preset == _BlockingPresets.unblock) {
|
||||||
|
rule = "@@||${fieldValue.trim()}^";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rule = fieldValue.trim();
|
||||||
|
}
|
||||||
|
if (important == true) {
|
||||||
|
rule = "$rule\$important";
|
||||||
|
}
|
||||||
|
return rule;
|
||||||
|
}
|
166
lib/screens/filters/modals/add_custom_rule/custom_rule_docs.dart
Normal file
166
lib/screens/filters/modals/add_custom_rule/custom_rule_docs.dart
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
import 'package:adguard_home_manager/constants/urls.dart';
|
||||||
|
import 'package:adguard_home_manager/functions/open_url.dart';
|
||||||
|
|
||||||
|
class CustomRuleDocs extends StatelessWidget {
|
||||||
|
const CustomRuleDocs({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
child: Card(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.info,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface
|
||||||
|
),
|
||||||
|
const SizedBox(width: 20),
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context)!.examples,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
SizedBox(
|
||||||
|
width: double.maxFinite,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"||example.org^",
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Theme.of(context).colorScheme.primary
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 5),
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context)!.example1,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Theme.of(context).colorScheme.primary
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
Text(
|
||||||
|
"@@||example.org^",
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Theme.of(context).colorScheme.primary
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 5),
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context)!.example2,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Theme.of(context).colorScheme.primary
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
Text(
|
||||||
|
"! Here goes a comment",
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Theme.of(context).colorScheme.primary
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"# Also a comment",
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Theme.of(context).colorScheme.primary
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 5),
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context)!.example3,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Theme.of(context).colorScheme.primary
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
Text(
|
||||||
|
"/REGEX/",
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Theme.of(context).colorScheme.primary
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 5),
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context)!.example4,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Theme.of(context).colorScheme.primary
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(height: 8),
|
||||||
|
Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () => openUrl(Urls.customRuleDocs),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 10),
|
||||||
|
child: Text(
|
||||||
|
AppLocalizations.of(context)!.moreInformation,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 15),
|
||||||
|
child: Icon(
|
||||||
|
Icons.open_in_new,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,167 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
import 'package:adguard_home_manager/screens/filters/modals/add_custom_rule/custom_rule_docs.dart';
|
||||||
|
|
||||||
|
import 'package:adguard_home_manager/providers/filtering_provider.dart';
|
||||||
|
|
||||||
|
class EditCustomRules extends StatefulWidget {
|
||||||
|
final bool fullScreen;
|
||||||
|
final void Function(List<String>) onConfirm;
|
||||||
|
|
||||||
|
const EditCustomRules({
|
||||||
|
super.key,
|
||||||
|
required this.fullScreen,
|
||||||
|
required this.onConfirm,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<EditCustomRules> createState() => _EditCustomRulesState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _EditCustomRulesState extends State<EditCustomRules> {
|
||||||
|
final _fieldController = TextEditingController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
final filteringProvider = Provider.of<FilteringProvider>(context, listen: false);
|
||||||
|
print(filteringProvider.filtering!.userRules);
|
||||||
|
if (filteringProvider.filtering != null) {
|
||||||
|
_fieldController.text = filteringProvider.filtering!.userRules.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (widget.fullScreen == true) {
|
||||||
|
return Dialog.fullscreen(
|
||||||
|
child: Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
leading: CloseButton(onPressed: () => Navigator.pop(context)),
|
||||||
|
title: Text(AppLocalizations.of(context)!.editCustomRules),
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
widget.onConfirm(_fieldController.text.split("\n"));
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.save_rounded),
|
||||||
|
tooltip: AppLocalizations.of(context)!.save,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: SafeArea(
|
||||||
|
child: ListView(
|
||||||
|
children: [
|
||||||
|
_CustomRulesRawEditor(fieldController: _fieldController)
|
||||||
|
]
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return Dialog(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
maxWidth: 500
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
icon: const Icon(Icons.clear_rounded),
|
||||||
|
tooltip: AppLocalizations.of(context)!.close,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context)!.addCustomRule,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 22
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
widget.onConfirm(_fieldController.text.split("\n"));
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.save_rounded),
|
||||||
|
tooltip: AppLocalizations.of(context)!.save,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Flexible(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Wrap(
|
||||||
|
alignment: WrapAlignment.center,
|
||||||
|
children: [
|
||||||
|
_CustomRulesRawEditor(fieldController: _fieldController)
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CustomRulesRawEditor extends StatelessWidget {
|
||||||
|
final TextEditingController fieldController;
|
||||||
|
|
||||||
|
const _CustomRulesRawEditor({
|
||||||
|
required this.fieldController
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
SizedBox(
|
||||||
|
height: 300,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 28),
|
||||||
|
child: TextField(
|
||||||
|
controller: fieldController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: const OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(10)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
labelText: AppLocalizations.of(context)!.rules,
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.always
|
||||||
|
),
|
||||||
|
autocorrect: false,
|
||||||
|
expands: true,
|
||||||
|
minLines: null,
|
||||||
|
maxLines: null,
|
||||||
|
textAlignVertical: TextAlignVertical.top,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
const CustomRuleDocs(),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,6 +17,7 @@ class CustomTabContentList extends StatelessWidget {
|
||||||
final bool? fabVisible;
|
final bool? fabVisible;
|
||||||
final bool? noSliver;
|
final bool? noSliver;
|
||||||
final EdgeInsets? listPadding;
|
final EdgeInsets? listPadding;
|
||||||
|
final double? heightFabHidden;
|
||||||
|
|
||||||
const CustomTabContentList({
|
const CustomTabContentList({
|
||||||
super.key,
|
super.key,
|
||||||
|
@ -31,7 +32,8 @@ class CustomTabContentList extends StatelessWidget {
|
||||||
this.fab,
|
this.fab,
|
||||||
this.fabVisible,
|
this.fabVisible,
|
||||||
this.noSliver,
|
this.noSliver,
|
||||||
this.listPadding
|
this.listPadding,
|
||||||
|
this.heightFabHidden,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -150,7 +152,7 @@ class CustomTabContentList extends StatelessWidget {
|
||||||
bottom: fabVisible != null && fabVisible == true ?
|
bottom: fabVisible != null && fabVisible == true ?
|
||||||
appConfigProvider.showingSnackbar
|
appConfigProvider.showingSnackbar
|
||||||
? 90 : 20
|
? 90 : 20
|
||||||
: -90,
|
: (heightFabHidden ?? -90),
|
||||||
right: 20,
|
right: 20,
|
||||||
child: SafeArea(child: fab!)
|
child: SafeArea(child: fab!)
|
||||||
),
|
),
|
||||||
|
|
Loading…
Add table
Reference in a new issue