diff --git a/lib/constants/enums.dart b/lib/constants/enums.dart index f54e665..569db37 100644 --- a/lib/constants/enums.dart +++ b/lib/constants/enums.dart @@ -1,2 +1,3 @@ enum LoadStatus { loading, loaded, error } -enum HomeTopItems { queriedDomains, blockedDomains, recurrentClients, topUpstreams, avgUpstreamResponseTime } \ No newline at end of file +enum HomeTopItems { queriedDomains, blockedDomains, recurrentClients, topUpstreams, avgUpstreamResponseTime } +enum CustomRulesSorting { topBottom, bottomTop } \ No newline at end of file diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index d72a148..4602c27 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -801,5 +801,7 @@ "applicationDetails": "Application details", "applicationDetailsDescription": "App repository, stores where it's available, and more", "myOtherApps": "Check my other apps", - "myOtherAppsDescription": "Created by JGeek00" + "myOtherAppsDescription": "Created by JGeek00", + "topToBottom": "From top to bottom", + "bottomToTop": "From bottom to top" } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 952104a..862e375 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -801,5 +801,7 @@ "applicationDetails": "Detalles de la aplicación", "applicationDetailsDescription": "Repositorio de la app, tiendas donde está disponible, y más", "myOtherApps": "Comprueba mis otras apps", - "myOtherAppsDescription": "Creadas por JGeek00" + "myOtherAppsDescription": "Creadas por JGeek00", + "topToBottom": "Desde arriba hacia abajo", + "bottomToTop": "Desde abajo hacia arriba" } \ No newline at end of file diff --git a/lib/screens/filters/custom_rules_list.dart b/lib/screens/filters/custom_rules_list.dart index 4cd5bde..284e15f 100644 --- a/lib/screens/filters/custom_rules_list.dart +++ b/lib/screens/filters/custom_rules_list.dart @@ -6,6 +6,7 @@ import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/screens/filters/add_button.dart'; +import 'package:adguard_home_manager/screens/filters/modals/custom_rules/sort_rules.dart'; import 'package:adguard_home_manager/widgets/tab_content_list.dart'; import 'package:adguard_home_manager/functions/snackbar.dart'; @@ -34,6 +35,8 @@ class CustomRulesList extends StatefulWidget { class _CustomRulesListState extends State { late bool isVisible; + CustomRulesSorting _sortingMethod = CustomRulesSorting.topBottom; + @override initState(){ super.initState(); @@ -60,6 +63,8 @@ class _CustomRulesListState extends State { final filteringProvider = Provider.of(context); final appConfigProvider = Provider.of(context); + final renderData = _sortingMethod == CustomRulesSorting.bottomTop ? widget.data.reversed.toList() : widget.data.toList(); + bool checkIfComment(String value) { final regex = RegExp(r'^(!|#).*$'); if (regex.hasMatch(value)) { @@ -104,6 +109,16 @@ class _CustomRulesListState extends State { } } + void showSortingMethodModal() { + showDialog( + context: context, + builder: (ctx) => SortCustomRulesModal( + sortingMethod: _sortingMethod, + onSelect: (value) => setState(() => _sortingMethod = value), + ), + ); + } + return CustomTabContentList( loadingGenerator: () => SizedBox( width: double.maxFinite, @@ -124,10 +139,10 @@ class _CustomRulesListState extends State { ], ), ), - itemsCount: widget.data.length, + itemsCount: renderData.length, contentWidget: (index) => ListTile( title: Text( - widget.data[index], + renderData[index], style: TextStyle( color: checkIfComment(widget.data[index]) == true ? Theme.of(context).colorScheme.onSurface.withOpacity(0.6) @@ -135,9 +150,9 @@ class _CustomRulesListState extends State { fontWeight: FontWeight.normal, ), ), - subtitle: generateSubtitle(widget.data[index]), + subtitle: generateSubtitle(renderData[index]), trailing: IconButton( - onPressed: () => widget.onRemoveCustomRule(widget.data[index]), + onPressed: () => widget.onRemoveCustomRule(renderData[index]), icon: const Icon(Icons.delete) ), ), @@ -212,6 +227,11 @@ class _CustomRulesListState extends State { }, fab: Column( children: [ + FloatingActionButton.small( + onPressed: showSortingMethodModal, + child: Icon(Icons.sort_rounded), + ), + const SizedBox(height: 16), AddFiltersButton( type: 'edit_custom_rule', widget: (fn) => FloatingActionButton.small( @@ -229,7 +249,7 @@ class _CustomRulesListState extends State { ), ], ), - heightFabHidden: -120, + heightFabHidden: -180, fabVisible: isVisible, ); } diff --git a/lib/screens/filters/modals/custom_rules/sort_rules.dart b/lib/screens/filters/modals/custom_rules/sort_rules.dart new file mode 100644 index 0000000..7ae0870 --- /dev/null +++ b/lib/screens/filters/modals/custom_rules/sort_rules.dart @@ -0,0 +1,128 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +import 'package:adguard_home_manager/constants/enums.dart'; + +class SortCustomRulesModal extends StatelessWidget { + final CustomRulesSorting sortingMethod; + final void Function(CustomRulesSorting) onSelect; + + const SortCustomRulesModal({ + super.key, + required this.sortingMethod, + required this.onSelect, + }); + + @override + Widget build(BuildContext context) { + return AlertDialog( + contentPadding: const EdgeInsets.symmetric(vertical: 16), + scrollable: true, + title: Column( + children: [ + Icon( + Icons.sort_rounded, + size: 24, + color: Theme.of(context).listTileTheme.iconColor + ), + const SizedBox(height: 16), + Text( + AppLocalizations.of(context)!.sortingOptions, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface + ), + ) + ], + ), + content: ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 500 + ), + child: Column( + children: [ + _CustomListTileDialog( + title: AppLocalizations.of(context)!.topToBottom, + icon: Icons.arrow_downward_rounded, + onTap: () { + Navigator.pop(context); + onSelect(CustomRulesSorting.topBottom); + }, + isSelected: sortingMethod == CustomRulesSorting.topBottom + ), + _CustomListTileDialog( + title: AppLocalizations.of(context)!.bottomToTop, + icon: Icons.arrow_upward_rounded, + onTap: () { + Navigator.pop(context); + onSelect(CustomRulesSorting.bottomTop); + }, + isSelected: sortingMethod == CustomRulesSorting.bottomTop + ), + ] + ), + ), + actions: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(AppLocalizations.of(context)!.close) + ) + ], + ) + ], + ); + } +} + +class _CustomListTileDialog extends StatelessWidget { + final String title; + final IconData? icon; + final void Function()? onTap; + final bool isSelected; + + const _CustomListTileDialog({ + required this.title, + required this.icon, + required this.onTap, + required this.isSelected, + }); + + @override + Widget build(BuildContext context) { + return Material( + color: Colors.transparent, + child: InkWell( + onTap: onTap, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), + child: Row( + children: [ + Icon( + icon, + color: Theme.of(context).colorScheme.onSurface, + ), + const SizedBox(width: 24), + Flexible( + child: Text( + title, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w400, + color: Theme.of(context).colorScheme.onSurface, + ), + ), + ), + const SizedBox(width: 24), + Icon( + isSelected == true ? Icons.radio_button_checked_rounded : Icons.radio_button_unchecked_rounded, + color: Theme.of(context).colorScheme.primary, + ) + ], + ), + ), + ), + ); + } +} \ No newline at end of file