diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 4786d6b..6bb805e 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -769,5 +769,7 @@ "editCustomRules": "Edit custom rules", "savingCustomRules": "Saving custom rules...", "customRulesUpdatedSuccessfully": "Custom rules updated successfully", - "customRulesNotUpdated": "Custom rules could not be updated" + "customRulesNotUpdated": "Custom rules could not be updated", + "reorder": "Reorder", + "showHide": "Show/hide" } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index d7ba7a0..afe0758 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -769,5 +769,7 @@ "editCustomRules": "Editar reglas personalizadas", "savingCustomRules": "Guardando reglas personalizadas...", "customRulesUpdatedSuccessfully": "Reglas personalizadas actualizadas correctamente", - "customRulesNotUpdated": "Las reglas personalizadas no pudieron ser actualizadas" + "customRulesNotUpdated": "Las reglas personalizadas no pudieron ser actualizadas", + "reorder": "Reordenar", + "showHide": "Mostrar/ocultar" } \ No newline at end of file diff --git a/lib/screens/settings/general_settings/general_settings.dart b/lib/screens/settings/general_settings/general_settings.dart index fb0560c..7439353 100644 --- a/lib/screens/settings/general_settings/general_settings.dart +++ b/lib/screens/settings/general_settings/general_settings.dart @@ -8,7 +8,7 @@ import 'package:provider/provider.dart'; import 'package:store_checker/store_checker.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:adguard_home_manager/screens/settings/general_settings/reorderable_top_items_home.dart'; +import 'package:adguard_home_manager/screens/settings/general_settings/top_items_list/top_items_list_settings.dart'; import 'package:adguard_home_manager/widgets/custom_list_tile.dart'; import 'package:adguard_home_manager/widgets/section_label.dart'; @@ -199,10 +199,10 @@ class _GeneralSettingsState extends State { title: AppLocalizations.of(context)!.topItemsOrder, subtitle: AppLocalizations.of(context)!.topItemsOrderDescription, onTap: () => widget.splitView == true - ? SplitView.of(context).push(const ReorderableTopItemsHome()) + ? SplitView.of(context).push(const TopItemsListSettings()) : Navigator.of(context).push( MaterialPageRoute( - builder: (context) => const ReorderableTopItemsHome() + builder: (context) => const TopItemsListSettings() ) ) ), diff --git a/lib/screens/settings/general_settings/reorderable_top_items_home.dart b/lib/screens/settings/general_settings/top_items_list/reorderable_top_items_home.dart similarity index 62% rename from lib/screens/settings/general_settings/reorderable_top_items_home.dart rename to lib/screens/settings/general_settings/top_items_list/reorderable_top_items_home.dart index 46e3a58..cdb39b7 100644 --- a/lib/screens/settings/general_settings/reorderable_top_items_home.dart +++ b/lib/screens/settings/general_settings/top_items_list/reorderable_top_items_home.dart @@ -1,6 +1,5 @@ import 'dart:io'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:flutter_reorderable_list/flutter_reorderable_list.dart' as reorderable_list; @@ -8,8 +7,6 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/widgets/custom_list_tile.dart'; -import 'package:adguard_home_manager/functions/desktop_mode.dart'; -import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/constants/enums.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; @@ -29,7 +26,14 @@ enum DraggingMode { } class ReorderableTopItemsHome extends StatefulWidget { - const ReorderableTopItemsHome({super.key}); + final List persistHomeTopItems; + final void Function(List value) setPersistHomeTopItems; + + const ReorderableTopItemsHome({ + super.key, + required this.persistHomeTopItems, + required this.setPersistHomeTopItems, + }); @override State createState() => _ReorderableTopItemsHomeState(); @@ -37,7 +41,6 @@ class ReorderableTopItemsHome extends StatefulWidget { class _ReorderableTopItemsHomeState extends State { List homeTopItemsList = []; - List persistHomeTopItemsList = []; List<_ItemData> renderItems = []; int _indexOfKey(Key key) { @@ -63,7 +66,7 @@ class _ReorderableTopItemsHomeState extends State { void _reorderDone(Key item) { renderItems[_indexOfKey(item)]; - setState(() => persistHomeTopItemsList = homeTopItemsList); + widget.setPersistHomeTopItems(homeTopItemsList); } List reorderEnumItems(int oldIndex, int newIndex) { @@ -75,10 +78,8 @@ class _ReorderableTopItemsHomeState extends State { @override void initState() { - final appConfigProvider = Provider.of(context, listen: false); - homeTopItemsList = appConfigProvider.homeTopItemsOrder; - persistHomeTopItemsList = appConfigProvider.homeTopItemsOrder; - renderItems = appConfigProvider.homeTopItemsOrder.asMap().entries.map( + homeTopItemsList = widget.persistHomeTopItems; + renderItems = widget.persistHomeTopItems.asMap().entries.map( (e) => _ItemData( key: ValueKey(e.key), title: e.value, @@ -136,99 +137,80 @@ class _ReorderableTopItemsHomeState extends State { } } - void saveSettings() async { - final result = await appConfigProvider.setHomeTopItemsOrder(homeTopItemsList); - if (!mounted) return; - if (result == true) { - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.settingsSaved, - color: Colors.green - ); - } - else { - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.settingsNotSaved, - color: Colors.red - ); - } - } + final draggingMode = Platform.isAndroid ? DraggingMode.android : DraggingMode.iOS; - return Scaffold( - appBar: AppBar( - title: Text(AppLocalizations.of(context)!.topItemsOrder), - surfaceTintColor: isDesktop(width) ? Colors.transparent : null, - actions: [ - IconButton( - onPressed: !listEquals(appConfigProvider.homeTopItemsOrder, persistHomeTopItemsList) - ? () => saveSettings() - : null, - icon: const Icon(Icons.save_rounded), - tooltip: AppLocalizations.of(context)!.save, - ), - const SizedBox(width: 8) - ], - ), - body: Column( - children: [ - Card( - margin: const EdgeInsets.all(16), - child: Padding( - padding: const EdgeInsets.all(16), - child: Row( - children: [ - Icon( - Icons.info_rounded, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - const SizedBox(width: 16), - Flexible( - child: Text(AppLocalizations.of(context)!.topItemsReorderInfo) - ) - ], - ), - ), - ), - Expanded( - child: reorderable_list.ReorderableList( - onReorder: _reorderCallback, - onReorderDone: _reorderDone, - child: ListView.builder( - itemBuilder: (context, index) => reorderable_list.ReorderableItem( - key: renderItems[index].key, - childBuilder: (context, state) { - if (draggingMode == DraggingMode.android) { - return reorderable_list.DelayedReorderableListener( - child: _Tile( - draggingMode: draggingMode, - isFirst: index == 0, - isLast: index == renderItems.length - 1, - state: state, - tileWidget: tile(renderItems[index].title), + return SafeArea( + top: false, + bottom: true, + child: Builder( + builder: (context) => CustomScrollView( + slivers: [ + SliverOverlapInjector( + handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), + ), + SliverList.list( + children: [ + Card( + margin: const EdgeInsets.all(16), + child: Padding( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + Icon( + Icons.info_rounded, + color: Theme.of(context).colorScheme.onSurfaceVariant, ), - ); - } - else { - return _Tile( - draggingMode: draggingMode, - isFirst: index == 0, - isLast: index == renderItems.length - 1, - state: state, - tileWidget: tile(renderItems[index].title), - ); - } - }, + const SizedBox(width: 16), + Flexible( + child: Text(AppLocalizations.of(context)!.topItemsReorderInfo) + ) + ], + ), + ), ), - itemCount: renderItems.length, - ) - ), - ), - ], + reorderable_list.ReorderableList( + onReorder: _reorderCallback, + onReorderDone: _reorderDone, + child: ListView.builder( + primary: false, + shrinkWrap: true, + padding: const EdgeInsets.only(top: 0), + itemBuilder: (context, index) => reorderable_list.ReorderableItem( + key: renderItems[index].key, + childBuilder: (context, state) { + if (draggingMode == DraggingMode.android) { + return reorderable_list.DelayedReorderableListener( + child: _Tile( + draggingMode: draggingMode, + isFirst: index == 0, + isLast: index == renderItems.length - 1, + state: state, + tileWidget: tile(renderItems[index].title), + ), + ); + } + else { + return _Tile( + draggingMode: draggingMode, + isFirst: index == 0, + isLast: index == renderItems.length - 1, + state: state, + tileWidget: tile(renderItems[index].title), + ); + } + }, + ), + itemCount: renderItems.length, + ) + ), + ] + ) + ], + ), ), ); } diff --git a/lib/screens/settings/general_settings/top_items_list/show_hide_top_items_list.dart b/lib/screens/settings/general_settings/top_items_list/show_hide_top_items_list.dart new file mode 100644 index 0000000..3088692 --- /dev/null +++ b/lib/screens/settings/general_settings/top_items_list/show_hide_top_items_list.dart @@ -0,0 +1,87 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +import 'package:adguard_home_manager/widgets/custom_switch_list_tile.dart'; +import 'package:adguard_home_manager/constants/enums.dart'; + +class ShowHideTopItemsList extends StatelessWidget { + final List enabledHomeTopItems; + final void Function(List) setEnabledHomeTopItems; + + const ShowHideTopItemsList({ + super.key, + required this.enabledHomeTopItems, + required this.setEnabledHomeTopItems, + }); + + @override + Widget build(BuildContext context) { + const padding = EdgeInsets.symmetric(horizontal: 16, vertical: 8); + + void updateValue(HomeTopItems value, bool newStatus) { + if (newStatus == true) { + setEnabledHomeTopItems([ + ...enabledHomeTopItems, + value + ]); + } + else { + setEnabledHomeTopItems(enabledHomeTopItems.where((e) => e != value).toList()); + } + } + + return SafeArea( + top: false, + bottom: true, + child: Builder( + builder: (context) => CustomScrollView( + slivers: [ + SliverOverlapInjector( + handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), + ), + SliverList.list( + children: [ + const SizedBox(height: 8), + CustomSwitchListTile( + value: enabledHomeTopItems.contains(HomeTopItems.queriedDomains), + onChanged: (v) => updateValue(HomeTopItems.queriedDomains, v), + title: AppLocalizations.of(context)!.topQueriedDomains, + leadingIcon: Icons.install_desktop_outlined, + padding: padding, + ), + CustomSwitchListTile( + value: enabledHomeTopItems.contains(HomeTopItems.blockedDomains), + onChanged: (v) => updateValue(HomeTopItems.blockedDomains, v), + title: AppLocalizations.of(context)!.topBlockedDomains, + leadingIcon: Icons.block_rounded, + padding: padding, + ), + CustomSwitchListTile( + value: enabledHomeTopItems.contains(HomeTopItems.recurrentClients), + onChanged: (v) => updateValue(HomeTopItems.recurrentClients, v), + title: AppLocalizations.of(context)!.topClients, + leadingIcon: Icons.smartphone_rounded, + padding: padding, + ), + CustomSwitchListTile( + value: enabledHomeTopItems.contains(HomeTopItems.topUpstreams), + onChanged: (v) => updateValue(HomeTopItems.topUpstreams, v), + title: AppLocalizations.of(context)!.topUpstreams, + leadingIcon: Icons.upload_file_rounded, + padding: padding, + ), + CustomSwitchListTile( + value: enabledHomeTopItems.contains(HomeTopItems.avgUpstreamResponseTime), + onChanged: (v) => updateValue(HomeTopItems.avgUpstreamResponseTime, v), + title: AppLocalizations.of(context)!.averageUpstreamResponseTime, + leadingIcon: Icons.timer_rounded, + padding: padding, + ), + ] + ) + ], + ), + ) + ); + } +} \ No newline at end of file diff --git a/lib/screens/settings/general_settings/top_items_list/top_items_list_settings.dart b/lib/screens/settings/general_settings/top_items_list/top_items_list_settings.dart new file mode 100644 index 0000000..9e9c836 --- /dev/null +++ b/lib/screens/settings/general_settings/top_items_list/top_items_list_settings.dart @@ -0,0 +1,131 @@ +import 'package:flutter/foundation.dart'; +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/settings/general_settings/top_items_list/show_hide_top_items_list.dart'; +import 'package:adguard_home_manager/screens/settings/general_settings/top_items_list/reorderable_top_items_home.dart'; + +import 'package:adguard_home_manager/constants/enums.dart'; +import 'package:adguard_home_manager/functions/desktop_mode.dart'; +import 'package:adguard_home_manager/functions/snackbar.dart'; +import 'package:adguard_home_manager/providers/app_config_provider.dart'; + +class TopItemsListSettings extends StatefulWidget { + const TopItemsListSettings({super.key}); + + @override + State createState() => _TopItemsListSettingsState(); +} + +class _TopItemsListSettingsState extends State with TickerProviderStateMixin { + late TabController _tabController; + + List persistHomeTopItemsList = []; + + @override + void initState() { + final appConfigProvider = Provider.of(context, listen: false); + persistHomeTopItemsList = appConfigProvider.homeTopItemsOrder; + + super.initState(); + + _tabController = TabController(length: 2, vsync: this); + } + + @override + Widget build(BuildContext context) { + final appConfigProvider = Provider.of(context); + + final width = MediaQuery.of(context).size.width; + + void saveSettings() async { + final result = await appConfigProvider.setHomeTopItemsOrder(persistHomeTopItemsList); + if (!context.mounted) return; + if (result == true) { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.settingsSaved, + color: Colors.green + ); + } + else { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.settingsNotSaved, + color: Colors.red + ); + } + } + + return Scaffold( + body: DefaultTabController( + length: 2, + child: NestedScrollView( + headerSliverBuilder: (context, innerBoxIsScrolled) => [ + SliverOverlapAbsorber( + handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), + sliver: SliverAppBar( + pinned: true, + floating: true, + centerTitle: false, + forceElevated: innerBoxIsScrolled, + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, + title: Text(AppLocalizations.of(context)!.topItemsOrder), + actions: [ + IconButton( + onPressed: !listEquals(appConfigProvider.homeTopItemsOrder, persistHomeTopItemsList) + ? () => saveSettings() + : null, + icon: const Icon(Icons.save_rounded), + tooltip: AppLocalizations.of(context)!.save, + ), + const SizedBox(width: 8) + ], + bottom: TabBar( + controller: _tabController, + unselectedLabelColor: Theme.of(context).colorScheme.onSurfaceVariant, + tabs: [ + Tab( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.reorder_rounded), + const SizedBox(width: 8), + Text(AppLocalizations.of(context)!.reorder) + ], + ), + ), + Tab( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.remove_red_eye_rounded), + const SizedBox(width: 8), + Text(AppLocalizations.of(context)!.showHide) + ], + ), + ), + ] + ) + ), + ) + ], + body: TabBarView( + controller: _tabController, + children: [ + ReorderableTopItemsHome( + persistHomeTopItems: persistHomeTopItemsList, + setPersistHomeTopItems: (v) => setState(() => persistHomeTopItemsList = v), + ), + ShowHideTopItemsList( + enabledHomeTopItems: persistHomeTopItemsList, + setEnabledHomeTopItems: (v) => setState(() => persistHomeTopItemsList = v), + ) + ] + ) + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/widgets/custom_switch_list_tile.dart b/lib/widgets/custom_switch_list_tile.dart index 84db718..84e65c5 100644 --- a/lib/widgets/custom_switch_list_tile.dart +++ b/lib/widgets/custom_switch_list_tile.dart @@ -7,16 +7,18 @@ class CustomSwitchListTile extends StatelessWidget { final String? subtitle; final bool? disabled; final EdgeInsets? padding; + final IconData? leadingIcon; const CustomSwitchListTile({ - Key? key, + super.key, required this.value, required this.onChanged, required this.title, this.disabled, this.subtitle, - this.padding - }) : super(key: key); + this.padding, + this.leadingIcon, + }); @override Widget build(BuildContext context) { @@ -33,6 +35,13 @@ class CustomSwitchListTile extends StatelessWidget { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ + if (leadingIcon != null) ...[ + Icon( + leadingIcon, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + const SizedBox(width: 16), + ], Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start,