From 621171c5b1940bc8e05b58370d59799f078eb1c9 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sat, 28 Oct 2023 22:38:49 +0200 Subject: [PATCH 1/6] Implemented go router --- lib/base.dart | 2 +- lib/config/app_screens.dart | 7 - lib/config/sizes.dart | 1 + lib/constants/routes_names.dart | 19 ++ lib/functions/desktop_mode.dart | 5 + lib/l10n/app_en.arb | 4 +- lib/l10n/app_es.arb | 4 +- lib/main.dart | 7 +- lib/models/app_screen.dart | 2 - lib/routes/router.dart | 20 ++ lib/routes/router_globals.dart | 10 + lib/routes/routes.dart | 113 ++++++++ lib/screens/clients/added_list.dart | 3 - .../clients/client/client_placeholder.dart | 12 + .../clients/client/logs_list_client.dart | 44 ++- lib/screens/clients/clients.dart | 263 ++++++++---------- lib/screens/clients/clients_desktop_view.dart | 19 +- lib/screens/clients/clients_list.dart | 3 - lib/screens/home/appbar.dart | 4 + lib/widgets/bottom_nav_bar.dart | 30 +- lib/widgets/layout.dart | 250 +++++++++++++++++ pubspec.lock | 24 ++ pubspec.yaml | 2 + 23 files changed, 614 insertions(+), 234 deletions(-) create mode 100644 lib/config/sizes.dart create mode 100644 lib/constants/routes_names.dart create mode 100644 lib/functions/desktop_mode.dart create mode 100644 lib/routes/router.dart create mode 100644 lib/routes/router_globals.dart create mode 100644 lib/routes/routes.dart create mode 100644 lib/screens/clients/client/client_placeholder.dart create mode 100644 lib/widgets/layout.dart diff --git a/lib/base.dart b/lib/base.dart index b2b593b..d57dd72 100644 --- a/lib/base.dart +++ b/lib/base.dart @@ -101,7 +101,7 @@ class _BaseState extends State with WidgetsBindingObserver { child: child, ) ), - child: screens[appConfigProvider.selectedScreen].body, + child: SizedBox() ), ), ], diff --git a/lib/config/app_screens.dart b/lib/config/app_screens.dart index 1835dc4..91d33f0 100644 --- a/lib/config/app_screens.dart +++ b/lib/config/app_screens.dart @@ -13,12 +13,10 @@ List screensSelectServer = [ const AppScreen( name: "connect", icon: Icons.link_rounded, - body: Connect(), ), const AppScreen( name: "settings", icon: Icons.settings_rounded, - body: Settings() ) ]; @@ -26,26 +24,21 @@ List screensServerConnected = [ const AppScreen( name: "home", icon: Icons.home_rounded, - body: Home(), ), const AppScreen( name: "clients", icon: Icons.devices, - body: Clients() ), const AppScreen( name: "logs", icon: Icons.list_alt_rounded, - body: Logs(), ), const AppScreen( name: "filters", icon: Icons.shield_rounded, - body: Filters(), ), const AppScreen( name: "settings", icon: Icons.settings_rounded, - body: Settings() ) ]; \ No newline at end of file diff --git a/lib/config/sizes.dart b/lib/config/sizes.dart new file mode 100644 index 0000000..0fc2640 --- /dev/null +++ b/lib/config/sizes.dart @@ -0,0 +1 @@ +const double desktopBreakpoint = 1000; \ No newline at end of file diff --git a/lib/constants/routes_names.dart b/lib/constants/routes_names.dart new file mode 100644 index 0000000..dae8723 --- /dev/null +++ b/lib/constants/routes_names.dart @@ -0,0 +1,19 @@ +class RoutesNames { + static const String connect = "/connect"; + + static const String home = "/home"; + static const String queriedDomains = "/home/queried-domains"; + static const String blockedDomains = "/home/blocked-domains"; + static const String recurrentClients = "/home/recurrent-clients"; + + static const String clients = "/clients"; + static const String clientsList = "/clients/list"; + static const String clientPlaceholder = "/clients/list/placeholder"; + static const String client = "/clients/list:id"; + + static const String logs = "/logs"; + + static const String filters = "/filters"; + + static const String settings = "/settings"; +} \ No newline at end of file diff --git a/lib/functions/desktop_mode.dart b/lib/functions/desktop_mode.dart new file mode 100644 index 0000000..4586068 --- /dev/null +++ b/lib/functions/desktop_mode.dart @@ -0,0 +1,5 @@ +import 'package:adguard_home_manager/config/sizes.dart'; + +bool isDesktop(double width) { + return width > desktopBreakpoint; +} \ No newline at end of file diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index ee0533d..d4962e3 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -667,5 +667,7 @@ "showChart": "Show chart", "hideChart": "Hide chart", "showTopItemsChart": "Show top items chart", - "showTopItemsChartDescription": "Shows by default the ring chart on the top items sections. Only affects to the mobile view." + "showTopItemsChartDescription": "Shows by default the ring chart on the top items sections. Only affects to the mobile view.", + "openMenu": "Open menu", + "closeMenu": "Close menu" } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 4276b7b..2f202a8 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -667,5 +667,7 @@ "showChart": "Mostrar gráfico", "hideChart": "Ocultar gráfico", "showTopItemsChart": "Mostrar gráfico en top de items", - "showTopItemsChartDescription": "Muestra por defecto el gráfico de anillo en las secciones de top de items. Sólo afecta a la vista móvil." + "showTopItemsChartDescription": "Muestra por defecto el gráfico de anillo en las secciones de top de items. Sólo afecta a la vista móvil.", + "openMenu": "Abrir menú", + "closeMenu": "Cerrar menú" } \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 98bb878..fafb46e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -15,8 +15,7 @@ import 'package:window_size/window_size.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:adguard_home_manager/base.dart'; - +import 'package:adguard_home_manager/routes/router.dart'; import 'package:adguard_home_manager/providers/logs_provider.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/providers/clients_provider.dart'; @@ -204,7 +203,7 @@ class _MainState extends State
{ final appConfigProvider = Provider.of(context); return DynamicColorBuilder( - builder: (lightDynamic, darkDynamic) => MaterialApp( + builder: (lightDynamic, darkDynamic) => MaterialApp.router( title: 'AdGuard Home Manager', theme: appConfigProvider.androidDeviceInfo != null && appConfigProvider.androidDeviceInfo!.version.sdkInt >= 31 ? appConfigProvider.useDynamicColor == true @@ -243,7 +242,7 @@ class _MainState extends State
{ child: child!, ); }, - home: const Base(), + routerConfig: goRouter, ), ); } diff --git a/lib/models/app_screen.dart b/lib/models/app_screen.dart index bce5cc3..1e377eb 100644 --- a/lib/models/app_screen.dart +++ b/lib/models/app_screen.dart @@ -4,14 +4,12 @@ class AppScreen { final String name; final IconData icon; final PreferredSizeWidget? appBar; - final Widget body; final Widget? fab; const AppScreen({ required this.name, required this.icon, this.appBar, - required this.body, this.fab }); } \ No newline at end of file diff --git a/lib/routes/router.dart b/lib/routes/router.dart new file mode 100644 index 0000000..9b1cfba --- /dev/null +++ b/lib/routes/router.dart @@ -0,0 +1,20 @@ +import 'package:go_router/go_router.dart'; +import 'package:provider/provider.dart'; + +import 'package:adguard_home_manager/routes/router_globals.dart'; +import 'package:adguard_home_manager/routes/routes.dart'; +import 'package:adguard_home_manager/providers/servers_provider.dart'; +import 'package:adguard_home_manager/constants/routes_names.dart'; + +final goRouter = GoRouter( + navigatorKey: rootNavigatorKey, + redirect: (context, state) { + final serversProvider = Provider.of(context, listen: false); + if (serversProvider.selectedServer == null) { + return RoutesNames.connect; + } + return null; + }, + initialLocation: RoutesNames.home, + routes: routes, +); \ No newline at end of file diff --git a/lib/routes/router_globals.dart b/lib/routes/router_globals.dart new file mode 100644 index 0000000..739c50c --- /dev/null +++ b/lib/routes/router_globals.dart @@ -0,0 +1,10 @@ +import 'package:flutter/widgets.dart'; + +final GlobalKey rootNavigatorKey = GlobalKey(); +final GlobalKey connectNavigatorKey = GlobalKey(); +final GlobalKey homeNavigatorKey = GlobalKey(); +final GlobalKey clientsNavigatorKey = GlobalKey(); +final GlobalKey clientsListNavigatorKey = GlobalKey(); +final GlobalKey logsNavigatorKey = GlobalKey(); +final GlobalKey filtersNavigatorKey = GlobalKey(); +final GlobalKey settingsNavigatorKey = GlobalKey(); \ No newline at end of file diff --git a/lib/routes/routes.dart b/lib/routes/routes.dart new file mode 100644 index 0000000..04bd218 --- /dev/null +++ b/lib/routes/routes.dart @@ -0,0 +1,113 @@ +import 'package:go_router/go_router.dart'; + +import 'package:adguard_home_manager/screens/home/home.dart'; +import 'package:adguard_home_manager/screens/clients/clients.dart'; +import 'package:adguard_home_manager/screens/connect/connect.dart'; +import 'package:adguard_home_manager/screens/filters/filters.dart'; +import 'package:adguard_home_manager/screens/settings/settings.dart'; +import 'package:adguard_home_manager/screens/clients/client/client_placeholder.dart'; +import 'package:adguard_home_manager/screens/clients/client/logs_list_client.dart'; +import 'package:adguard_home_manager/screens/logs/logs.dart'; +import 'package:adguard_home_manager/widgets/layout.dart'; + +import 'package:adguard_home_manager/routes/router_globals.dart'; +import 'package:adguard_home_manager/constants/routes_names.dart'; + +final List routes = [ + GoRoute( + path: "/", + redirect: (context, state) => RoutesNames.home, + ), + StatefulShellRoute.indexedStack( + builder: (context, state, navigationShell) => Layout( + navigationShell: navigationShell + ), + branches: [ + StatefulShellBranch( + navigatorKey: homeNavigatorKey, + routes: [ + GoRoute( + parentNavigatorKey: homeNavigatorKey, + path: RoutesNames.home, + builder: (context, state) => const Home(), + ), + GoRoute( + parentNavigatorKey: homeNavigatorKey, + path: RoutesNames.queriedDomains, + builder: (context, state) => const Home(), + ), + GoRoute( + parentNavigatorKey: homeNavigatorKey, + path: RoutesNames.blockedDomains, + builder: (context, state) => const Home(), + ), + GoRoute( + parentNavigatorKey: homeNavigatorKey, + path: RoutesNames.recurrentClients, + builder: (context, state) => const Home(), + ), + ] + ), + StatefulShellBranch( + navigatorKey: clientsNavigatorKey, + routes: [ + ShellRoute( + parentNavigatorKey: clientsNavigatorKey, + navigatorKey: clientsListNavigatorKey, + builder: (context, state, child) => Clients(child: child), + routes: [ + GoRoute( + path: RoutesNames.clientPlaceholder, + parentNavigatorKey: clientsListNavigatorKey, + builder: (context, state) => const ClientPlaceholder(), + ), + GoRoute( + path: RoutesNames.client, + parentNavigatorKey: clientsListNavigatorKey, + builder: (context, state) => LogsListClient( + id: (state.extra as Map?)?['id'] + ) + ) + ] + ) + ] + ), + StatefulShellBranch( + navigatorKey: logsNavigatorKey, + routes: [ + GoRoute( + path: RoutesNames.logs, + builder: (context, state) => const Logs(), + ) + ] + ), + StatefulShellBranch( + navigatorKey: filtersNavigatorKey, + routes: [ + GoRoute( + path: RoutesNames.filters, + builder: (context, state) => const Filters(), + ) + ] + ), + StatefulShellBranch( + navigatorKey: settingsNavigatorKey, + routes: [ + GoRoute( + path: RoutesNames.settings, + builder: (context, state) => const Settings(), + ) + ] + ), + StatefulShellBranch( + navigatorKey: connectNavigatorKey, + routes: [ + GoRoute( + path: RoutesNames.connect, + builder: (context, state) => const Connect(), + ) + ] + ), + ] + ) +]; \ No newline at end of file diff --git a/lib/screens/clients/added_list.dart b/lib/screens/clients/added_list.dart index 24b69ae..eea9dd6 100644 --- a/lib/screens/clients/added_list.dart +++ b/lib/screens/clients/added_list.dart @@ -1,7 +1,5 @@ // ignore_for_file: use_build_context_synchronously -import 'dart:io'; - import 'package:flutter/material.dart'; import 'package:animations/animations.dart'; import 'package:flutter/rendering.dart'; @@ -157,7 +155,6 @@ class _AddedListState extends State { } return CustomTabContentList( - noSliver: !(Platform.isAndroid || Platform.isIOS), listPadding: widget.splitView == true ? const EdgeInsets.only(top: 8) : null, diff --git a/lib/screens/clients/client/client_placeholder.dart b/lib/screens/clients/client/client_placeholder.dart new file mode 100644 index 0000000..cf4f485 --- /dev/null +++ b/lib/screens/clients/client/client_placeholder.dart @@ -0,0 +1,12 @@ +import 'package:flutter/material.dart'; + +class ClientPlaceholder extends StatelessWidget { + const ClientPlaceholder({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Center( + child: Text("Select a client"), + ); + } +} \ No newline at end of file diff --git a/lib/screens/clients/client/logs_list_client.dart b/lib/screens/clients/client/logs_list_client.dart index 0ddc99a..f35de0b 100644 --- a/lib/screens/clients/client/logs_list_client.dart +++ b/lib/screens/clients/client/logs_list_client.dart @@ -1,7 +1,9 @@ import 'dart:io'; +import 'package:adguard_home_manager/constants/enums.dart'; import 'package:flutter/material.dart'; import 'package:async/async.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -12,18 +14,12 @@ import 'package:adguard_home_manager/models/logs.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/providers/servers_provider.dart'; -class LogsListClient extends StatefulWidget { - final String ip; - final String? name; - final ServersProvider serversProvider; - final AppConfigProvider appConfigProvider; +class LogsListClient extends StatefulHookWidget { + final String id; const LogsListClient({ Key? key, - required this.ip, - this.name, - required this.serversProvider, - required this.appConfigProvider + required this.id, }) : super(key: key); @override @@ -38,11 +34,9 @@ class _LogsListClientState extends State { int logsQuantity = 100; int offset = 0; - int loadStatus = 0; + LoadStatus loadStatus = LoadStatus.loading; LogsData? logsData; - String previousIp = ""; - bool showDivider = true; CancelableOperation? cancelableRequest; @@ -67,7 +61,7 @@ class _LogsListClientState extends State { serversProvider.apiClient!.getLogs( count: logsQuantity, offset: offst, - search: '"${widget.ip}"' + search: '"${widget.id}"' ) ); @@ -90,11 +84,11 @@ class _LogsListClientState extends State { LogsData newLogsData = result['data']; setState(() => logsData = newLogsData); } - setState(() => loadStatus = 1); + setState(() => loadStatus = LoadStatus.loaded); } else { - setState(() => loadStatus = 2); - widget.appConfigProvider.addLog(result['log']); + setState(() => loadStatus = LoadStatus.error); + Provider.of(context, listen: false).addLog(result['log']); } } } @@ -116,8 +110,6 @@ class _LogsListClientState extends State { @override void initState() { scrollController = ScrollController()..addListener(scrollListener); - fetchLogs(inOffset: 0); - setState(() => previousIp = widget.ip); super.initState(); } @@ -125,15 +117,15 @@ class _LogsListClientState extends State { Widget build(BuildContext context) { final width = MediaQuery.of(context).size.width; - if (widget.ip != previousIp) { - setState(() => loadStatus = 0); + useEffect(() { + setState(() => loadStatus = LoadStatus.loading); fetchLogs(inOffset: 0); - setState(() => previousIp = widget.ip); - } + return null; + }, [widget.id]); Widget status() { switch (loadStatus) { - case 0: + case LoadStatus.loading: return SizedBox( width: double.maxFinite, child: Column( @@ -154,7 +146,7 @@ class _LogsListClientState extends State { ), ); - case 1: + case LoadStatus.loaded: if (logsData!.data.isNotEmpty) { return RefreshIndicator( onRefresh: fetchLogs, @@ -217,7 +209,7 @@ class _LogsListClientState extends State { ); } - case 2: + case LoadStatus.error: return SizedBox( width: double.maxFinite, child: Column( @@ -249,7 +241,7 @@ class _LogsListClientState extends State { return Scaffold( appBar: AppBar( - title: Text(widget.name != null && widget.name != '' ? widget.name! : widget.ip), + title: Text(widget.id), centerTitle: true, actions: [ if (!(Platform.isAndroid || Platform.isIOS)) ...[ diff --git a/lib/screens/clients/clients.dart b/lib/screens/clients/clients.dart index 4762e5b..45ccbd4 100644 --- a/lib/screens/clients/clients.dart +++ b/lib/screens/clients/clients.dart @@ -1,24 +1,25 @@ -import 'dart:io'; - import 'package:flutter/material.dart'; -import 'package:flutter_split_view/flutter_split_view.dart'; +import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/screens/clients/clients_list.dart'; -import 'package:adguard_home_manager/screens/clients/search_clients.dart'; -import 'package:adguard_home_manager/screens/clients/client/logs_list_client.dart'; -import 'package:adguard_home_manager/screens/clients/clients_desktop_view.dart'; import 'package:adguard_home_manager/screens/clients/added_list.dart'; +import 'package:adguard_home_manager/constants/routes_names.dart'; +import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:adguard_home_manager/providers/clients_provider.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/constants/enums.dart'; import 'package:adguard_home_manager/models/clients.dart'; -import 'package:adguard_home_manager/providers/servers_provider.dart'; class Clients extends StatefulWidget { - const Clients({Key? key}) : super(key: key); + final Widget child; + + const Clients({ + Key? key, + required this.child, + }) : super(key: key); @override State createState() => _ClientsState(); @@ -53,9 +54,7 @@ class _ClientsState extends State with TickerProviderStateMixin { @override Widget build(BuildContext context) { - final serversProvider = Provider.of(context); final clientsProvider = Provider.of(context); - final appConfigProvider = Provider.of(context); final width = MediaQuery.of(context).size.width; @@ -96,163 +95,119 @@ class _ClientsState extends State with TickerProviderStateMixin { scrollController: scrollController, data: clientsProvider.loadStatus == LoadStatus.loaded ? clientsProvider.filteredActiveClients : [], - onClientSelected: (client) => Navigator.push(context, MaterialPageRoute( - builder: (context) => LogsListClient( - ip: client.ip, - serversProvider: serversProvider, - appConfigProvider: appConfigProvider - ) - )), - splitView: false, - sliver: sliver, + onClientSelected: (client) => context.go( + RoutesNames.client, + extra: { + "id": client.name != null && client.name != "" + ? client.name + : client.ip + } + ), + splitView: isDesktop(width), ), AddedList( scrollController: scrollController, data: clientsProvider.loadStatus == LoadStatus.loaded ? clientsProvider.filteredAddedClients : [], - onClientSelected: (client) => Navigator.push(context, MaterialPageRoute( - builder: (context) => LogsListClient( - ip: client.ids[0], - serversProvider: serversProvider, - appConfigProvider: appConfigProvider - ) - )), - splitView: false, + onClientSelected: (client) => context.go( + RoutesNames.client, + extra: { "id": client.name } + ), + splitView: isDesktop(width), ), ] ); } - if (width > 900) { - return SplitView.material( - hideDivider: true, - flexWidth: const FlexWidth(mainViewFlexWidth: 1, secondaryViewFlexWidth: 2), - placeholder: Center( - child: Padding( - padding: const EdgeInsets.all(24), - child: Text( - AppLocalizations.of(context)!.selectClientLeftColumn, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 24, - color: Theme.of(context).colorScheme.onSurfaceVariant - ), + return Row( + children: [ + SizedBox( + width: isDesktop(width) ? 300 : width, + height: double.maxFinite, + child: Material( + child: DefaultTabController( + length: 2, + child: NestedScrollView( + controller: scrollController, + headerSliverBuilder: ((context, innerBoxIsScrolled) { + return [ + SliverOverlapAbsorber( + handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), + sliver: SliverAppBar( + title: searchMode == true + ? Row( + children: [ + IconButton( + onPressed: () { + setState(() { + searchMode = false; + searchController.text = ""; + clientsProvider.setSearchTermClients(null); + }); + }, + icon: const Icon(Icons.arrow_back_rounded), + tooltip: AppLocalizations.of(context)!.exitSearch, + ), + const SizedBox(width: 16), + Expanded( + child: TextField( + controller: searchController, + onChanged: (value) => clientsProvider.setSearchTermClients(value), + decoration: InputDecoration( + suffixIcon: IconButton( + onPressed: () { + setState(() { + searchController.text = ""; + clientsProvider.setSearchTermClients(null); + }); + }, + icon: const Icon(Icons.clear_rounded) + ), + hintText: AppLocalizations.of(context)!.search, + hintStyle: const TextStyle( + fontWeight: FontWeight.normal, + fontSize: 18 + ), + border: InputBorder.none, + ), + style: const TextStyle( + fontWeight: FontWeight.normal, + fontSize: 18 + ), + autofocus: true, + ), + ) + ], + ) + : Text(AppLocalizations.of(context)!.clients), + pinned: true, + floating: true, + centerTitle: false, + forceElevated: innerBoxIsScrolled, + actions: [ + if (clientsProvider.loadStatus == LoadStatus.loaded && searchMode == false) ...[ + IconButton( + onPressed: () => setState(() => searchMode = true), + icon: const Icon(Icons.search), + tooltip: AppLocalizations.of(context)!.searchClients, + ), + const SizedBox(width: 10), + ] + ], + bottom: tabBar() + ), + ) + ]; + }), + body: tabBarView(true) + ) ), ), ), - child: ClientsDesktopView( - serversProvider: serversProvider, - appConfigProvider: appConfigProvider, + if (isDesktop(width) == true) Expanded( + child: widget.child, ) - ); - } - else { - if (!(Platform.isAndroid || Platform.isIOS)) { - return DefaultTabController( - length: 2, - child: Scaffold( - appBar: AppBar( - title: Text(AppLocalizations.of(context)!.clients), - centerTitle: false, - actions: [ - if (clientsProvider.loadStatus == LoadStatus.loaded) ...[ - IconButton( - onPressed: () => { - Navigator.push(context, MaterialPageRoute( - builder: (context) => const SearchClients() - )) - }, - icon: const Icon(Icons.search), - tooltip: AppLocalizations.of(context)!.searchClients, - ), - const SizedBox(width: 10), - ] - ], - bottom: tabBar() - ), - body: tabBarView(false), - ), - ); - } - else { - return DefaultTabController( - length: 2, - child: NestedScrollView( - controller: scrollController, - headerSliverBuilder: ((context, innerBoxIsScrolled) { - return [ - SliverOverlapAbsorber( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - sliver: SliverAppBar( - title: searchMode == true - ? Row( - children: [ - IconButton( - onPressed: () { - setState(() { - searchMode = false; - searchController.text = ""; - clientsProvider.setSearchTermClients(null); - }); - }, - icon: const Icon(Icons.arrow_back_rounded), - tooltip: AppLocalizations.of(context)!.exitSearch, - ), - const SizedBox(width: 16), - Expanded( - child: TextField( - controller: searchController, - onChanged: (value) => clientsProvider.setSearchTermClients(value), - decoration: InputDecoration( - suffixIcon: IconButton( - onPressed: () { - setState(() { - searchController.text = ""; - clientsProvider.setSearchTermClients(null); - }); - }, - icon: const Icon(Icons.clear_rounded) - ), - hintText: AppLocalizations.of(context)!.search, - hintStyle: const TextStyle( - fontWeight: FontWeight.normal, - fontSize: 18 - ), - border: InputBorder.none, - ), - style: const TextStyle( - fontWeight: FontWeight.normal, - fontSize: 18 - ), - autofocus: true, - ), - ) - ], - ) - : Text(AppLocalizations.of(context)!.clients), - pinned: true, - floating: true, - centerTitle: false, - forceElevated: innerBoxIsScrolled, - actions: [ - if (clientsProvider.loadStatus == LoadStatus.loaded && searchMode == false) ...[ - IconButton( - onPressed: () => setState(() => searchMode = true), - icon: const Icon(Icons.search), - tooltip: AppLocalizations.of(context)!.searchClients, - ), - const SizedBox(width: 10), - ] - ], - bottom: tabBar() - ), - ) - ]; - }), - body: tabBarView(true) - ) - ); - } - } + ], + ); } } \ No newline at end of file diff --git a/lib/screens/clients/clients_desktop_view.dart b/lib/screens/clients/clients_desktop_view.dart index 9e261ff..7356e15 100644 --- a/lib/screens/clients/clients_desktop_view.dart +++ b/lib/screens/clients/clients_desktop_view.dart @@ -97,18 +97,10 @@ class _ClientsDesktopViewState extends State with TickerPro onClientSelected: (client) => setState(() { selectedAddedClient = null; selectedActiveClient = client; - SplitView.of(context).setSecondary( - LogsListClient( - ip: client.ip, - name: client.name, - serversProvider: serversProvider, - appConfigProvider: appConfigProvider, - ) - ); + }), selectedClient: selectedActiveClient, splitView: true, - sliver: sliver, ), AddedList( scrollController: scrollController, @@ -117,14 +109,7 @@ class _ClientsDesktopViewState extends State with TickerPro onClientSelected: (client) => setState(() { selectedActiveClient = null; selectedAddedClient = client; - SplitView.of(context).setSecondary( - LogsListClient( - ip: client.ids[0], - name: client.name, - serversProvider: serversProvider, - appConfigProvider: appConfigProvider, - ) - ); + }), selectedClient: selectedAddedClient, splitView: true, diff --git a/lib/screens/clients/clients_list.dart b/lib/screens/clients/clients_list.dart index f3ee547..d0ecd33 100644 --- a/lib/screens/clients/clients_list.dart +++ b/lib/screens/clients/clients_list.dart @@ -15,7 +15,6 @@ class ClientsList extends StatelessWidget { final void Function(AutoClient) onClientSelected; final AutoClient? selectedClient; final bool splitView; - final bool sliver; const ClientsList({ Key? key, @@ -24,7 +23,6 @@ class ClientsList extends StatelessWidget { required this.onClientSelected, this.selectedClient, required this.splitView, - required this.sliver }) : super(key: key); @override @@ -35,7 +33,6 @@ class ClientsList extends StatelessWidget { listPadding: splitView == true ? const EdgeInsets.only(top: 8) : null, - noSliver: !sliver, loadingGenerator: () => SizedBox( width: double.maxFinite, height: MediaQuery.of(context).size.height-171, diff --git a/lib/screens/home/appbar.dart b/lib/screens/home/appbar.dart index 8660f54..34893b6 100644 --- a/lib/screens/home/appbar.dart +++ b/lib/screens/home/appbar.dart @@ -1,3 +1,4 @@ +import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -25,6 +26,8 @@ class HomeAppBar extends StatelessWidget { final statusProvider = Provider.of(context); final appConfigProvider = Provider.of(context); + final width = MediaQuery.of(context).size.width; + final Server? server = serversProvider.selectedServer; void navigateServers() { @@ -40,6 +43,7 @@ class HomeAppBar extends StatelessWidget { floating: true, centerTitle: false, forceElevated: innerBoxScrolled, + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, leading: Stack( children: [ Center( diff --git a/lib/widgets/bottom_nav_bar.dart b/lib/widgets/bottom_nav_bar.dart index 366fe2c..0f16dcd 100644 --- a/lib/widgets/bottom_nav_bar.dart +++ b/lib/widgets/bottom_nav_bar.dart @@ -84,21 +84,21 @@ class BottomNavBar extends StatelessWidget { ), label: translatedName(screen.name) )).toList(), - onDestinationSelected: (value) { - // Reset clients tab to 0 when changing screen - if (value != 1) { - appConfigProvider.setSelectedClientsTab(0); - } - // Reset logs filters when changing screen - if (value != 2) { - logsProvider.resetFilters(); - } - // Reset settings selected screen - if (value != screens.length-1) { - appConfigProvider.setSelectedSettingsScreen(screen: null); - } - appConfigProvider.setSelectedScreen(value); - }, + // onDestinationSelected: (value) { + // // Reset clients tab to 0 when changing screen + // if (value != 1) { + // appConfigProvider.setSelectedClientsTab(0); + // } + // // Reset logs filters when changing screen + // if (value != 2) { + // logsProvider.resetFilters(); + // } + // // Reset settings selected screen + // if (value != screens.length-1) { + // appConfigProvider.setSelectedSettingsScreen(screen: null); + // } + // appConfigProvider.setSelectedScreen(value); + // }, ); } } \ No newline at end of file diff --git a/lib/widgets/layout.dart b/lib/widgets/layout.dart new file mode 100644 index 0000000..3513bb9 --- /dev/null +++ b/lib/widgets/layout.dart @@ -0,0 +1,250 @@ +import 'package:adguard_home_manager/widgets/bottom_nav_bar.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:go_router/go_router.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +import 'package:adguard_home_manager/config/app_screens.dart'; +import 'package:adguard_home_manager/config/sizes.dart'; +import 'package:adguard_home_manager/providers/servers_provider.dart'; + +class Layout extends StatefulWidget { + final StatefulNavigationShell navigationShell; + + const Layout({ + Key? key, + required this.navigationShell, + }) : super(key: key); + + @override + State createState() => _LayoutState(); +} + +class _LayoutState extends State { + bool _drawerExpanded = true; + + void _goBranch(int index) { + widget.navigationShell.goBranch( + index, + initialLocation: index == widget.navigationShell.currentIndex, + ); + } + + @override + Widget build(BuildContext context) { + final width = MediaQuery.of(context).size.width; + + final serversProvider = Provider.of(context); + + String translatedName(String key) { + switch (key) { + case 'home': + return AppLocalizations.of(context)!.home; + + case 'settings': + return AppLocalizations.of(context)!.settings; + + case 'connect': + return AppLocalizations.of(context)!.connect; + + case 'clients': + return AppLocalizations.of(context)!.clients; + + case 'logs': + return AppLocalizations.of(context)!.logs; + + case 'filters': + return AppLocalizations.of(context)!.filters; + + default: + return ''; + } + } + + if (width > desktopBreakpoint) { + return Material( + child: Row( + children: [ + AnimatedContainer( + duration: const Duration(milliseconds: 250), + curve: Curves.ease, + width: _drawerExpanded ? 250 : 90, + child: ListView( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 16 + ), + child: IconButton( + onPressed: () => setState(() => _drawerExpanded = !_drawerExpanded), + icon: const Icon(Icons.menu_open_rounded), + tooltip: _drawerExpanded == true + ? AppLocalizations.of(context)!.closeMenu + : AppLocalizations.of(context)!.openMenu, + ), + ), + ], + ), + if (serversProvider.selectedServer != null) + ...screensServerConnected.asMap().entries.map( + (s) => DrawerTile( + icon: s.value.icon, + title: translatedName(s.value.name), + isSelected: + widget.navigationShell.currentIndex == s.key, + onSelect: () => _goBranch(s.key), + withoutTitle: !_drawerExpanded, + ), + ), + if (serversProvider.selectedServer == null) + ...screensSelectServer.asMap().entries.map( + (s) => DrawerTile( + icon: s.value.icon, + title: translatedName(s.value.name), + isSelected: + widget.navigationShell.currentIndex == s.key, + onSelect: () => _goBranch(s.key), + withoutTitle: !_drawerExpanded, + ), + ), + ], + ), + ), + Expanded( + child: widget.navigationShell + ), + ], + ), + ); + } + else { + final screens = serversProvider.selectedServer != null && serversProvider.apiClient != null + ? screensServerConnected + : screensSelectServer; + + return Scaffold( + body: widget.navigationShell, + bottomNavigationBar: NavigationBar( + selectedIndex: (serversProvider.selectedServer == null || serversProvider.apiClient == null) && widget.navigationShell.currentIndex > 1 + ? 0 + : widget.navigationShell.currentIndex, + onDestinationSelected: (s) => _goBranch(s), + destinations: screens.asMap().entries.map((screen) => NavigationDestination( + icon: Stack( + children: [ + Icon( + screen.value.icon, + color: widget.navigationShell.currentIndex == screen.key + ? Theme.of(context).colorScheme.onSecondaryContainer + : Theme.of(context).colorScheme.onSurfaceVariant, + ), + if ( + screen.value.name == 'settings' && + serversProvider.updateAvailable.data != null && + serversProvider.updateAvailable.data!.canAutoupdate == true + ) Positioned( + bottom: 0, + right: -12, + child: Container( + width: 10, + height: 10, + margin: const EdgeInsets.only(right: 12), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: Colors.red + ), + ), + ) + ], + ), + label: translatedName(screen.value.name) + )).toList(), + ) + ); + } + } +} + +class DrawerTile extends StatelessWidget { + final IconData icon; + final String title; + final bool isSelected; + final void Function() onSelect; + final bool? withoutTitle; + + const DrawerTile({ + super.key, + required this.icon, + required this.title, + required this.isSelected, + required this.onSelect, + this.withoutTitle, + }); + + @override + Widget build(BuildContext context) { + Widget iconWidget = withoutTitle == true + ? Tooltip( + message: title, + child: Icon( + icon, + color: isSelected + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + : Icon( + icon, + color: isSelected + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.onSurfaceVariant, + ); + + return Padding( + padding: const EdgeInsets.only(right: 16), + child: Material( + color: Colors.transparent, + child: InkWell( + borderRadius: const BorderRadius.only( + topRight: Radius.circular(30), + bottomRight: Radius.circular(30), + ), + onTap: onSelect, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + decoration: BoxDecoration( + color: isSelected + ? Theme.of(context).colorScheme.secondaryContainer + : null, + borderRadius: const BorderRadius.only( + topRight: Radius.circular(30), + bottomRight: Radius.circular(30), + ), + ), + child: Row(children: [ + iconWidget, + const SizedBox(width: 16), + Flexible( + child: Text( + title, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontWeight: FontWeight.w500, + fontSize: 14, + color: isSelected + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + ) + ]), + ), + ), + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index c647bf5..a194327 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -222,6 +222,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.1.0" + flutter_hooks: + dependency: "direct main" + description: + name: flutter_hooks + sha256: "7c8db779c2d1010aa7f9ea3fbefe8f86524fcb87b69e8b0af31e1a4b55422dec" + url: "https://pub.dev" + source: hosted + version: "0.20.3" flutter_html: dependency: "direct main" description: @@ -310,6 +318,14 @@ packages: description: flutter source: sdk version: "0.0.0" + go_router: + dependency: "direct main" + description: + name: go_router + sha256: e156bc1b2088eb5ece9351bccd48c3e1719a4858eacbd44e59162e98a68205d1 + url: "https://pub.dev" + source: hosted + version: "12.0.1" html: dependency: "direct main" description: @@ -382,6 +398,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.2" + logging: + dependency: transitive + description: + name: logging + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + url: "https://pub.dev" + source: hosted + version: "1.2.0" markdown: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 2445ec4..06ae67d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -74,6 +74,8 @@ dependencies: flutter_dotenv: ^5.0.2 flutter_reorderable_list: ^1.3.1 pie_chart: ^5.3.2 + go_router: ^12.0.1 + flutter_hooks: ^0.20.3 dev_dependencies: flutter_test: From 96fe7eb730603ed0bc45b247cd158434cc0f3e5c Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 29 Oct 2023 02:19:00 +0100 Subject: [PATCH 2/6] Implemented new router --- lib/config/app_screens.dart | 7 - lib/constants/routes_names.dart | 12 + lib/routes/router_globals.dart | 4 +- lib/routes/routes.dart | 44 +- .../clients/client/logs_list_client.dart | 49 +- lib/screens/clients/clients.dart | 222 +------ lib/screens/clients/clients_desktop_view.dart | 229 ------- lib/screens/clients/clients_list.dart | 2 - lib/screens/clients/clients_lists.dart | 222 +++++++ lib/screens/filters/filters.dart | 34 +- lib/screens/filters/filters_tabs_view.dart | 4 + .../filters/filters_triple_column.dart | 4 + lib/screens/logs/log_details_screen.dart | 4 + lib/screens/logs/log_tile.dart | 6 +- lib/screens/logs/logs.dart | 601 ++---------------- lib/screens/logs/logs_list.dart | 566 +++++++++++++++++ lib/screens/settings/settings.dart | 65 +- 17 files changed, 985 insertions(+), 1090 deletions(-) delete mode 100644 lib/screens/clients/clients_desktop_view.dart create mode 100644 lib/screens/clients/clients_lists.dart create mode 100644 lib/screens/logs/logs_list.dart diff --git a/lib/config/app_screens.dart b/lib/config/app_screens.dart index 91d33f0..4f9a97d 100644 --- a/lib/config/app_screens.dart +++ b/lib/config/app_screens.dart @@ -1,12 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:adguard_home_manager/screens/filters/filters.dart'; -import 'package:adguard_home_manager/screens/logs/logs.dart'; -import 'package:adguard_home_manager/screens/connect/connect.dart'; -import 'package:adguard_home_manager/screens/home/home.dart'; -import 'package:adguard_home_manager/screens/clients/clients.dart'; -import 'package:adguard_home_manager/screens/settings/settings.dart'; - import 'package:adguard_home_manager/models/app_screen.dart'; List screensSelectServer = [ diff --git a/lib/constants/routes_names.dart b/lib/constants/routes_names.dart index dae8723..fa0b07c 100644 --- a/lib/constants/routes_names.dart +++ b/lib/constants/routes_names.dart @@ -16,4 +16,16 @@ class RoutesNames { static const String filters = "/filters"; static const String settings = "/settings"; + static const String safeSearch = "/settings/safe-search"; + static const String accessSettings = "/settings/access-settigs"; + static const String dhcpSettings = "/settings/dhcp-settings"; + static const String dnsSettings = "/settings/dns-settings"; + static const String encryptionSettings = "/settings/encryption-settings"; + static const String dnsRewrites = "/settings/dns-rewrites"; + static const String serverUpdates = "/settings/server-updates"; + static const String serverInfo = "/settings/server-info"; + static const String customization = "/settings/customization"; + static const String servers = "/settings/servers"; + static const String generalSettings = "/settings/general-settings"; + static const String advancedSettings = "/settings/advanced-settings"; } \ No newline at end of file diff --git a/lib/routes/router_globals.dart b/lib/routes/router_globals.dart index 739c50c..f78c1a7 100644 --- a/lib/routes/router_globals.dart +++ b/lib/routes/router_globals.dart @@ -4,7 +4,7 @@ final GlobalKey rootNavigatorKey = GlobalKey(); final GlobalKey connectNavigatorKey = GlobalKey(); final GlobalKey homeNavigatorKey = GlobalKey(); final GlobalKey clientsNavigatorKey = GlobalKey(); -final GlobalKey clientsListNavigatorKey = GlobalKey(); final GlobalKey logsNavigatorKey = GlobalKey(); final GlobalKey filtersNavigatorKey = GlobalKey(); -final GlobalKey settingsNavigatorKey = GlobalKey(); \ No newline at end of file +final GlobalKey settingsNavigatorKey = GlobalKey(); +final GlobalKey settingsListNavigatorKey = GlobalKey(); \ No newline at end of file diff --git a/lib/routes/routes.dart b/lib/routes/routes.dart index 04bd218..a216446 100644 --- a/lib/routes/routes.dart +++ b/lib/routes/routes.dart @@ -4,10 +4,8 @@ import 'package:adguard_home_manager/screens/home/home.dart'; import 'package:adguard_home_manager/screens/clients/clients.dart'; import 'package:adguard_home_manager/screens/connect/connect.dart'; import 'package:adguard_home_manager/screens/filters/filters.dart'; -import 'package:adguard_home_manager/screens/settings/settings.dart'; -import 'package:adguard_home_manager/screens/clients/client/client_placeholder.dart'; -import 'package:adguard_home_manager/screens/clients/client/logs_list_client.dart'; import 'package:adguard_home_manager/screens/logs/logs.dart'; +import 'package:adguard_home_manager/screens/settings/settings.dart'; import 'package:adguard_home_manager/widgets/layout.dart'; import 'package:adguard_home_manager/routes/router_globals.dart'; @@ -31,44 +29,15 @@ final List routes = [ path: RoutesNames.home, builder: (context, state) => const Home(), ), - GoRoute( - parentNavigatorKey: homeNavigatorKey, - path: RoutesNames.queriedDomains, - builder: (context, state) => const Home(), - ), - GoRoute( - parentNavigatorKey: homeNavigatorKey, - path: RoutesNames.blockedDomains, - builder: (context, state) => const Home(), - ), - GoRoute( - parentNavigatorKey: homeNavigatorKey, - path: RoutesNames.recurrentClients, - builder: (context, state) => const Home(), - ), ] ), StatefulShellBranch( navigatorKey: clientsNavigatorKey, routes: [ - ShellRoute( + GoRoute( parentNavigatorKey: clientsNavigatorKey, - navigatorKey: clientsListNavigatorKey, - builder: (context, state, child) => Clients(child: child), - routes: [ - GoRoute( - path: RoutesNames.clientPlaceholder, - parentNavigatorKey: clientsListNavigatorKey, - builder: (context, state) => const ClientPlaceholder(), - ), - GoRoute( - path: RoutesNames.client, - parentNavigatorKey: clientsListNavigatorKey, - builder: (context, state) => LogsListClient( - id: (state.extra as Map?)?['id'] - ) - ) - ] + path: RoutesNames.clients, + builder: (context, state) => const Clients(), ) ] ), @@ -76,6 +45,7 @@ final List routes = [ navigatorKey: logsNavigatorKey, routes: [ GoRoute( + parentNavigatorKey: logsNavigatorKey, path: RoutesNames.logs, builder: (context, state) => const Logs(), ) @@ -85,6 +55,7 @@ final List routes = [ navigatorKey: filtersNavigatorKey, routes: [ GoRoute( + parentNavigatorKey: filtersNavigatorKey, path: RoutesNames.filters, builder: (context, state) => const Filters(), ) @@ -94,7 +65,8 @@ final List routes = [ navigatorKey: settingsNavigatorKey, routes: [ GoRoute( - path: RoutesNames.settings, + parentNavigatorKey: settingsNavigatorKey, + path: RoutesNames.settings, builder: (context, state) => const Settings(), ) ] diff --git a/lib/screens/clients/client/logs_list_client.dart b/lib/screens/clients/client/logs_list_client.dart index f35de0b..eb724fd 100644 --- a/lib/screens/clients/client/logs_list_client.dart +++ b/lib/screens/clients/client/logs_list_client.dart @@ -1,9 +1,7 @@ import 'dart:io'; -import 'package:adguard_home_manager/constants/enums.dart'; import 'package:flutter/material.dart'; import 'package:async/async.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -14,12 +12,20 @@ import 'package:adguard_home_manager/models/logs.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/providers/servers_provider.dart'; -class LogsListClient extends StatefulHookWidget { - final String id; +class LogsListClient extends StatefulWidget { + final String ip; + final String? name; + final ServersProvider serversProvider; + final AppConfigProvider appConfigProvider; + final bool splitView; const LogsListClient({ Key? key, - required this.id, + required this.ip, + this.name, + required this.serversProvider, + required this.appConfigProvider, + required this.splitView, }) : super(key: key); @override @@ -34,9 +40,11 @@ class _LogsListClientState extends State { int logsQuantity = 100; int offset = 0; - LoadStatus loadStatus = LoadStatus.loading; + int loadStatus = 0; LogsData? logsData; + String previousIp = ""; + bool showDivider = true; CancelableOperation? cancelableRequest; @@ -61,7 +69,7 @@ class _LogsListClientState extends State { serversProvider.apiClient!.getLogs( count: logsQuantity, offset: offst, - search: '"${widget.id}"' + search: '"${widget.ip}"' ) ); @@ -84,11 +92,11 @@ class _LogsListClientState extends State { LogsData newLogsData = result['data']; setState(() => logsData = newLogsData); } - setState(() => loadStatus = LoadStatus.loaded); + setState(() => loadStatus = 1); } else { - setState(() => loadStatus = LoadStatus.error); - Provider.of(context, listen: false).addLog(result['log']); + setState(() => loadStatus = 2); + widget.appConfigProvider.addLog(result['log']); } } } @@ -110,6 +118,8 @@ class _LogsListClientState extends State { @override void initState() { scrollController = ScrollController()..addListener(scrollListener); + fetchLogs(inOffset: 0); + setState(() => previousIp = widget.ip); super.initState(); } @@ -117,15 +127,15 @@ class _LogsListClientState extends State { Widget build(BuildContext context) { final width = MediaQuery.of(context).size.width; - useEffect(() { - setState(() => loadStatus = LoadStatus.loading); + if (widget.ip != previousIp) { + setState(() => loadStatus = 0); fetchLogs(inOffset: 0); - return null; - }, [widget.id]); + setState(() => previousIp = widget.ip); + } Widget status() { switch (loadStatus) { - case LoadStatus.loading: + case 0: return SizedBox( width: double.maxFinite, child: Column( @@ -146,7 +156,7 @@ class _LogsListClientState extends State { ), ); - case LoadStatus.loaded: + case 1: if (logsData!.data.isNotEmpty) { return RefreshIndicator( onRefresh: fetchLogs, @@ -189,7 +199,8 @@ class _LogsListClientState extends State { ) )) } - } + }, + twoColumns: widget.splitView, ); } } @@ -209,7 +220,7 @@ class _LogsListClientState extends State { ); } - case LoadStatus.error: + case 2: return SizedBox( width: double.maxFinite, child: Column( @@ -241,7 +252,7 @@ class _LogsListClientState extends State { return Scaffold( appBar: AppBar( - title: Text(widget.id), + title: Text(widget.name != null && widget.name != '' ? widget.name! : widget.ip), centerTitle: true, actions: [ if (!(Platform.isAndroid || Platform.isIOS)) ...[ diff --git a/lib/screens/clients/clients.dart b/lib/screens/clients/clients.dart index 45ccbd4..80393a8 100644 --- a/lib/screens/clients/clients.dart +++ b/lib/screens/clients/clients.dart @@ -1,213 +1,55 @@ import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; -import 'package:provider/provider.dart'; +import 'package:flutter_split_view/flutter_split_view.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:adguard_home_manager/screens/clients/clients_list.dart'; -import 'package:adguard_home_manager/screens/clients/added_list.dart'; +import 'package:adguard_home_manager/screens/clients/clients_lists.dart'; -import 'package:adguard_home_manager/constants/routes_names.dart'; -import 'package:adguard_home_manager/functions/desktop_mode.dart'; -import 'package:adguard_home_manager/providers/clients_provider.dart'; -import 'package:adguard_home_manager/providers/app_config_provider.dart'; -import 'package:adguard_home_manager/constants/enums.dart'; import 'package:adguard_home_manager/models/clients.dart'; class Clients extends StatefulWidget { - final Widget child; - - const Clients({ - Key? key, - required this.child, - }) : super(key: key); + const Clients({Key? key}) : super(key: key); @override State createState() => _ClientsState(); } class _ClientsState extends State with TickerProviderStateMixin { - late TabController tabController; - final ScrollController scrollController = ScrollController(); - - bool searchMode = false; - final TextEditingController searchController = TextEditingController(); - - @override - void initState() { - final clientsProvider = Provider.of(context, listen: false); - clientsProvider.fetchClients(updateLoading: true); - - super.initState(); - tabController = TabController( - initialIndex: 0, - length: 2, - vsync: this, - ); - tabController.addListener( - () => Provider.of(context, listen: false).setSelectedClientsTab(tabController.index) - ); - } - List generateClientsList(List clients, List ips) { return clients.where((client) => ips.contains(client.ip)).toList(); } @override Widget build(BuildContext context) { - final clientsProvider = Provider.of(context); - - final width = MediaQuery.of(context).size.width; - - PreferredSizeWidget tabBar() { - return TabBar( - controller: tabController, - unselectedLabelColor: Theme.of(context).colorScheme.onSurfaceVariant, - tabs: [ - Tab( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.devices), - const SizedBox(width: 8), - Text(AppLocalizations.of(context)!.activeClients) - ], + return LayoutBuilder( + builder: (context, constraints) { + if (constraints.maxWidth > 1000) { + return SplitView.material( + hideDivider: true, + flexWidth: const FlexWidth(mainViewFlexWidth: 1, secondaryViewFlexWidth: 2), + placeholder: Center( + child: Padding( + padding: const EdgeInsets.all(24), + child: Text( + AppLocalizations.of(context)!.selectClientLeftColumn, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 24, + color: Theme.of(context).colorScheme.onSurfaceVariant + ), + ), + ), ), - ), - Tab( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.add_rounded), - const SizedBox(width: 8), - Text(AppLocalizations.of(context)!.added) - ], - ), - ), - ] - ); - } - - Widget tabBarView(bool sliver) { - return TabBarView( - controller: tabController, - children: [ - ClientsList( - scrollController: scrollController, - data: clientsProvider.loadStatus == LoadStatus.loaded - ? clientsProvider.filteredActiveClients : [], - onClientSelected: (client) => context.go( - RoutesNames.client, - extra: { - "id": client.name != null && client.name != "" - ? client.name - : client.ip - } - ), - splitView: isDesktop(width), - ), - AddedList( - scrollController: scrollController, - data: clientsProvider.loadStatus == LoadStatus.loaded - ? clientsProvider.filteredAddedClients : [], - onClientSelected: (client) => context.go( - RoutesNames.client, - extra: { "id": client.name } - ), - splitView: isDesktop(width), - ), - ] - ); - } - - return Row( - children: [ - SizedBox( - width: isDesktop(width) ? 300 : width, - height: double.maxFinite, - child: Material( - child: DefaultTabController( - length: 2, - child: NestedScrollView( - controller: scrollController, - headerSliverBuilder: ((context, innerBoxIsScrolled) { - return [ - SliverOverlapAbsorber( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - sliver: SliverAppBar( - title: searchMode == true - ? Row( - children: [ - IconButton( - onPressed: () { - setState(() { - searchMode = false; - searchController.text = ""; - clientsProvider.setSearchTermClients(null); - }); - }, - icon: const Icon(Icons.arrow_back_rounded), - tooltip: AppLocalizations.of(context)!.exitSearch, - ), - const SizedBox(width: 16), - Expanded( - child: TextField( - controller: searchController, - onChanged: (value) => clientsProvider.setSearchTermClients(value), - decoration: InputDecoration( - suffixIcon: IconButton( - onPressed: () { - setState(() { - searchController.text = ""; - clientsProvider.setSearchTermClients(null); - }); - }, - icon: const Icon(Icons.clear_rounded) - ), - hintText: AppLocalizations.of(context)!.search, - hintStyle: const TextStyle( - fontWeight: FontWeight.normal, - fontSize: 18 - ), - border: InputBorder.none, - ), - style: const TextStyle( - fontWeight: FontWeight.normal, - fontSize: 18 - ), - autofocus: true, - ), - ) - ], - ) - : Text(AppLocalizations.of(context)!.clients), - pinned: true, - floating: true, - centerTitle: false, - forceElevated: innerBoxIsScrolled, - actions: [ - if (clientsProvider.loadStatus == LoadStatus.loaded && searchMode == false) ...[ - IconButton( - onPressed: () => setState(() => searchMode = true), - icon: const Icon(Icons.search), - tooltip: AppLocalizations.of(context)!.searchClients, - ), - const SizedBox(width: 10), - ] - ], - bottom: tabBar() - ), - ) - ]; - }), - body: tabBarView(true) - ) - ), - ), - ), - if (isDesktop(width) == true) Expanded( - child: widget.child, - ) - ], + child: const ClientsLists( + splitView: true, + ) + ); + } + else { + return const ClientsLists( + splitView: false, + ); + } + }, ); } } \ No newline at end of file diff --git a/lib/screens/clients/clients_desktop_view.dart b/lib/screens/clients/clients_desktop_view.dart deleted file mode 100644 index 7356e15..0000000 --- a/lib/screens/clients/clients_desktop_view.dart +++ /dev/null @@ -1,229 +0,0 @@ -import 'dart:io'; - -import 'package:flutter/material.dart'; -import 'package:flutter_split_view/flutter_split_view.dart'; -import 'package:provider/provider.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; - -import 'package:adguard_home_manager/screens/clients/client/logs_list_client.dart'; -import 'package:adguard_home_manager/screens/clients/added_list.dart'; -import 'package:adguard_home_manager/screens/clients/clients_list.dart'; - -import 'package:adguard_home_manager/providers/app_config_provider.dart'; -import 'package:adguard_home_manager/providers/clients_provider.dart'; -import 'package:adguard_home_manager/constants/enums.dart'; -import 'package:adguard_home_manager/models/clients.dart'; -import 'package:adguard_home_manager/providers/servers_provider.dart'; - - -class ClientsDesktopView extends StatefulWidget { - final ServersProvider serversProvider; - final AppConfigProvider appConfigProvider; - - const ClientsDesktopView({ - Key? key, - required this.serversProvider, - required this.appConfigProvider, - }) : super(key: key); - - @override - State createState() => _ClientsDesktopViewState(); -} - -class _ClientsDesktopViewState extends State with TickerProviderStateMixin { - late TabController tabController; - final ScrollController scrollController = ScrollController(); - - AutoClient? selectedActiveClient; - Client? selectedAddedClient; - - bool searchMode = false; - final TextEditingController searchController = TextEditingController(); - - @override - void initState() { - super.initState(); - tabController = TabController( - initialIndex: 0, - length: 2, - vsync: this, - ); - tabController.addListener(() => widget.appConfigProvider.setSelectedClientsTab(tabController.index)); - } - - @override - Widget build(BuildContext context) { - final serversProvider = Provider.of(context); - final clientsProvider = Provider.of(context); - final appConfigProvider = Provider.of(context); - - PreferredSizeWidget tabBar() { - return TabBar( - controller: tabController, - unselectedLabelColor: Theme.of(context).colorScheme.onSurfaceVariant, - tabs: [ - Tab( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.devices), - const SizedBox(width: 8), - Text(AppLocalizations.of(context)!.activeClients) - ], - ), - ), - Tab( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.add_rounded), - const SizedBox(width: 8), - Text(AppLocalizations.of(context)!.added) - ], - ), - ), - ] - ); - } - - Widget tabBarView(bool sliver) { - return TabBarView( - controller: tabController, - children: [ - ClientsList( - scrollController: scrollController, - data: clientsProvider.loadStatus == LoadStatus.loaded - ? clientsProvider.filteredActiveClients : [], - onClientSelected: (client) => setState(() { - selectedAddedClient = null; - selectedActiveClient = client; - - }), - selectedClient: selectedActiveClient, - splitView: true, - ), - AddedList( - scrollController: scrollController, - data: clientsProvider.loadStatus == LoadStatus.loaded - ? clientsProvider.filteredAddedClients : [], - onClientSelected: (client) => setState(() { - selectedActiveClient = null; - selectedAddedClient = client; - - }), - selectedClient: selectedAddedClient, - splitView: true, - ), - ] - ); - } - - Widget title() { - if (searchMode == true) { - return Row( - children: [ - IconButton( - onPressed: () { - setState(() { - searchMode = false; - searchController.text = ""; - clientsProvider.setSearchTermClients(null); - }); - }, - icon: const Icon(Icons.arrow_back_rounded) - ), - const SizedBox(width: 16), - Expanded( - child: TextField( - controller: searchController, - onChanged: (value) => clientsProvider.setSearchTermClients(value), - decoration: InputDecoration( - suffixIcon: IconButton( - onPressed: () { - setState(() { - searchController.text = ""; - clientsProvider.setSearchTermClients(null); - }); - }, - icon: const Icon(Icons.clear_rounded) - ), - hintText: AppLocalizations.of(context)!.search, - hintStyle: const TextStyle( - fontWeight: FontWeight.normal, - fontSize: 18 - ), - border: InputBorder.none, - ), - style: const TextStyle( - fontWeight: FontWeight.normal, - fontSize: 18 - ), - ), - ) - ], - ); - } - else { - return Text(AppLocalizations.of(context)!.clients); - } - } - - if (!(Platform.isAndroid || Platform.isIOS)) { - return DefaultTabController( - length: 2, - child: Scaffold( - appBar: AppBar( - title: title(), - centerTitle: false, - actions: [ - if (clientsProvider.loadStatus == LoadStatus.loaded && searchMode == false) ...[ - IconButton( - onPressed: () => setState(() => searchMode = true), - icon: const Icon(Icons.search), - tooltip: AppLocalizations.of(context)!.searchClients, - ), - const SizedBox(width: 10), - ] - ], - bottom: tabBar() - ), - body: tabBarView(false), - ), - ); - } - else { - return DefaultTabController( - length: 2, - child: NestedScrollView( - controller: scrollController, - headerSliverBuilder: ((context, innerBoxIsScrolled) { - return [ - SliverOverlapAbsorber( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - sliver: SliverAppBar( - title: title(), - pinned: true, - floating: true, - centerTitle: false, - forceElevated: innerBoxIsScrolled, - actions: [ - if (clientsProvider.loadStatus == LoadStatus.loaded && searchMode == false) ...[ - IconButton( - onPressed: () => setState(() => searchMode = true), - icon: const Icon(Icons.search), - tooltip: AppLocalizations.of(context)!.searchClients, - ), - const SizedBox(width: 10), - ] - ], - bottom: tabBar() - ), - ) - ]; - }), - body: tabBarView(true) - ) - ); - } - } -} \ No newline at end of file diff --git a/lib/screens/clients/clients_list.dart b/lib/screens/clients/clients_list.dart index d0ecd33..2e52738 100644 --- a/lib/screens/clients/clients_list.dart +++ b/lib/screens/clients/clients_list.dart @@ -10,7 +10,6 @@ import 'package:adguard_home_manager/providers/clients_provider.dart'; import 'package:adguard_home_manager/models/clients.dart'; class ClientsList extends StatelessWidget { - final ScrollController scrollController; final List data; final void Function(AutoClient) onClientSelected; final AutoClient? selectedClient; @@ -18,7 +17,6 @@ class ClientsList extends StatelessWidget { const ClientsList({ Key? key, - required this.scrollController, required this.data, required this.onClientSelected, this.selectedClient, diff --git a/lib/screens/clients/clients_lists.dart b/lib/screens/clients/clients_lists.dart new file mode 100644 index 0000000..ec451bc --- /dev/null +++ b/lib/screens/clients/clients_lists.dart @@ -0,0 +1,222 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_split_view/flutter_split_view.dart'; +import 'package:provider/provider.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +import 'package:adguard_home_manager/screens/clients/added_list.dart'; +import 'package:adguard_home_manager/screens/clients/client/logs_list_client.dart'; +import 'package:adguard_home_manager/screens/clients/clients_list.dart'; + +import 'package:adguard_home_manager/constants/enums.dart'; +import 'package:adguard_home_manager/functions/desktop_mode.dart'; +import 'package:adguard_home_manager/models/clients.dart'; +import 'package:adguard_home_manager/providers/app_config_provider.dart'; +import 'package:adguard_home_manager/providers/clients_provider.dart'; +import 'package:adguard_home_manager/providers/servers_provider.dart'; + +class ClientsLists extends StatefulWidget { + final bool splitView; + + const ClientsLists({ + Key? key, + required this.splitView, + }) : super(key: key); + + @override + State createState() => _ClientsListsState(); +} + +class _ClientsListsState extends State with TickerProviderStateMixin { + late TabController tabController; + final ScrollController scrollController = ScrollController(); + + bool searchMode = false; + final TextEditingController searchController = TextEditingController(); + + AutoClient? _selectedAutoClient; + Client? _selectedClient; + + @override + void initState() { + final clientsProvider = Provider.of(context, listen: false); + clientsProvider.fetchClients(updateLoading: true); + + super.initState(); + tabController = TabController( + initialIndex: 0, + length: 2, + vsync: this, + ); + tabController.addListener( + () => Provider.of(context, listen: false).setSelectedClientsTab(tabController.index) + ); + } + + + @override + Widget build(BuildContext context) { + final serversProvider = Provider.of(context); + final clientsProvider = Provider.of(context); + final appConfigProvider = Provider.of(context); + + void onAutoClientSelected(AutoClient client) { + setState(() => _selectedAutoClient = client); + final w = LogsListClient( + ip: client.ip, + serversProvider: serversProvider, + appConfigProvider: appConfigProvider, + splitView: widget.splitView, + ); + if (widget.splitView) { + SplitView.of(context).push(w); + } + else { + Navigator.push(context, MaterialPageRoute( + builder: (context) => w, + )); + } + } + + void onClientSelected(Client client) { + setState(() => _selectedClient = client); + final w = LogsListClient( + ip: client.ids[0], + serversProvider: serversProvider, + appConfigProvider: appConfigProvider, + splitView: widget.splitView, + ); + if (widget.splitView) { + SplitView.of(context).push(w); + } + else { + Navigator.push(context, MaterialPageRoute( + builder: (context) => w, + )); + } + } + + return DefaultTabController( + length: 2, + child: NestedScrollView( + headerSliverBuilder: ((context, innerBoxIsScrolled) { + return [ + SliverOverlapAbsorber( + handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), + sliver: SliverAppBar( + title: searchMode == true + ? Row( + children: [ + IconButton( + onPressed: () { + setState(() { + searchMode = false; + searchController.text = ""; + clientsProvider.setSearchTermClients(null); + }); + }, + icon: const Icon(Icons.arrow_back_rounded), + tooltip: AppLocalizations.of(context)!.exitSearch, + ), + const SizedBox(width: 16), + Expanded( + child: TextField( + controller: searchController, + onChanged: (value) => clientsProvider.setSearchTermClients(value), + decoration: InputDecoration( + suffixIcon: IconButton( + onPressed: () { + setState(() { + searchController.text = ""; + clientsProvider.setSearchTermClients(null); + }); + }, + icon: const Icon(Icons.clear_rounded) + ), + hintText: AppLocalizations.of(context)!.search, + hintStyle: const TextStyle( + fontWeight: FontWeight.normal, + fontSize: 18 + ), + border: InputBorder.none, + ), + style: const TextStyle( + fontWeight: FontWeight.normal, + fontSize: 18 + ), + autofocus: true, + ), + ) + ], + ) + : Text(AppLocalizations.of(context)!.clients), + pinned: true, + floating: true, + centerTitle: false, + forceElevated: innerBoxIsScrolled, + surfaceTintColor: isDesktop(MediaQuery.of(context).size.width) + ? Colors.transparent + : null, + actions: [ + if (clientsProvider.loadStatus == LoadStatus.loaded && searchMode == false) ...[ + IconButton( + onPressed: () => setState(() => searchMode = true), + icon: const Icon(Icons.search), + tooltip: AppLocalizations.of(context)!.searchClients, + ), + const SizedBox(width: 10), + ] + ], + bottom: TabBar( + controller: tabController, + unselectedLabelColor: Theme.of(context).colorScheme.onSurfaceVariant, + tabs: [ + Tab( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.devices), + const SizedBox(width: 8), + Text(AppLocalizations.of(context)!.activeClients) + ], + ), + ), + Tab( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.add_rounded), + const SizedBox(width: 8), + Text(AppLocalizations.of(context)!.added) + ], + ), + ), + ] + ) + ), + ) + ]; + }), + body: TabBarView( + controller: tabController, + children: [ + ClientsList( + data: clientsProvider.loadStatus == LoadStatus.loaded + ? clientsProvider.filteredActiveClients : [], + onClientSelected: onAutoClientSelected, + selectedClient: _selectedAutoClient, + splitView: widget.splitView, + ), + AddedList( + scrollController: scrollController, + data: clientsProvider.loadStatus == LoadStatus.loaded + ? clientsProvider.filteredAddedClients : [], + onClientSelected: onClientSelected, + selectedClient: _selectedClient, + splitView: widget.splitView, + ), + ] + ) + ) + ); + } +} \ No newline at end of file diff --git a/lib/screens/filters/filters.dart b/lib/screens/filters/filters.dart index c4e6d9b..4b94b1e 100644 --- a/lib/screens/filters/filters.dart +++ b/lib/screens/filters/filters.dart @@ -324,20 +324,24 @@ class _FiltersState extends State { } } - if (width > 1200) { - return FiltersTripleColumn( - onRemoveCustomRule: openRemoveCustomRuleModal, - onOpenDetailsModal: openListDetails, - actions: actions(), - ); - } - else { - return FiltersTabsView( - appConfigProvider: appConfigProvider, - actions: actions(), - onRemoveCustomRule: openRemoveCustomRuleModal, - onOpenDetailsModal: openListDetails, - ); - } + return LayoutBuilder( + builder: (context, constraints) { + if (constraints.maxWidth > 900) { + return FiltersTripleColumn( + onRemoveCustomRule: openRemoveCustomRuleModal, + onOpenDetailsModal: openListDetails, + actions: actions(), + ); + } + else { + return FiltersTabsView( + appConfigProvider: appConfigProvider, + actions: actions(), + onRemoveCustomRule: openRemoveCustomRuleModal, + onOpenDetailsModal: openListDetails, + ); + } + }, + ); } } \ No newline at end of file diff --git a/lib/screens/filters/filters_tabs_view.dart b/lib/screens/filters/filters_tabs_view.dart index 81cd67a..63e6a4e 100644 --- a/lib/screens/filters/filters_tabs_view.dart +++ b/lib/screens/filters/filters_tabs_view.dart @@ -5,6 +5,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/screens/filters/custom_rules_list.dart'; import 'package:adguard_home_manager/screens/filters/filters_list.dart'; +import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:adguard_home_manager/providers/filtering_provider.dart'; import 'package:adguard_home_manager/constants/enums.dart'; import 'package:adguard_home_manager/models/filtering.dart'; @@ -47,6 +48,8 @@ class _FiltersTabsViewState extends State with TickerProviderSt Widget build(BuildContext context) { final filteringProvider = Provider.of(context); + final width = MediaQuery.of(context).size.width; + return DefaultTabController( length: 3, child: NestedScrollView( @@ -62,6 +65,7 @@ class _FiltersTabsViewState extends State with TickerProviderSt forceElevated: innerBoxIsScrolled, centerTitle: false, actions: widget.actions, + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, bottom: TabBar( controller: tabController, isScrollable: true, diff --git a/lib/screens/filters/filters_triple_column.dart b/lib/screens/filters/filters_triple_column.dart index 2f7979d..3a8bfa2 100644 --- a/lib/screens/filters/filters_triple_column.dart +++ b/lib/screens/filters/filters_triple_column.dart @@ -13,6 +13,7 @@ import 'package:adguard_home_manager/widgets/custom_list_tile.dart'; import 'package:adguard_home_manager/widgets/options_modal.dart'; import 'package:adguard_home_manager/constants/enums.dart'; +import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:adguard_home_manager/models/menu_option.dart'; import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/functions/copy_clipboard.dart'; @@ -37,6 +38,8 @@ class FiltersTripleColumn extends StatelessWidget { Widget build(BuildContext context) { final filteringProvider = Provider.of(context); final appConfigProvider = Provider.of(context); + + final width = MediaQuery.of(context).size.width; Widget? generateSubtitle(String rule) { final allowRegex = RegExp(r'^@@.*$'); @@ -325,6 +328,7 @@ class FiltersTripleColumn extends StatelessWidget { return Scaffold( appBar: AppBar( + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, title: Text(AppLocalizations.of(context)!.filters), actions: [ IconButton( diff --git a/lib/screens/logs/log_details_screen.dart b/lib/screens/logs/log_details_screen.dart index 859d5e7..161e9fa 100644 --- a/lib/screens/logs/log_details_screen.dart +++ b/lib/screens/logs/log_details_screen.dart @@ -8,6 +8,7 @@ import 'package:adguard_home_manager/widgets/section_label.dart'; import 'package:adguard_home_manager/screens/logs/log_list_tile.dart'; +import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:adguard_home_manager/functions/open_url.dart'; import 'package:adguard_home_manager/constants/urls.dart'; import 'package:adguard_home_manager/classes/process_modal.dart'; @@ -34,6 +35,8 @@ class LogDetailsScreen extends StatelessWidget { final appConfigProvider = Provider.of(context); final statusProvider = Provider.of(context); + final width = MediaQuery.of(context).size.width; + Filter? getList(int id) { try { return statusProvider.filteringStatus!.filters.firstWhere((filter) => filter.id == id, orElse: () { @@ -288,6 +291,7 @@ class LogDetailsScreen extends StatelessWidget { floating: true, centerTitle: false, forceElevated: innerBoxIsScrolled, + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, title: Text(AppLocalizations.of(context)!.logDetails), actions: [ IconButton( diff --git a/lib/screens/logs/log_tile.dart b/lib/screens/logs/log_tile.dart index bd2b1a9..40f3b0d 100644 --- a/lib/screens/logs/log_tile.dart +++ b/lib/screens/logs/log_tile.dart @@ -17,6 +17,7 @@ class LogTile extends StatelessWidget { final bool? isLogSelected; final void Function(Log) onLogTap; final bool? useAlwaysNormalTile; + final bool twoColumns; const LogTile({ Key? key, @@ -25,7 +26,8 @@ class LogTile extends StatelessWidget { required this.index, this.isLogSelected, required this.onLogTap, - this.useAlwaysNormalTile + this.useAlwaysNormalTile, + required this.twoColumns, }) : super(key: key); @override @@ -83,7 +85,7 @@ class LogTile extends StatelessWidget { } } - if (width > 1100 && !(useAlwaysNormalTile == true)) { + if (twoColumns && !(useAlwaysNormalTile == true)) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 12), child: InkWell( diff --git a/lib/screens/logs/logs.dart b/lib/screens/logs/logs.dart index 6d9cef6..b218ad7 100644 --- a/lib/screens/logs/logs.dart +++ b/lib/screens/logs/logs.dart @@ -1,27 +1,10 @@ // ignore_for_file: use_build_context_synchronously -import 'dart:io'; - -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/logs/logs_filters_modal.dart'; -import 'package:adguard_home_manager/screens/logs/logs_config_modal.dart'; -import 'package:adguard_home_manager/screens/logs/log_tile.dart'; -import 'package:adguard_home_manager/screens/logs/log_details_screen.dart'; - -import 'package:adguard_home_manager/functions/snackbar.dart'; -import 'package:adguard_home_manager/classes/process_modal.dart'; -import 'package:adguard_home_manager/models/applied_filters.dart'; -import 'package:adguard_home_manager/functions/compare_versions.dart'; -import 'package:adguard_home_manager/providers/clients_provider.dart'; -import 'package:adguard_home_manager/constants/enums.dart'; -import 'package:adguard_home_manager/providers/status_provider.dart'; -import 'package:adguard_home_manager/providers/logs_provider.dart'; -import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/models/logs.dart'; -import 'package:adguard_home_manager/providers/servers_provider.dart'; +import 'package:flutter/material.dart'; + +import 'package:adguard_home_manager/screens/logs/logs_list.dart'; +import 'package:adguard_home_manager/screens/logs/log_details_screen.dart'; class Logs extends StatefulWidget { const Logs({Key? key}) : super(key: key); @@ -31,554 +14,48 @@ class Logs extends StatefulWidget { } class _LogsState extends State { - bool showDivider = true; - - Log? selectedLog; - - void fetchFilteringRules() async { - final appConfigProvider = Provider.of(context, listen: false); - final statusProvider = Provider.of(context, listen: false); - - final result = await statusProvider.getFilteringRules(); - if (mounted && result == false) { - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.couldntGetFilteringStatus, - color: Colors.red - ); - } - } - - Future fetchClients() async { - final clientsProvider = Provider.of(context, listen: false); - final appConfigProvider = Provider.of(context, listen: false); - - final result = await clientsProvider.fetchClients(); - if (mounted && result == false) { - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.couldntGetFilteringStatus, - color: Colors.red - ); - } - } - - bool scrollListener(ScrollUpdateNotification scrollNotification) { - final logsProvider = Provider.of(context, listen: false); - - if (scrollNotification.metrics.extentAfter < 500 && logsProvider.isLoadingMore == false) { - logsProvider.fetchLogs(loadingMore: true); - } - if (scrollNotification.metrics.pixels > 0) { - setState(() => showDivider = false); - } - else { - setState(() => showDivider = true); - } - - return false; - } - - @override - void initState() { - final logsProvider = Provider.of(context, listen: false); - - logsProvider.fetchLogs(inOffset: 0); - fetchFilteringRules(); - fetchClients(); - super.initState(); - } + Log? _selectedLog; @override Widget build(BuildContext context) { - final serversProvider = Provider.of(context); - final statusProvider = Provider.of(context); - final appConfigProvider = Provider.of(context); - final logsProvider = Provider.of(context); + - final width = MediaQuery.of(context).size.width; - - void updateConfig(Map data) async { - ProcessModal processModal = ProcessModal(context: context); - processModal.open(AppLocalizations.of(context)!.updatingSettings); - - final result = serverVersionIsAhead( - currentVersion: statusProvider.serverStatus!.serverVersion, - referenceVersion: 'v0.107.28', - referenceVersionBeta: 'v0.108.0-b.33' - ) == true - ? await serversProvider.apiClient!.updateQueryLogParameters(data: data) - : await serversProvider.apiClient!.updateQueryLogParametersLegacy(data: data); - - processModal.close(); - - if (result['result'] == 'success') { - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.logsConfigUpdated, - color: Colors.green - ); - } - else { - appConfigProvider.addLog(result['log']); - - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.logsConfigNotUpdated, - color: Colors.red - ); - } - } - - void clearQueries() async { - ProcessModal processModal = ProcessModal(context: context); - processModal.open(AppLocalizations.of(context)!.updatingSettings); - - final result = await serversProvider.apiClient!.clearLogs(); - - processModal.close(); - - if (result['result'] == 'success') { - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.logsCleared, - color: Colors.green - ); - } - else { - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.logsNotCleared, - color: Colors.red - ); - } - } - - - void openFilersModal() { - if (width > 700 || !(Platform.isAndroid || Platform.isIOS)) { - showDialog( - context: context, - builder: (context) => const LogsFiltersModal( - dialog: true, - ), - barrierDismissible: false - ); - } - else { - showModalBottomSheet( - context: context, - builder: (context) => const LogsFiltersModal( - dialog: false, - ), - backgroundColor: Colors.transparent, - isScrollControlled: true - ); - } - } - - final Map translatedString = { - "all": AppLocalizations.of(context)!.all, - "filtered": AppLocalizations.of(context)!.filtered, - "processed": AppLocalizations.of(context)!.processedRow, - "whitelisted": AppLocalizations.of(context)!.processedWhitelistRow, - "blocked": AppLocalizations.of(context)!.blocked, - "blocked_safebrowsing": AppLocalizations.of(context)!.blockedSafeBrowsingRow, - "blocked_parental": AppLocalizations.of(context)!.blockedParentalRow, - "safe_search": AppLocalizations.of(context)!.safeSearch, - }; - - Widget generateBody() { - switch (logsProvider.loadStatus) { - case LoadStatus.loading: - return SafeArea( - top: false, - bottom: false, - child: Builder( - builder: (context) => CustomScrollView( - slivers: [ - SliverOverlapInjector( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - ), - SliverFillRemaining( - child: SizedBox( - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const CircularProgressIndicator(), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.loadingLogs, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ), - ) + return LayoutBuilder( + builder: (context, constraints) { + if (constraints.maxWidth > 1000) { + return Material( + color: Colors.transparent, + child: Row( + children: [ + Expanded( + flex: 2, + child: LogsListWidget( + twoColumns: true, + selectedLog: _selectedLog, + onLogSelected: (log) => setState(() => _selectedLog = log), ) - ], - ), - ) - ); - - case LoadStatus.loaded: - return SafeArea( - top: false, - bottom: false, - child: Builder( - builder: (context) => RefreshIndicator( - onRefresh: () async { - await logsProvider.fetchLogs(inOffset: 0); - }, - displacement: 95, - child: NotificationListener( - onNotification: scrollListener, - child: CustomScrollView( - slivers: [ - SliverOverlapInjector( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - ), - if (logsProvider.logsData!.data.isNotEmpty) SliverList.builder( - itemCount: logsProvider.isLoadingMore - ? logsProvider.logsData!.data.length + 1 - : logsProvider.logsData!.data.length, - itemBuilder: (context, index) { - if (logsProvider.isLoadingMore == true && index == logsProvider.logsData!.data.length) { - return const Padding( - padding: EdgeInsets.symmetric(vertical: 20), - child: Center( - child: CircularProgressIndicator(), - ), - ); - } - else if (logsProvider.logsData!.data[index].question.name != null) { - return LogTile( - log: logsProvider.logsData!.data[index], - index: index, - length: logsProvider.logsData!.data.length, - isLogSelected: selectedLog != null && selectedLog == logsProvider.logsData!.data[index], - onLogTap: (log) { - if (width <= 1100) { - Navigator.push(context, MaterialPageRoute( - builder: (context) => LogDetailsScreen( - log: log, - dialog: false, - ) - )); - } - setState(() => selectedLog = log); - } - ); - } - else { - return null; - } - } - ), - if (logsProvider.logsData!.data.isEmpty) SliverFillRemaining( - child: Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - AppLocalizations.of(context)!.noLogsDisplay, - style: TextStyle( - fontSize: 24, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - if (logsProvider.logsOlderThan != null) Padding( - padding: const EdgeInsets.only( - top: 30, - left: 20, - right: 20 - ), - child: Text( - AppLocalizations.of(context)!.noLogsThatOld, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 16, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - ), - ] - ), - ), - ) - ], - ), ), - ), - ) - ); - - case LoadStatus.error: - return SafeArea( - top: false, - bottom: false, - child: Builder( - builder: (context) => CustomScrollView( - slivers: [ - SliverOverlapInjector( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - ), - SliverFillRemaining( - child: SizedBox( - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Icon( - Icons.error, - color: Colors.red, - size: 50, - ), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.logsNotLoaded, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ), - ) - ) - ], - ), - ) - ); - - default: - return const SizedBox(); - } - } - - Widget logsScreen() { - return Scaffold( - body: NestedScrollView( - headerSliverBuilder: (context, innerBoxIsScrolled) => [ - SliverOverlapAbsorber( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - sliver: SliverAppBar.large( - pinned: true, - floating: true, - centerTitle: false, - forceElevated: innerBoxIsScrolled, - title: Text(AppLocalizations.of(context)!.logs), - expandedHeight: logsProvider.appliedFilters.searchText != null || logsProvider.appliedFilters.selectedResultStatus != 'all' || logsProvider.appliedFilters.clients != null - ? 170 : null, - actions: [ - if (!(Platform.isAndroid || Platform.isIOS)) IconButton( - onPressed: () => logsProvider.fetchLogs(inOffset: 0), - icon: const Icon(Icons.refresh_rounded), - tooltip: AppLocalizations.of(context)!.refresh, - ), - logsProvider.loadStatus == LoadStatus.loaded - ? IconButton( - onPressed: openFilersModal, - icon: const Icon(Icons.filter_list_rounded), - tooltip: AppLocalizations.of(context)!.filters, + Expanded( + flex: 3, + child: _selectedLog != null + ? LogDetailsScreen( + log: _selectedLog!, + dialog: false, ) - : const SizedBox(), - if (statusProvider.serverStatus != null) IconButton( - tooltip: AppLocalizations.of(context)!.settings, - onPressed: () => { - if (width > 700 || !(Platform.isAndroid || Platform.isIOS)) { - showDialog( - context: context, - builder: (context) => LogsConfigModal( - onConfirm: updateConfig, - onClear: clearQueries, - dialog: true, - serverVersion: statusProvider.serverStatus!.serverVersion, - ), - barrierDismissible: false - ) - } - else { - showModalBottomSheet( - context: context, - builder: (context) => LogsConfigModal( - onConfirm: updateConfig, - onClear: clearQueries, - dialog: false, - serverVersion: statusProvider.serverStatus!.serverVersion, - ), - backgroundColor: Colors.transparent, - isScrollControlled: true - ) - } - }, - icon: const Icon(Icons.settings) - ), - const SizedBox(width: 5), - ], - bottom: logsProvider.appliedFilters.searchText != null || logsProvider.appliedFilters.selectedResultStatus != 'all' || logsProvider.appliedFilters.clients != null - ? PreferredSize( - preferredSize: const Size(double.maxFinite, 70), - child: Container( - height: 50, - width: double.maxFinite, - padding: const EdgeInsets.only(bottom: 10), - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: showDivider == true - ? Theme.of(context).colorScheme.onSurface.withOpacity(0.1) - : Colors.transparent, - ) - ) - ), - child: ListView( - scrollDirection: Axis.horizontal, - children: [ - if (logsProvider.appliedFilters.searchText != null) ...[ - const SizedBox(width: 15), - Chip( - avatar: const Icon( - Icons.search_rounded, - ), - label: Row( - children: [ - Text( - logsProvider.appliedFilters.searchText!, - ), - ], - ), - deleteIcon: const Icon( - Icons.clear, - size: 18, - ), - onDeleted: () { - logsProvider.setAppliedFilters( - AppliedFiters( - selectedResultStatus: logsProvider.appliedFilters.selectedResultStatus, - searchText: null, - clients: logsProvider.appliedFilters.clients - ) - ); - logsProvider.setSearchText(null); - logsProvider.fetchLogs( - inOffset: 0, - searchText: '' - ); - }, - ), - ], - if (logsProvider.appliedFilters.selectedResultStatus != 'all') ...[ - const SizedBox(width: 15), - Chip( - avatar: const Icon( - Icons.shield_rounded, - ), - label: Row( - children: [ - Text( - translatedString[logsProvider.appliedFilters.selectedResultStatus]!, - ), - ], - ), - deleteIcon: const Icon( - Icons.clear, - size: 18, - ), - onDeleted: () { - logsProvider.setAppliedFilters( - AppliedFiters( - selectedResultStatus: 'all', - searchText: logsProvider.appliedFilters.searchText, - clients: logsProvider.appliedFilters.clients - ) - ); - logsProvider.setSelectedResultStatus('all'); - logsProvider.fetchLogs( - inOffset: 0, - responseStatus: 'all' - ); - }, - ), - ], - if (logsProvider.appliedFilters.clients != null) ...[ - const SizedBox(width: 15), - Chip( - avatar: const Icon( - Icons.smartphone_rounded, - ), - label: Row( - children: [ - Text( - logsProvider.appliedFilters.clients!.length == 1 - ? logsProvider.appliedFilters.clients![0] - : "${logsProvider.appliedFilters.clients!.length} ${AppLocalizations.of(context)!.clients}", - ), - ], - ), - deleteIcon: const Icon( - Icons.clear, - size: 18, - ), - onDeleted: () { - logsProvider.setAppliedFilters( - AppliedFiters( - selectedResultStatus: logsProvider.appliedFilters.selectedResultStatus, - searchText: logsProvider.appliedFilters.searchText, - clients: null - ) - ); - logsProvider.setSelectedClients(null); - logsProvider.fetchLogs( - inOffset: 0, - responseStatus: logsProvider.appliedFilters.selectedResultStatus - ); - }, - ), - ], - const SizedBox(width: 15), - ], - ), - ) - ) - : null, - ), - ) - ], - body: generateBody() - ), - ); - } - - if (width > 1100) { - return Material( - color: Colors.transparent, - child: Row( - children: [ - Expanded( - flex: 1, - child: logsScreen() + : const SizedBox() + ) + ], ), - Expanded( - flex: 2, - child: selectedLog != null - ? LogDetailsScreen( - log: selectedLog!, - dialog: false, - ) - : const SizedBox() - ) - ], - ), - ); - } - else { - return logsScreen(); - } + ); + } + else { + return LogsListWidget( + twoColumns: false, + selectedLog: _selectedLog, + onLogSelected: (log) => setState(() => _selectedLog = log), + ); + } + }, + ); } } \ No newline at end of file diff --git a/lib/screens/logs/logs_list.dart b/lib/screens/logs/logs_list.dart new file mode 100644 index 0000000..9ca811d --- /dev/null +++ b/lib/screens/logs/logs_list.dart @@ -0,0 +1,566 @@ +// ignore_for_file: use_build_context_synchronously + +import 'dart:io'; + +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/logs/log_details_screen.dart'; +import 'package:adguard_home_manager/screens/logs/log_tile.dart'; +import 'package:adguard_home_manager/screens/logs/logs_config_modal.dart'; +import 'package:adguard_home_manager/screens/logs/logs_filters_modal.dart'; + +import 'package:adguard_home_manager/classes/process_modal.dart'; +import 'package:adguard_home_manager/constants/enums.dart'; +import 'package:adguard_home_manager/functions/compare_versions.dart'; +import 'package:adguard_home_manager/functions/desktop_mode.dart'; +import 'package:adguard_home_manager/functions/snackbar.dart'; +import 'package:adguard_home_manager/models/applied_filters.dart'; +import 'package:adguard_home_manager/models/logs.dart'; +import 'package:adguard_home_manager/providers/app_config_provider.dart'; +import 'package:adguard_home_manager/providers/clients_provider.dart'; +import 'package:adguard_home_manager/providers/logs_provider.dart'; +import 'package:adguard_home_manager/providers/servers_provider.dart'; +import 'package:adguard_home_manager/providers/status_provider.dart'; + +class LogsListWidget extends StatefulWidget { + final Log? selectedLog; + final bool twoColumns; + final void Function(Log) onLogSelected; + + const LogsListWidget({ + Key? key, + required this.twoColumns, + required this.selectedLog, + required this.onLogSelected, + }) : super(key: key); + + @override + State createState() => _LogsListWidgetState(); +} + +class _LogsListWidgetState extends State { + bool showDivider = true; + + void fetchFilteringRules() async { + final appConfigProvider = Provider.of(context, listen: false); + final statusProvider = Provider.of(context, listen: false); + + final result = await statusProvider.getFilteringRules(); + if (mounted && result == false) { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.couldntGetFilteringStatus, + color: Colors.red + ); + } + } + + Future fetchClients() async { + final clientsProvider = Provider.of(context, listen: false); + final appConfigProvider = Provider.of(context, listen: false); + + final result = await clientsProvider.fetchClients(); + if (mounted && result == false) { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.couldntGetFilteringStatus, + color: Colors.red + ); + } + } + + bool scrollListener(ScrollUpdateNotification scrollNotification) { + final logsProvider = Provider.of(context, listen: false); + + if (scrollNotification.metrics.extentAfter < 500 && logsProvider.isLoadingMore == false) { + logsProvider.fetchLogs(loadingMore: true); + } + if (scrollNotification.metrics.pixels > 0) { + setState(() => showDivider = false); + } + else { + setState(() => showDivider = true); + } + + return false; + } + + @override + void initState() { + final logsProvider = Provider.of(context, listen: false); + + logsProvider.fetchLogs(inOffset: 0); + fetchFilteringRules(); + fetchClients(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + final serversProvider = Provider.of(context); + final statusProvider = Provider.of(context); + final appConfigProvider = Provider.of(context); + final logsProvider = Provider.of(context); + + final width = MediaQuery.of(context).size.width; + + void updateConfig(Map data) async { + ProcessModal processModal = ProcessModal(context: context); + processModal.open(AppLocalizations.of(context)!.updatingSettings); + + final result = serverVersionIsAhead( + currentVersion: statusProvider.serverStatus!.serverVersion, + referenceVersion: 'v0.107.28', + referenceVersionBeta: 'v0.108.0-b.33' + ) == true + ? await serversProvider.apiClient!.updateQueryLogParameters(data: data) + : await serversProvider.apiClient!.updateQueryLogParametersLegacy(data: data); + + processModal.close(); + + if (result['result'] == 'success') { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.logsConfigUpdated, + color: Colors.green + ); + } + else { + appConfigProvider.addLog(result['log']); + + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.logsConfigNotUpdated, + color: Colors.red + ); + } + } + + void clearQueries() async { + ProcessModal processModal = ProcessModal(context: context); + processModal.open(AppLocalizations.of(context)!.updatingSettings); + + final result = await serversProvider.apiClient!.clearLogs(); + + processModal.close(); + + if (result['result'] == 'success') { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.logsCleared, + color: Colors.green + ); + } + else { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.logsNotCleared, + color: Colors.red + ); + } + } + + + void openFilersModal() { + if (width > 700 || !(Platform.isAndroid || Platform.isIOS)) { + showDialog( + context: context, + builder: (context) => const LogsFiltersModal( + dialog: true, + ), + barrierDismissible: false + ); + } + else { + showModalBottomSheet( + context: context, + builder: (context) => const LogsFiltersModal( + dialog: false, + ), + backgroundColor: Colors.transparent, + isScrollControlled: true + ); + } + } + + final Map translatedString = { + "all": AppLocalizations.of(context)!.all, + "filtered": AppLocalizations.of(context)!.filtered, + "processed": AppLocalizations.of(context)!.processedRow, + "whitelisted": AppLocalizations.of(context)!.processedWhitelistRow, + "blocked": AppLocalizations.of(context)!.blocked, + "blocked_safebrowsing": AppLocalizations.of(context)!.blockedSafeBrowsingRow, + "blocked_parental": AppLocalizations.of(context)!.blockedParentalRow, + "safe_search": AppLocalizations.of(context)!.safeSearch, + }; + + return Scaffold( + body: NestedScrollView( + headerSliverBuilder: (context, innerBoxIsScrolled) => [ + SliverOverlapAbsorber( + handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), + sliver: SliverAppBar.large( + pinned: true, + floating: true, + centerTitle: false, + forceElevated: innerBoxIsScrolled, + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, + title: Text(AppLocalizations.of(context)!.logs), + expandedHeight: logsProvider.appliedFilters.searchText != null || logsProvider.appliedFilters.selectedResultStatus != 'all' || logsProvider.appliedFilters.clients != null + ? 170 : null, + actions: [ + if (!(Platform.isAndroid || Platform.isIOS)) IconButton( + onPressed: () => logsProvider.fetchLogs(inOffset: 0), + icon: const Icon(Icons.refresh_rounded), + tooltip: AppLocalizations.of(context)!.refresh, + ), + logsProvider.loadStatus == LoadStatus.loaded + ? IconButton( + onPressed: openFilersModal, + icon: const Icon(Icons.filter_list_rounded), + tooltip: AppLocalizations.of(context)!.filters, + ) + : const SizedBox(), + if (statusProvider.serverStatus != null) IconButton( + tooltip: AppLocalizations.of(context)!.settings, + onPressed: () => { + if (width > 700 || !(Platform.isAndroid || Platform.isIOS)) { + showDialog( + context: context, + builder: (context) => LogsConfigModal( + onConfirm: updateConfig, + onClear: clearQueries, + dialog: true, + serverVersion: statusProvider.serverStatus!.serverVersion, + ), + barrierDismissible: false + ) + } + else { + showModalBottomSheet( + context: context, + builder: (context) => LogsConfigModal( + onConfirm: updateConfig, + onClear: clearQueries, + dialog: false, + serverVersion: statusProvider.serverStatus!.serverVersion, + ), + backgroundColor: Colors.transparent, + isScrollControlled: true + ) + } + }, + icon: const Icon(Icons.settings) + ), + const SizedBox(width: 5), + ], + bottom: logsProvider.appliedFilters.searchText != null || logsProvider.appliedFilters.selectedResultStatus != 'all' || logsProvider.appliedFilters.clients != null + ? PreferredSize( + preferredSize: const Size(double.maxFinite, 70), + child: Container( + height: 50, + width: double.maxFinite, + padding: const EdgeInsets.only(bottom: 10), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: showDivider == true + ? Theme.of(context).colorScheme.onSurface.withOpacity(0.1) + : Colors.transparent, + ) + ) + ), + child: ListView( + scrollDirection: Axis.horizontal, + children: [ + if (logsProvider.appliedFilters.searchText != null) ...[ + const SizedBox(width: 15), + Chip( + avatar: const Icon( + Icons.search_rounded, + ), + label: Row( + children: [ + Text( + logsProvider.appliedFilters.searchText!, + ), + ], + ), + deleteIcon: const Icon( + Icons.clear, + size: 18, + ), + onDeleted: () { + logsProvider.setAppliedFilters( + AppliedFiters( + selectedResultStatus: logsProvider.appliedFilters.selectedResultStatus, + searchText: null, + clients: logsProvider.appliedFilters.clients + ) + ); + logsProvider.setSearchText(null); + logsProvider.fetchLogs( + inOffset: 0, + searchText: '' + ); + }, + ), + ], + if (logsProvider.appliedFilters.selectedResultStatus != 'all') ...[ + const SizedBox(width: 15), + Chip( + avatar: const Icon( + Icons.shield_rounded, + ), + label: Row( + children: [ + Text( + translatedString[logsProvider.appliedFilters.selectedResultStatus]!, + ), + ], + ), + deleteIcon: const Icon( + Icons.clear, + size: 18, + ), + onDeleted: () { + logsProvider.setAppliedFilters( + AppliedFiters( + selectedResultStatus: 'all', + searchText: logsProvider.appliedFilters.searchText, + clients: logsProvider.appliedFilters.clients + ) + ); + logsProvider.setSelectedResultStatus('all'); + logsProvider.fetchLogs( + inOffset: 0, + responseStatus: 'all' + ); + }, + ), + ], + if (logsProvider.appliedFilters.clients != null) ...[ + const SizedBox(width: 15), + Chip( + avatar: const Icon( + Icons.smartphone_rounded, + ), + label: Row( + children: [ + Text( + logsProvider.appliedFilters.clients!.length == 1 + ? logsProvider.appliedFilters.clients![0] + : "${logsProvider.appliedFilters.clients!.length} ${AppLocalizations.of(context)!.clients}", + ), + ], + ), + deleteIcon: const Icon( + Icons.clear, + size: 18, + ), + onDeleted: () { + logsProvider.setAppliedFilters( + AppliedFiters( + selectedResultStatus: logsProvider.appliedFilters.selectedResultStatus, + searchText: logsProvider.appliedFilters.searchText, + clients: null + ) + ); + logsProvider.setSelectedClients(null); + logsProvider.fetchLogs( + inOffset: 0, + responseStatus: logsProvider.appliedFilters.selectedResultStatus + ); + }, + ), + ], + const SizedBox(width: 15), + ], + ), + ) + ) + : null, + ), + ) + ], + body: Builder( + builder: (context) { + switch (logsProvider.loadStatus) { + case LoadStatus.loading: + return SafeArea( + top: false, + bottom: false, + child: Builder( + builder: (context) => CustomScrollView( + slivers: [ + SliverOverlapInjector( + handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), + ), + SliverFillRemaining( + child: SizedBox( + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const CircularProgressIndicator(), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.loadingLogs, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ), + ) + ) + ], + ), + ) + ); + + case LoadStatus.loaded: + return SafeArea( + top: false, + bottom: false, + child: Builder( + builder: (context) => RefreshIndicator( + onRefresh: () async { + await logsProvider.fetchLogs(inOffset: 0); + }, + displacement: 95, + child: NotificationListener( + onNotification: scrollListener, + child: CustomScrollView( + slivers: [ + SliverOverlapInjector( + handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), + ), + if (logsProvider.logsData!.data.isNotEmpty) SliverList.builder( + itemCount: logsProvider.isLoadingMore + ? logsProvider.logsData!.data.length + 1 + : logsProvider.logsData!.data.length, + itemBuilder: (context, index) { + if (logsProvider.isLoadingMore == true && index == logsProvider.logsData!.data.length) { + return const Padding( + padding: EdgeInsets.symmetric(vertical: 20), + child: Center( + child: CircularProgressIndicator(), + ), + ); + } + else if (logsProvider.logsData!.data[index].question.name != null) { + return LogTile( + log: logsProvider.logsData!.data[index], + index: index, + length: logsProvider.logsData!.data.length, + isLogSelected: widget.selectedLog != null && widget.selectedLog == logsProvider.logsData!.data[index], + onLogTap: (log) { + if (!widget.twoColumns) { + Navigator.push(context, MaterialPageRoute( + builder: (context) => LogDetailsScreen( + log: log, + dialog: false, + ) + )); + } + widget.onLogSelected(log); + }, + twoColumns: widget.twoColumns, + ); + } + else { + return null; + } + } + ), + if (logsProvider.logsData!.data.isEmpty) SliverFillRemaining( + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + AppLocalizations.of(context)!.noLogsDisplay, + style: TextStyle( + fontSize: 24, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + if (logsProvider.logsOlderThan != null) Padding( + padding: const EdgeInsets.only( + top: 30, + left: 20, + right: 20 + ), + child: Text( + AppLocalizations.of(context)!.noLogsThatOld, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + ), + ] + ), + ), + ) + ], + ), + ), + ), + ) + ); + + case LoadStatus.error: + return SafeArea( + top: false, + bottom: false, + child: Builder( + builder: (context) => CustomScrollView( + slivers: [ + SliverOverlapInjector( + handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), + ), + SliverFillRemaining( + child: SizedBox( + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Icon( + Icons.error, + color: Colors.red, + size: 50, + ), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.logsNotLoaded, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ), + ) + ) + ], + ), + ) + ); + + default: + return const SizedBox(); + } + }, + ) + ), + ); + } +} \ No newline at end of file diff --git a/lib/screens/settings/settings.dart b/lib/screens/settings/settings.dart index 2f9a24d..87d23ef 100644 --- a/lib/screens/settings/settings.dart +++ b/lib/screens/settings/settings.dart @@ -23,6 +23,7 @@ import 'package:adguard_home_manager/widgets/custom_settings_tile.dart'; import 'package:adguard_home_manager/widgets/section_label.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/constants/strings.dart'; import 'package:adguard_home_manager/functions/open_url.dart'; import 'package:adguard_home_manager/functions/compare_versions.dart'; @@ -36,35 +37,46 @@ class Settings extends StatelessWidget { @override Widget build(BuildContext context) { - final width = MediaQuery.of(context).size.width; - - if (width > 900) { - return SplitView.material( - hideDivider: true, - flexWidth: const FlexWidth(mainViewFlexWidth: 1, secondaryViewFlexWidth: 2), - placeholder: Center( - child: Padding( - padding: const EdgeInsets.all(24), - child: Text( - AppLocalizations.of(context)!.selectOptionLeftColumn, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 24, - color: Theme.of(context).colorScheme.onSurfaceVariant + return LayoutBuilder( + builder: (context, constraints) { + if (constraints.maxWidth > 900) { + return SplitView.material( + hideDivider: true, + flexWidth: const FlexWidth(mainViewFlexWidth: 1, secondaryViewFlexWidth: 2), + placeholder: Center( + child: Padding( + padding: const EdgeInsets.all(24), + child: Text( + AppLocalizations.of(context)!.selectOptionLeftColumn, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 24, + color: Theme.of(context).colorScheme.onSurfaceVariant + ), + ), ), ), - ), - ), - child: const SettingsWidget(), - ); - } - else { - return const SettingsWidget(); - } + child: const SettingsWidget( + twoColumns: true, + ), + ); + } + else { + return const SettingsWidget( + twoColumns: false, + ); + } + }, + ); } } class SettingsWidget extends StatelessWidget { - const SettingsWidget({Key? key}) : super(key: key); + final bool twoColumns; + + const SettingsWidget({ + Key? key, + required this.twoColumns, + }) : super(key: key); @override Widget build(BuildContext context) { @@ -74,7 +86,7 @@ class SettingsWidget extends StatelessWidget { final width = MediaQuery.of(context).size.width; - if (width <= 900 && appConfigProvider.selectedSettingsScreen != null) { + if (!twoColumns && appConfigProvider.selectedSettingsScreen != null) { appConfigProvider.setSelectedSettingsScreen(screen: null); } @@ -86,7 +98,7 @@ class SettingsWidget extends StatelessWidget { required Widget screenToNavigate, required int thisItem }) { - if (width > 900) { + if (twoColumns) { return CustomSettingsTile( title: title, subtitle: subtitle, @@ -125,6 +137,7 @@ class SettingsWidget extends StatelessWidget { floating: true, centerTitle: false, forceElevated: innerBoxIsScrolled, + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, title: Text(AppLocalizations.of(context)!.settings), ) ) From 91d4d2c87a911bc1fa9f72826296ef11fe96cada Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 29 Oct 2023 02:47:14 +0100 Subject: [PATCH 3/6] Removed color appbar desktop mode --- .../clients/client/logs_list_client.dart | 240 ++--- lib/screens/filters/add_button.dart | 2 + lib/screens/filters/filters.dart | 2 + lib/screens/filters/list_details_screen.dart | 1 + lib/screens/home/fab.dart | 1 + lib/screens/logs/logs_filters_modal.dart | 2 + lib/screens/logs/logs_list.dart | 4 +- .../access_settings/access_settings.dart | 4 + .../access_settings/clients_list.dart | 1 + lib/screens/settings/advanced_setings.dart | 4 + .../settings/customization/customization.dart | 6 +- lib/screens/settings/dhcp/dhcp.dart | 980 +++++++++--------- lib/screens/settings/dhcp/dhcp_leases.dart | 3 + lib/screens/settings/dns/bootstrap_dns.dart | 4 + lib/screens/settings/dns/cache_config.dart | 4 + lib/screens/settings/dns/dns.dart | 202 ++-- .../settings/dns/dns_server_settings.dart | 3 + .../settings/dns/private_reverse_servers.dart | 4 + lib/screens/settings/dns/upstream_dns.dart | 4 + .../settings/dns_rewrites/dns_rewrites.dart | 364 +++---- .../settings/encryption/encryption.dart | 744 ++++++------- .../general_settings/general_settings.dart | 4 + .../reorderable_top_items_home.dart | 4 + .../settings/safe_search_settings.dart | 322 +++--- .../settings/server_info/server_info.dart | 221 ++-- lib/screens/settings/settings.dart | 4 +- 26 files changed, 1607 insertions(+), 1527 deletions(-) diff --git a/lib/screens/clients/client/logs_list_client.dart b/lib/screens/clients/client/logs_list_client.dart index eb724fd..5d8f6ed 100644 --- a/lib/screens/clients/client/logs_list_client.dart +++ b/lib/screens/clients/client/logs_list_client.dart @@ -8,6 +8,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/screens/logs/log_tile.dart'; import 'package:adguard_home_manager/screens/logs/log_details_screen.dart'; +import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:adguard_home_manager/models/logs.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/providers/servers_provider.dart'; @@ -133,127 +134,13 @@ class _LogsListClientState extends State { setState(() => previousIp = widget.ip); } - Widget status() { - switch (loadStatus) { - case 0: - return SizedBox( - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const CircularProgressIndicator(), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.loadingLogs, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ), - ); - - case 1: - if (logsData!.data.isNotEmpty) { - return RefreshIndicator( - onRefresh: fetchLogs, - child: ListView.builder( - controller: scrollController, - padding: const EdgeInsets.only(top: 0), - itemCount: isLoadingMore == true - ? logsData!.data.length+1 - : logsData!.data.length, - itemBuilder: (context, index) { - if (isLoadingMore == true && index == logsData!.data.length) { - return const Padding( - padding: EdgeInsets.symmetric(vertical: 20), - child: Center( - child: CircularProgressIndicator(), - ), - ); - } - else { - return LogTile( - log: logsData!.data[index], - index: index, - length: logsData!.data.length, - useAlwaysNormalTile: true, - onLogTap: (log) => { - if (width > 700) { - showDialog( - context: context, - builder: (context) => LogDetailsScreen( - log: log, - dialog: true - ) - ) - } - else { - Navigator.push(context, MaterialPageRoute( - builder: (context) => LogDetailsScreen( - log: log, - dialog: false - ) - )) - } - }, - twoColumns: widget.splitView, - ); - } - } - ), - ); - } - else { - return Center( - child: Text( - AppLocalizations.of(context)!.noLogsDisplay, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - ); - } - - case 2: - return SizedBox( - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Icon( - Icons.error, - color: Colors.red, - size: 50, - ), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.logsNotLoaded, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ), - ); - - default: - return const SizedBox(); - } - } - return Scaffold( appBar: AppBar( title: Text(widget.name != null && widget.name != '' ? widget.name! : widget.ip), centerTitle: true, + surfaceTintColor: isDesktop(MediaQuery.of(context).size.width) + ? Colors.transparent + : null, actions: [ if (!(Platform.isAndroid || Platform.isIOS)) ...[ IconButton( @@ -265,7 +152,124 @@ class _LogsListClientState extends State { ] ], ), - body: status(), + body: Builder( + builder: (context) { + switch (loadStatus) { + case 0: + return SizedBox( + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const CircularProgressIndicator(), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.loadingLogs, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ), + ); + + case 1: + if (logsData!.data.isNotEmpty) { + return RefreshIndicator( + onRefresh: fetchLogs, + child: ListView.builder( + controller: scrollController, + padding: const EdgeInsets.only(top: 0), + itemCount: isLoadingMore == true + ? logsData!.data.length+1 + : logsData!.data.length, + itemBuilder: (context, index) { + if (isLoadingMore == true && index == logsData!.data.length) { + return const Padding( + padding: EdgeInsets.symmetric(vertical: 20), + child: Center( + child: CircularProgressIndicator(), + ), + ); + } + else { + return LogTile( + log: logsData!.data[index], + index: index, + length: logsData!.data.length, + useAlwaysNormalTile: true, + onLogTap: (log) => { + if (width > 700) { + showDialog( + context: context, + builder: (context) => LogDetailsScreen( + log: log, + dialog: true + ) + ) + } + else { + Navigator.push(context, MaterialPageRoute( + builder: (context) => LogDetailsScreen( + log: log, + dialog: false + ) + )) + } + }, + twoColumns: widget.splitView, + ); + } + } + ), + ); + } + else { + return Center( + child: Text( + AppLocalizations.of(context)!.noLogsDisplay, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + ); + } + + case 2: + return SizedBox( + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Icon( + Icons.error, + color: Colors.red, + size: 50, + ), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.logsNotLoaded, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ), + ); + + default: + return const SizedBox(); + } + }, + ) ); } } \ No newline at end of file diff --git a/lib/screens/filters/add_button.dart b/lib/screens/filters/add_button.dart index 195d7af..7afccf0 100644 --- a/lib/screens/filters/add_button.dart +++ b/lib/screens/filters/add_button.dart @@ -2,6 +2,7 @@ import 'dart:io'; +import 'package:adguard_home_manager/routes/router_globals.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -134,6 +135,7 @@ class AddFiltersButton extends StatelessWidget { else { showModalBottomSheet( context: context, + useRootNavigator: true, builder: (ctx) => AddListModal( type: type, onConfirm: confirmAddList, diff --git a/lib/screens/filters/filters.dart b/lib/screens/filters/filters.dart index 4b94b1e..5975cf6 100644 --- a/lib/screens/filters/filters.dart +++ b/lib/screens/filters/filters.dart @@ -87,6 +87,7 @@ class _FiltersState extends State { else { showModalBottomSheet( context: context, + useRootNavigator: true, builder: (context) => const CheckHostModal( dialog: false, ), @@ -269,6 +270,7 @@ class _FiltersState extends State { else { showModalBottomSheet( context: context, + useRootNavigator: true, builder: (context) => UpdateIntervalListsModal( interval: filteringProvider.filtering!.interval, onChange: setUpdateFrequency, diff --git a/lib/screens/filters/list_details_screen.dart b/lib/screens/filters/list_details_screen.dart index e98c759..236ee75 100644 --- a/lib/screens/filters/list_details_screen.dart +++ b/lib/screens/filters/list_details_screen.dart @@ -219,6 +219,7 @@ class _ListDetailsScreenState extends State { else { showModalBottomSheet( context: context, + useRootNavigator: true, builder: (ctx) => AddListModal( list: list, type: widget.type, diff --git a/lib/screens/home/fab.dart b/lib/screens/home/fab.dart index 0e7db97..d0701cc 100644 --- a/lib/screens/home/fab.dart +++ b/lib/screens/home/fab.dart @@ -28,6 +28,7 @@ class HomeFab extends StatelessWidget { showModalBottomSheet( context: context, isScrollControlled: true, + useRootNavigator: true, builder: (context) => const ManagementModal( dialog: false, ), diff --git a/lib/screens/logs/logs_filters_modal.dart b/lib/screens/logs/logs_filters_modal.dart index 5251d9a..8f1d7f0 100644 --- a/lib/screens/logs/logs_filters_modal.dart +++ b/lib/screens/logs/logs_filters_modal.dart @@ -89,6 +89,7 @@ class _LogsFiltersModalWidgetState extends State { else { showModalBottomSheet( context: context, + useRootNavigator: true, builder: (context) => FilterStatusModal( value: logsProvider.selectedResultStatus, dialog: false, @@ -113,6 +114,7 @@ class _LogsFiltersModalWidgetState extends State { else { showModalBottomSheet( context: context, + useRootNavigator: true, builder: (context) => ClientsModal( value: logsProvider.selectedClients, dialog: false, diff --git a/lib/screens/logs/logs_list.dart b/lib/screens/logs/logs_list.dart index 9ca811d..6fd80e6 100644 --- a/lib/screens/logs/logs_list.dart +++ b/lib/screens/logs/logs_list.dart @@ -176,6 +176,7 @@ class _LogsListWidgetState extends State { else { showModalBottomSheet( context: context, + useRootNavigator: true, builder: (context) => const LogsFiltersModal( dialog: false, ), @@ -240,7 +241,8 @@ class _LogsListWidgetState extends State { } else { showModalBottomSheet( - context: context, + context: context, + useRootNavigator: true, builder: (context) => LogsConfigModal( onConfirm: updateConfig, onClear: clearQueries, diff --git a/lib/screens/settings/access_settings/access_settings.dart b/lib/screens/settings/access_settings/access_settings.dart index beb34fd..ca9cf12 100644 --- a/lib/screens/settings/access_settings/access_settings.dart +++ b/lib/screens/settings/access_settings/access_settings.dart @@ -6,6 +6,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/screens/settings/access_settings/clients_list.dart'; +import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:adguard_home_manager/constants/enums.dart'; import 'package:adguard_home_manager/providers/clients_provider.dart'; @@ -35,6 +36,8 @@ class _AccessSettingsState extends State with TickerProviderStat Widget build(BuildContext context) { final clientsProvider = Provider.of(context); + final width = MediaQuery.of(context).size.width; + Widget body() { return TabBarView( controller: tabController, @@ -119,6 +122,7 @@ class _AccessSettingsState extends State with TickerProviderStat floating: true, centerTitle: false, forceElevated: innerBoxIsScrolled, + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, bottom: tabBar() ), ), diff --git a/lib/screens/settings/access_settings/clients_list.dart b/lib/screens/settings/access_settings/clients_list.dart index 31c01a7..6f87c7c 100644 --- a/lib/screens/settings/access_settings/clients_list.dart +++ b/lib/screens/settings/access_settings/clients_list.dart @@ -361,6 +361,7 @@ class _ClientsListState extends State { else { showModalBottomSheet( context: context, + useRootNavigator: true, builder: (context) => AddClientModal( type: widget.type, onConfirm: confirmAddItem, diff --git a/lib/screens/settings/advanced_setings.dart b/lib/screens/settings/advanced_setings.dart index 66ff49b..75572c1 100644 --- a/lib/screens/settings/advanced_setings.dart +++ b/lib/screens/settings/advanced_setings.dart @@ -6,6 +6,7 @@ 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/providers/app_config_provider.dart'; @@ -16,6 +17,8 @@ class AdvancedSettings extends StatelessWidget { Widget build(BuildContext context) { final appConfigProvider = Provider.of(context); + final width = MediaQuery.of(context).size.width; + Future updateSettings({ required bool newStatus, required Future Function(bool) function @@ -40,6 +43,7 @@ class AdvancedSettings extends StatelessWidget { return Scaffold( appBar: AppBar( title: Text(AppLocalizations.of(context)!.advancedSettings), + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, ), body: ListView( children: [ diff --git a/lib/screens/settings/customization/customization.dart b/lib/screens/settings/customization/customization.dart index 98a7121..6ddd5e4 100644 --- a/lib/screens/settings/customization/customization.dart +++ b/lib/screens/settings/customization/customization.dart @@ -1,4 +1,3 @@ -import 'package:adguard_home_manager/functions/generate_color_translation.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -9,6 +8,8 @@ import 'package:adguard_home_manager/screens/settings/customization/theme_mode_b import 'package:adguard_home_manager/widgets/custom_switch_list_tile.dart'; import 'package:adguard_home_manager/widgets/section_label.dart'; +import 'package:adguard_home_manager/functions/generate_color_translation.dart'; +import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/constants/colors.dart'; @@ -56,10 +57,13 @@ class _CustomizationWidgetState extends State { Widget build(BuildContext context) { final appConfigProvider = Provider.of(context); + final width = MediaQuery.of(context).size.width; + return Scaffold( appBar: AppBar( title: Text(AppLocalizations.of(context)!.customization), centerTitle: false, + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, ), body: ListView( children: [ diff --git a/lib/screens/settings/dhcp/dhcp.dart b/lib/screens/settings/dhcp/dhcp.dart index 724a33a..aa44414 100644 --- a/lib/screens/settings/dhcp/dhcp.dart +++ b/lib/screens/settings/dhcp/dhcp.dart @@ -12,6 +12,7 @@ import 'package:adguard_home_manager/widgets/confirm_action_modal.dart'; import 'package:adguard_home_manager/screens/settings/dhcp/dhcp_leases.dart'; import 'package:adguard_home_manager/screens/settings/dhcp/select_interface_modal.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/dhcp_provider.dart'; @@ -337,6 +338,7 @@ class _DhcpScreenState extends State { else { showModalBottomSheet( context: context, + useRootNavigator: true, builder: (context) => SelectInterfaceModal( interfaces: dhcpProvider.dhcp!.networkInterfaces, onSelect: (i) => setState(() { @@ -353,498 +355,11 @@ class _DhcpScreenState extends State { }); } - Widget generateBody() { - switch (dhcpProvider.loadStatus) { - case LoadStatus.loading: - return SizedBox( - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const CircularProgressIndicator(), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.loadingDhcp, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ), - ); - - case LoadStatus.loaded: - if (selectedInterface != null) { - return SingleChildScrollView( - child: Wrap( - children: [ - Padding( - padding: const EdgeInsets.only( - top: 10, - left: 16, - right: 16 - ), - child: Material( - color: Theme.of(context).colorScheme.primary.withOpacity(0.1), - borderRadius: BorderRadius.circular(28), - child: InkWell( - onTap: selectedInterface != null - ? () => setState(() => enabled = !enabled) - : null, - borderRadius: BorderRadius.circular(28), - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 12 - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - AppLocalizations.of(context)!.enableDhcpServer, - style: TextStyle( - fontSize: 16, - color: Theme.of(context).colorScheme.onSurface - ), - ), - if (selectedInterface != null) ...[ - Text( - selectedInterface!.name, - style: TextStyle( - fontSize: 14, - color: Theme.of(context).listTileTheme.textColor - ), - ) - ] - ], - ), - Switch( - value: enabled, - onChanged: selectedInterface != null - ? (value) => setState(() => enabled = value) - : null, - ), - ], - ), - ), - ), - ), - ), - if (selectedInterface!.ipv4Addresses.isNotEmpty) ...[ - SectionLabel( - label: AppLocalizations.of(context)!.ipv4settings, - padding: const EdgeInsets.only( - top: 24, left: 16, right: 16, bottom: 8 - ) - ), - FractionallySizedBox( - widthFactor: width > 900 ? 0.5 : 1, - child: Padding( - padding: width > 900 - ? const EdgeInsets.only(top: 12, bottom: 12, left: 16, right: 8) - : const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - child: TextFormField( - controller: ipv4StartRangeController, - onChanged: (value) => validateIpV4(value, 'ipv4StartRangeError', AppLocalizations.of(context)!.ipNotValid), - decoration: InputDecoration( - prefixIcon: const Icon(Icons.skip_previous_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - errorText: ipv4StartRangeError, - labelText: AppLocalizations.of(context)!.startOfRange, - ), - keyboardType: TextInputType.number, - ), - ), - ), - FractionallySizedBox( - widthFactor: width > 900 ? 0.5 : 1, - child: Padding( - padding: width > 900 - ? const EdgeInsets.only(top: 12, bottom: 12, left: 8, right: 16) - : const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - child: TextFormField( - controller: ipv4EndRangeController, - onChanged: (value) => validateIpV4(value, 'ipv4EndRangeError', AppLocalizations.of(context)!.ipNotValid), - decoration: InputDecoration( - prefixIcon: const Icon(Icons.skip_next_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - errorText: ipv4EndRangeError, - labelText: AppLocalizations.of(context)!.endOfRange, - ), - keyboardType: TextInputType.number, - ), - ), - ), - FractionallySizedBox( - widthFactor: width > 900 ? 0.5 : 1, - child: Padding( - padding: width > 900 - ? const EdgeInsets.only(top: 12, bottom: 12, left: 16, right: 8) - : const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - child: TextFormField( - controller: ipv4SubnetMaskController, - onChanged: (value) => validateIpV4(value, 'ipv4SubnetMaskError', AppLocalizations.of(context)!.subnetMaskNotValid), - decoration: InputDecoration( - prefixIcon: const Icon(Icons.hub_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - errorText: ipv4SubnetMaskError, - labelText: AppLocalizations.of(context)!.subnetMask, - ), - keyboardType: TextInputType.number, - ), - ), - ), - FractionallySizedBox( - widthFactor: width > 900 ? 0.5 : 1, - child: Padding( - padding: width > 900 - ? const EdgeInsets.only(top: 12, bottom: 12, left: 8, right: 16) - : const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - child: TextFormField( - controller: ipv4GatewayController, - onChanged: (value) => validateIpV4(value, 'ipv4GatewayError', AppLocalizations.of(context)!.gatewayNotValid), - decoration: InputDecoration( - prefixIcon: const Icon(Icons.router_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - errorText: ipv4GatewayError, - labelText: AppLocalizations.of(context)!.gateway, - ), - keyboardType: TextInputType.number, - ), - ), - ), - FractionallySizedBox( - widthFactor: 1, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - child: TextFormField( - controller: ipv4LeaseTimeController, - onChanged: (value) { - if (int.tryParse(value).runtimeType == int) { - setState(() => ipv4LeaseTimeError = null); - } - else { - setState(() => ipv4LeaseTimeError = AppLocalizations.of(context)!.leaseTimeNotValid); - } - }, - decoration: InputDecoration( - prefixIcon: const Icon(Icons.timer), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - errorText: ipv4LeaseTimeError, - labelText: AppLocalizations.of(context)!.leaseTime, - ), - keyboardType: TextInputType.number, - ), - ), - ), - ], - if (selectedInterface!.ipv6Addresses.isNotEmpty) ...[ - SectionLabel( - label: AppLocalizations.of(context)!.ipv6settings, - padding: const EdgeInsets.all(16) - ), - FractionallySizedBox( - widthFactor: width > 900 ? 0.5 : 1, - child: Padding( - padding: width > 900 - ? const EdgeInsets.only(top: 8, bottom: 12, left: 16, right: 8) - : const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - child: TextFormField( - controller: ipv6StartRangeController, - onChanged: (value) => validateIpV4(value, 'ipv6StartRangeError', AppLocalizations.of(context)!.ipNotValid), - decoration: InputDecoration( - prefixIcon: const Icon(Icons.skip_next_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - errorText: ipv6StartRangeError, - labelText: AppLocalizations.of(context)!.startOfRange, - ), - keyboardType: TextInputType.number, - ), - ), - ), - FractionallySizedBox( - widthFactor: width > 900 ? 0.5 : 1, - child: Padding( - padding: width > 900 - ? const EdgeInsets.only(top: 8, bottom: 12, left: 8, right: 16) - : const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - child: TextFormField( - controller: ipv6EndRangeController, - onChanged: (value) => validateIpV4(value, 'ipv6EndRangeError', AppLocalizations.of(context)!.ipNotValid), - decoration: InputDecoration( - prefixIcon: const Icon(Icons.skip_previous_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - errorText: ipv6EndRangeError, - labelText: AppLocalizations.of(context)!.endOfRange, - ), - keyboardType: TextInputType.number, - ), - ), - ), - FractionallySizedBox( - widthFactor: 1, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - child: TextFormField( - controller: ipv6LeaseTimeController, - onChanged: (value) { - if (int.tryParse(value).runtimeType == int) { - setState(() => ipv6LeaseTimeError = null); - } - else { - setState(() => ipv6LeaseTimeError = AppLocalizations.of(context)!.leaseTimeNotValid); - } - }, - decoration: InputDecoration( - prefixIcon: const Icon(Icons.timer), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - errorText: ipv6LeaseTimeError, - labelText: AppLocalizations.of(context)!.leaseTime, - ), - keyboardType: TextInputType.number, - ), - ), - ), - ], - const SizedBox(height: 20), - SectionLabel( - label: AppLocalizations.of(context)!.dhcpLeases, - padding: const EdgeInsets.all(16), - ), - if (width <= 900) Material( - color: Colors.transparent, - child: InkWell( - onTap: () { - Navigator.push(context, MaterialPageRoute( - builder: (context) => DhcpLeases( - items: dhcpProvider.dhcp!.dhcpStatus.leases, - staticLeases: false, - ) - )); - }, - child: Container( - padding: const EdgeInsets.all(16), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - AppLocalizations.of(context)!.dhcpLeases, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 16, - color: Theme.of(context).colorScheme.onSurface, - ), - ), - Icon( - Icons.arrow_forward_rounded, - color: Theme.of(context).colorScheme.onSurface, - ) - ], - ), - ), - ), - ), - if (width <= 900) Material( - color: Colors.transparent, - child: InkWell( - onTap: () { - Navigator.push(context, MaterialPageRoute( - builder: (context) => DhcpLeases( - items: dhcpProvider.dhcp!.dhcpStatus.staticLeases, - staticLeases: true, - ) - )); - }, - child: Container( - padding: const EdgeInsets.all(16), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - AppLocalizations.of(context)!.dhcpStatic, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 16, - color: Theme.of(context).colorScheme.onSurface, - ), - ), - Icon( - Icons.arrow_forward_rounded, - color: Theme.of(context).colorScheme.onSurface, - ) - ], - ), - ), - ), - ), - if (width > 900) Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - ElevatedButton( - onPressed: () { - if (!(Platform.isAndroid || Platform.isIOS)) { - SplitView.of(context).push( - DhcpLeases( - items: dhcpProvider.dhcp!.dhcpStatus.leases, - staticLeases: false, - ) - ); - } - else { - Navigator.push(context, MaterialPageRoute( - builder: (context) => DhcpLeases( - items: dhcpProvider.dhcp!.dhcpStatus.leases, - staticLeases: false, - ) - )); - } - }, - child: Row( - children: [ - Text(AppLocalizations.of(context)!.dhcpLeases), - const SizedBox(width: 8), - const Icon(Icons.arrow_forward_rounded) - ], - ) - ), - ElevatedButton( - onPressed: () { - if (!(Platform.isAndroid || Platform.isIOS)) { - SplitView.of(context).push( - DhcpLeases( - items: dhcpProvider.dhcp!.dhcpStatus.staticLeases, - staticLeases: true, - ) - ); - } - else { - Navigator.push(context, MaterialPageRoute( - builder: (context) => DhcpLeases( - items: dhcpProvider.dhcp!.dhcpStatus.staticLeases, - staticLeases: true, - ) - )); - } - }, - child: Row( - children: [ - Text(AppLocalizations.of(context)!.dhcpStatic), - const SizedBox(width: 8), - const Icon(Icons.arrow_forward_rounded) - ], - ) - ), - ], - ), - const SizedBox(height: 10) - ], - ), - ); - } - else { - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Flexible( - child: Column( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Text( - AppLocalizations.of(context)!.neededSelectInterface, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurface.withOpacity(0.5) - ), - ), - ), - const SizedBox(height: 30), - ElevatedButton( - onPressed: selectInterface, - child: Text(AppLocalizations.of(context)!.selectInterface) - ), - ], - ), - ), - ], - ); - } - - case LoadStatus.error: - return SizedBox( - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Icon( - Icons.error, - color: Colors.red, - size: 50, - ), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.dhcpSettingsNotLoaded, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ), - ); - - default: - return const SizedBox(); - } - } - - return Scaffold( appBar: AppBar( title: Text(AppLocalizations.of(context)!.dhcpSettings), centerTitle: false, + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, actions: selectedInterface != null ? [ IconButton( onPressed: checkDataValid() == true @@ -890,7 +405,494 @@ class _DhcpScreenState extends State { const SizedBox(width: 10) ] : null, ), - body: generateBody(), + body: Builder( + builder: (context) { + switch (dhcpProvider.loadStatus) { + case LoadStatus.loading: + return SizedBox( + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const CircularProgressIndicator(), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.loadingDhcp, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ), + ); + + case LoadStatus.loaded: + if (selectedInterface != null) { + return SingleChildScrollView( + child: Wrap( + children: [ + Padding( + padding: const EdgeInsets.only( + top: 10, + left: 16, + right: 16 + ), + child: Material( + color: Theme.of(context).colorScheme.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(28), + child: InkWell( + onTap: selectedInterface != null + ? () => setState(() => enabled = !enabled) + : null, + borderRadius: BorderRadius.circular(28), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 12 + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + AppLocalizations.of(context)!.enableDhcpServer, + style: TextStyle( + fontSize: 16, + color: Theme.of(context).colorScheme.onSurface + ), + ), + if (selectedInterface != null) ...[ + Text( + selectedInterface!.name, + style: TextStyle( + fontSize: 14, + color: Theme.of(context).listTileTheme.textColor + ), + ) + ] + ], + ), + Switch( + value: enabled, + onChanged: selectedInterface != null + ? (value) => setState(() => enabled = value) + : null, + ), + ], + ), + ), + ), + ), + ), + if (selectedInterface!.ipv4Addresses.isNotEmpty) ...[ + SectionLabel( + label: AppLocalizations.of(context)!.ipv4settings, + padding: const EdgeInsets.only( + top: 24, left: 16, right: 16, bottom: 8 + ) + ), + FractionallySizedBox( + widthFactor: width > 900 ? 0.5 : 1, + child: Padding( + padding: width > 900 + ? const EdgeInsets.only(top: 12, bottom: 12, left: 16, right: 8) + : const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: TextFormField( + controller: ipv4StartRangeController, + onChanged: (value) => validateIpV4(value, 'ipv4StartRangeError', AppLocalizations.of(context)!.ipNotValid), + decoration: InputDecoration( + prefixIcon: const Icon(Icons.skip_previous_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + errorText: ipv4StartRangeError, + labelText: AppLocalizations.of(context)!.startOfRange, + ), + keyboardType: TextInputType.number, + ), + ), + ), + FractionallySizedBox( + widthFactor: width > 900 ? 0.5 : 1, + child: Padding( + padding: width > 900 + ? const EdgeInsets.only(top: 12, bottom: 12, left: 8, right: 16) + : const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: TextFormField( + controller: ipv4EndRangeController, + onChanged: (value) => validateIpV4(value, 'ipv4EndRangeError', AppLocalizations.of(context)!.ipNotValid), + decoration: InputDecoration( + prefixIcon: const Icon(Icons.skip_next_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + errorText: ipv4EndRangeError, + labelText: AppLocalizations.of(context)!.endOfRange, + ), + keyboardType: TextInputType.number, + ), + ), + ), + FractionallySizedBox( + widthFactor: width > 900 ? 0.5 : 1, + child: Padding( + padding: width > 900 + ? const EdgeInsets.only(top: 12, bottom: 12, left: 16, right: 8) + : const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: TextFormField( + controller: ipv4SubnetMaskController, + onChanged: (value) => validateIpV4(value, 'ipv4SubnetMaskError', AppLocalizations.of(context)!.subnetMaskNotValid), + decoration: InputDecoration( + prefixIcon: const Icon(Icons.hub_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + errorText: ipv4SubnetMaskError, + labelText: AppLocalizations.of(context)!.subnetMask, + ), + keyboardType: TextInputType.number, + ), + ), + ), + FractionallySizedBox( + widthFactor: width > 900 ? 0.5 : 1, + child: Padding( + padding: width > 900 + ? const EdgeInsets.only(top: 12, bottom: 12, left: 8, right: 16) + : const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: TextFormField( + controller: ipv4GatewayController, + onChanged: (value) => validateIpV4(value, 'ipv4GatewayError', AppLocalizations.of(context)!.gatewayNotValid), + decoration: InputDecoration( + prefixIcon: const Icon(Icons.router_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + errorText: ipv4GatewayError, + labelText: AppLocalizations.of(context)!.gateway, + ), + keyboardType: TextInputType.number, + ), + ), + ), + FractionallySizedBox( + widthFactor: 1, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: TextFormField( + controller: ipv4LeaseTimeController, + onChanged: (value) { + if (int.tryParse(value).runtimeType == int) { + setState(() => ipv4LeaseTimeError = null); + } + else { + setState(() => ipv4LeaseTimeError = AppLocalizations.of(context)!.leaseTimeNotValid); + } + }, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.timer), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + errorText: ipv4LeaseTimeError, + labelText: AppLocalizations.of(context)!.leaseTime, + ), + keyboardType: TextInputType.number, + ), + ), + ), + ], + if (selectedInterface!.ipv6Addresses.isNotEmpty) ...[ + SectionLabel( + label: AppLocalizations.of(context)!.ipv6settings, + padding: const EdgeInsets.all(16) + ), + FractionallySizedBox( + widthFactor: width > 900 ? 0.5 : 1, + child: Padding( + padding: width > 900 + ? const EdgeInsets.only(top: 8, bottom: 12, left: 16, right: 8) + : const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: TextFormField( + controller: ipv6StartRangeController, + onChanged: (value) => validateIpV4(value, 'ipv6StartRangeError', AppLocalizations.of(context)!.ipNotValid), + decoration: InputDecoration( + prefixIcon: const Icon(Icons.skip_next_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + errorText: ipv6StartRangeError, + labelText: AppLocalizations.of(context)!.startOfRange, + ), + keyboardType: TextInputType.number, + ), + ), + ), + FractionallySizedBox( + widthFactor: width > 900 ? 0.5 : 1, + child: Padding( + padding: width > 900 + ? const EdgeInsets.only(top: 8, bottom: 12, left: 8, right: 16) + : const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: TextFormField( + controller: ipv6EndRangeController, + onChanged: (value) => validateIpV4(value, 'ipv6EndRangeError', AppLocalizations.of(context)!.ipNotValid), + decoration: InputDecoration( + prefixIcon: const Icon(Icons.skip_previous_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + errorText: ipv6EndRangeError, + labelText: AppLocalizations.of(context)!.endOfRange, + ), + keyboardType: TextInputType.number, + ), + ), + ), + FractionallySizedBox( + widthFactor: 1, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: TextFormField( + controller: ipv6LeaseTimeController, + onChanged: (value) { + if (int.tryParse(value).runtimeType == int) { + setState(() => ipv6LeaseTimeError = null); + } + else { + setState(() => ipv6LeaseTimeError = AppLocalizations.of(context)!.leaseTimeNotValid); + } + }, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.timer), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + errorText: ipv6LeaseTimeError, + labelText: AppLocalizations.of(context)!.leaseTime, + ), + keyboardType: TextInputType.number, + ), + ), + ), + ], + const SizedBox(height: 20), + SectionLabel( + label: AppLocalizations.of(context)!.dhcpLeases, + padding: const EdgeInsets.all(16), + ), + if (width <= 900) Material( + color: Colors.transparent, + child: InkWell( + onTap: () { + Navigator.push(context, MaterialPageRoute( + builder: (context) => DhcpLeases( + items: dhcpProvider.dhcp!.dhcpStatus.leases, + staticLeases: false, + ) + )); + }, + child: Container( + padding: const EdgeInsets.all(16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + AppLocalizations.of(context)!.dhcpLeases, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16, + color: Theme.of(context).colorScheme.onSurface, + ), + ), + Icon( + Icons.arrow_forward_rounded, + color: Theme.of(context).colorScheme.onSurface, + ) + ], + ), + ), + ), + ), + if (width <= 900) Material( + color: Colors.transparent, + child: InkWell( + onTap: () { + Navigator.push(context, MaterialPageRoute( + builder: (context) => DhcpLeases( + items: dhcpProvider.dhcp!.dhcpStatus.staticLeases, + staticLeases: true, + ) + )); + }, + child: Container( + padding: const EdgeInsets.all(16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + AppLocalizations.of(context)!.dhcpStatic, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16, + color: Theme.of(context).colorScheme.onSurface, + ), + ), + Icon( + Icons.arrow_forward_rounded, + color: Theme.of(context).colorScheme.onSurface, + ) + ], + ), + ), + ), + ), + if (width > 900) Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + ElevatedButton( + onPressed: () { + if (!(Platform.isAndroid || Platform.isIOS)) { + SplitView.of(context).push( + DhcpLeases( + items: dhcpProvider.dhcp!.dhcpStatus.leases, + staticLeases: false, + ) + ); + } + else { + Navigator.push(context, MaterialPageRoute( + builder: (context) => DhcpLeases( + items: dhcpProvider.dhcp!.dhcpStatus.leases, + staticLeases: false, + ) + )); + } + }, + child: Row( + children: [ + Text(AppLocalizations.of(context)!.dhcpLeases), + const SizedBox(width: 8), + const Icon(Icons.arrow_forward_rounded) + ], + ) + ), + ElevatedButton( + onPressed: () { + if (!(Platform.isAndroid || Platform.isIOS)) { + SplitView.of(context).push( + DhcpLeases( + items: dhcpProvider.dhcp!.dhcpStatus.staticLeases, + staticLeases: true, + ) + ); + } + else { + Navigator.push(context, MaterialPageRoute( + builder: (context) => DhcpLeases( + items: dhcpProvider.dhcp!.dhcpStatus.staticLeases, + staticLeases: true, + ) + )); + } + }, + child: Row( + children: [ + Text(AppLocalizations.of(context)!.dhcpStatic), + const SizedBox(width: 8), + const Icon(Icons.arrow_forward_rounded) + ], + ) + ), + ], + ), + const SizedBox(height: 10) + ], + ), + ); + } + else { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Flexible( + child: Column( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Text( + AppLocalizations.of(context)!.neededSelectInterface, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurface.withOpacity(0.5) + ), + ), + ), + const SizedBox(height: 30), + ElevatedButton( + onPressed: selectInterface, + child: Text(AppLocalizations.of(context)!.selectInterface) + ), + ], + ), + ), + ], + ); + } + + case LoadStatus.error: + return SizedBox( + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Icon( + Icons.error, + color: Colors.red, + size: 50, + ), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.dhcpSettingsNotLoaded, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ), + ); + + default: + return const SizedBox(); + } + }, + ) ); } } \ No newline at end of file diff --git a/lib/screens/settings/dhcp/dhcp_leases.dart b/lib/screens/settings/dhcp/dhcp_leases.dart index 3763704..60a5644 100644 --- a/lib/screens/settings/dhcp/dhcp_leases.dart +++ b/lib/screens/settings/dhcp/dhcp_leases.dart @@ -2,6 +2,7 @@ import 'dart:io'; +import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:animations/animations.dart'; @@ -108,6 +109,7 @@ class DhcpLeases extends StatelessWidget { else { showModalBottomSheet( context: context, + useRootNavigator: true, builder: (context) => AddStaticLeaseModal( onConfirm: createLease, dialog: false, @@ -120,6 +122,7 @@ class DhcpLeases extends StatelessWidget { return Scaffold( appBar: AppBar( + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, title: Text( staticLeases == true ? AppLocalizations.of(context)!.dhcpStatic diff --git a/lib/screens/settings/dns/bootstrap_dns.dart b/lib/screens/settings/dns/bootstrap_dns.dart index 055042d..291601a 100644 --- a/lib/screens/settings/dns/bootstrap_dns.dart +++ b/lib/screens/settings/dns/bootstrap_dns.dart @@ -1,5 +1,6 @@ // ignore_for_file: use_build_context_synchronously +import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -66,6 +67,8 @@ class _BootstrapDnsScreenState extends State { final dnsProvider = Provider.of(context); final appConfigProvider = Provider.of(context); + final width = MediaQuery.of(context).size.width; + void saveData() async { ProcessModal processModal = ProcessModal(context: context); processModal.open(AppLocalizations.of(context)!.savingConfig); @@ -102,6 +105,7 @@ class _BootstrapDnsScreenState extends State { return Scaffold( appBar: AppBar( title: Text(AppLocalizations.of(context)!.bootstrapDns), + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, actions: [ IconButton( onPressed: validValues == true diff --git a/lib/screens/settings/dns/cache_config.dart b/lib/screens/settings/dns/cache_config.dart index eb5aad3..b092a8b 100644 --- a/lib/screens/settings/dns/cache_config.dart +++ b/lib/screens/settings/dns/cache_config.dart @@ -7,6 +7,7 @@ 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/screens/settings/dns/clear_dns_cache_dialog.dart'; +import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:adguard_home_manager/providers/servers_provider.dart'; import 'package:adguard_home_manager/providers/dns_provider.dart'; import 'package:adguard_home_manager/classes/process_modal.dart'; @@ -69,6 +70,8 @@ class _CacheConfigDnsScreenState extends State { final dnsProvider = Provider.of(context); final appConfigProvider = Provider.of(context); + final width = MediaQuery.of(context).size.width; + void saveData() async { ProcessModal processModal = ProcessModal(context: context); processModal.open(AppLocalizations.of(context)!.savingConfig); @@ -154,6 +157,7 @@ class _CacheConfigDnsScreenState extends State { return Scaffold( appBar: AppBar( title: Text(AppLocalizations.of(context)!.dnsCacheConfig), + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, actions: [ IconButton( onPressed: validData == true diff --git a/lib/screens/settings/dns/dns.dart b/lib/screens/settings/dns/dns.dart index eac3c2c..1aa63fd 100644 --- a/lib/screens/settings/dns/dns.dart +++ b/lib/screens/settings/dns/dns.dart @@ -1,9 +1,6 @@ // ignore_for_file: use_build_context_synchronously import 'dart:io'; - -import 'package:adguard_home_manager/constants/enums.dart'; -import 'package:adguard_home_manager/providers/dns_provider.dart'; import 'package:flutter/material.dart'; import 'package:flutter_split_view/flutter_split_view.dart'; import 'package:provider/provider.dart'; @@ -17,13 +14,21 @@ import 'package:adguard_home_manager/screens/settings/dns/private_reverse_server import 'package:adguard_home_manager/screens/settings/dns/upstream_dns.dart'; import 'package:adguard_home_manager/widgets/custom_list_tile.dart'; +import 'package:adguard_home_manager/constants/enums.dart'; +import 'package:adguard_home_manager/functions/desktop_mode.dart'; +import 'package:adguard_home_manager/providers/dns_provider.dart'; import 'package:adguard_home_manager/functions/clear_dns_cache.dart'; import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/providers/servers_provider.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; class DnsSettings extends StatefulWidget { - const DnsSettings({Key? key}) : super(key: key); + final bool splitView; + + const DnsSettings({ + Key? key, + required this.splitView, + }) : super(key: key); @override State createState() => _DnsSettingsState(); @@ -44,106 +49,17 @@ class _DnsSettingsState extends State { final width = MediaQuery.of(context).size.width; - void navigate(Widget widget) { - if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) { - SplitView.of(context).push(widget); + void navigate(Widget w) { + if (widget.splitView) { + SplitView.of(context).push(w); } else { Navigator.push(context, MaterialPageRoute( - builder: (context) => widget + builder: (context) => w )); } } - Widget generateBody() { - switch (dnsProvider.loadStatus) { - case LoadStatus.loading: - return SizedBox( - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const CircularProgressIndicator(), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.loadingDnsConfig, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ) - ); - - case LoadStatus.loaded: - return ListView( - children: [ - CustomListTile( - title: AppLocalizations.of(context)!.upstreamDns, - subtitle: AppLocalizations.of(context)!.upstreamDnsDescription, - onTap: () => navigate(const UpstreamDnsScreen()), - icon: Icons.upload_rounded, - ), - CustomListTile( - title: AppLocalizations.of(context)!.bootstrapDns, - subtitle: AppLocalizations.of(context)!.bootstrapDnsDescription, - onTap: () => navigate(const BootstrapDnsScreen()), - icon: Icons.dns_rounded, - ), - CustomListTile( - title: AppLocalizations.of(context)!.privateReverseDnsServers, - subtitle: AppLocalizations.of(context)!.privateReverseDnsDescription, - onTap: () => navigate(const PrivateReverseDnsServersScreen()), - icon: Icons.person_rounded, - ), - CustomListTile( - title: AppLocalizations.of(context)!.dnsServerSettings, - subtitle: AppLocalizations.of(context)!.dnsServerSettingsDescription, - onTap: () => navigate(const DnsServerSettingsScreen()), - icon: Icons.settings, - ), - CustomListTile( - title: AppLocalizations.of(context)!.dnsCacheConfig, - subtitle: AppLocalizations.of(context)!.dnsCacheConfigDescription, - onTap: () => navigate(const CacheConfigDnsScreen()), - icon: Icons.storage_rounded, - ), - ], - ); - - case LoadStatus.error: - return SizedBox( - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Icon( - Icons.error, - color: Colors.red, - size: 50, - ), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.dnsConfigNotLoaded, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ), - ); - - default: - return const SizedBox(); - } - } - void clearCache() async { final result = await clearDnsCache(context, serversProvider.selectedServer!); if (result == true) { @@ -165,6 +81,7 @@ class _DnsSettingsState extends State { return Scaffold( appBar: AppBar( title: Text(AppLocalizations.of(context)!.dnsSettings), + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, actions: [ PopupMenuButton( itemBuilder: (context) => [ @@ -200,7 +117,96 @@ class _DnsSettingsState extends State { const SizedBox(width: 10) ], ), - body: generateBody(), + body: Builder( + builder: (context) { + switch (dnsProvider.loadStatus) { + case LoadStatus.loading: + return SizedBox( + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const CircularProgressIndicator(), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.loadingDnsConfig, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ) + ); + + case LoadStatus.loaded: + return ListView( + children: [ + CustomListTile( + title: AppLocalizations.of(context)!.upstreamDns, + subtitle: AppLocalizations.of(context)!.upstreamDnsDescription, + onTap: () => navigate(const UpstreamDnsScreen()), + icon: Icons.upload_rounded, + ), + CustomListTile( + title: AppLocalizations.of(context)!.bootstrapDns, + subtitle: AppLocalizations.of(context)!.bootstrapDnsDescription, + onTap: () => navigate(const BootstrapDnsScreen()), + icon: Icons.dns_rounded, + ), + CustomListTile( + title: AppLocalizations.of(context)!.privateReverseDnsServers, + subtitle: AppLocalizations.of(context)!.privateReverseDnsDescription, + onTap: () => navigate(const PrivateReverseDnsServersScreen()), + icon: Icons.person_rounded, + ), + CustomListTile( + title: AppLocalizations.of(context)!.dnsServerSettings, + subtitle: AppLocalizations.of(context)!.dnsServerSettingsDescription, + onTap: () => navigate(const DnsServerSettingsScreen()), + icon: Icons.settings, + ), + CustomListTile( + title: AppLocalizations.of(context)!.dnsCacheConfig, + subtitle: AppLocalizations.of(context)!.dnsCacheConfigDescription, + onTap: () => navigate(const CacheConfigDnsScreen()), + icon: Icons.storage_rounded, + ), + ], + ); + + case LoadStatus.error: + return SizedBox( + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Icon( + Icons.error, + color: Colors.red, + size: 50, + ), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.dnsConfigNotLoaded, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ), + ); + + default: + return const SizedBox(); + } + }, + ) ); } } \ No newline at end of file diff --git a/lib/screens/settings/dns/dns_server_settings.dart b/lib/screens/settings/dns/dns_server_settings.dart index 9169408..b8481a0 100644 --- a/lib/screens/settings/dns/dns_server_settings.dart +++ b/lib/screens/settings/dns/dns_server_settings.dart @@ -8,6 +8,7 @@ import 'package:adguard_home_manager/widgets/custom_radio_list_tile.dart'; import 'package:adguard_home_manager/widgets/section_label.dart'; import 'package:adguard_home_manager/widgets/custom_switch_list_tile.dart'; +import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:adguard_home_manager/classes/process_modal.dart'; import 'package:adguard_home_manager/providers/dns_provider.dart'; import 'package:adguard_home_manager/functions/snackbar.dart'; @@ -99,6 +100,7 @@ class _DnsServerSettingsScreenState extends State { Widget build(BuildContext context) { final dnsProvider = Provider.of(context); final appConfigProvider = Provider.of(context); + final width = MediaQuery.of(context).size.width; void saveData() async { ProcessModal processModal = ProcessModal(context: context); @@ -153,6 +155,7 @@ class _DnsServerSettingsScreenState extends State { return Scaffold( appBar: AppBar( title: Text(AppLocalizations.of(context)!.dnsServerSettings), + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, actions: [ IconButton( onPressed: isDataValid == true diff --git a/lib/screens/settings/dns/private_reverse_servers.dart b/lib/screens/settings/dns/private_reverse_servers.dart index eb0f16a..2373d44 100644 --- a/lib/screens/settings/dns/private_reverse_servers.dart +++ b/lib/screens/settings/dns/private_reverse_servers.dart @@ -6,6 +6,7 @@ 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/functions/desktop_mode.dart'; import 'package:adguard_home_manager/providers/dns_provider.dart'; import 'package:adguard_home_manager/classes/process_modal.dart'; import 'package:adguard_home_manager/functions/snackbar.dart'; @@ -90,6 +91,8 @@ class _PrivateReverseDnsServersScreenState extends State(context); final appConfigProvider = Provider.of(context); + final width = MediaQuery.of(context).size.width; + void saveData() async { ProcessModal processModal = ProcessModal(context: context); processModal.open(AppLocalizations.of(context)!.savingConfig); @@ -134,6 +137,7 @@ class _PrivateReverseDnsServersScreenState extends State { else { showModalBottomSheet( context: context, + useRootNavigator: true, builder: (context) => CommentModal( onConfirm: (value) { setState(() { @@ -123,6 +125,7 @@ class _UpstreamDnsScreenState extends State { else { showModalBottomSheet( context: context, + useRootNavigator: true, builder: (context) => CommentModal( comment: item['comment'], onConfirm: (value) { @@ -174,6 +177,7 @@ class _UpstreamDnsScreenState extends State { return Scaffold( appBar: AppBar( title: Text(AppLocalizations.of(context)!.upstreamDns), + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, actions: [ IconButton( onPressed: validValues == true diff --git a/lib/screens/settings/dns_rewrites/dns_rewrites.dart b/lib/screens/settings/dns_rewrites/dns_rewrites.dart index fda731e..cd4c8bd 100644 --- a/lib/screens/settings/dns_rewrites/dns_rewrites.dart +++ b/lib/screens/settings/dns_rewrites/dns_rewrites.dart @@ -11,6 +11,7 @@ import 'package:adguard_home_manager/screens/settings/dns_rewrites/delete_dns_re import 'package:adguard_home_manager/screens/settings/dns_rewrites/dns_rewrite_modal.dart'; import 'package:adguard_home_manager/providers/app_config_provider.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/rewrite_rules_provider.dart'; @@ -129,193 +130,195 @@ class _DnsRewritesScreenState extends State { } } - Widget generateBody() { - switch (rewriteRulesProvider.loadStatus) { - case LoadStatus.loading: - return SizedBox( - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const CircularProgressIndicator(), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.loadingRewriteRules, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ), - ); - - case LoadStatus.loaded: - if (rewriteRulesProvider.rewriteRules!.isNotEmpty) { - return RefreshIndicator( - onRefresh: () async { - final result = await rewriteRulesProvider.fetchRules(); - if (result == false) { - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.rewriteRulesNotLoaded, - color: Colors.red - ); - } - }, - child: ListView.builder( - controller: scrollController, - padding: const EdgeInsets.only(top: 0), - itemCount: rewriteRulesProvider.rewriteRules!.length, - itemBuilder: (context, index) => Card( - margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: InkWell( - onTap: () => { - if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) { - showDialog( - context: context, - builder: (context) => DnsRewriteModal( - onConfirm: updateRewriteRule, - dialog: true, - rule: rewriteRulesProvider.rewriteRules![index], - onDelete: (rule) => showDialog( - context: context, - builder: (context) => DeleteDnsRewrite( - onConfirm: () => deleteDnsRewrite(rule) - ) - ), - ), - ) - } - else { - showModalBottomSheet( - context: context, - builder: (context) => DnsRewriteModal( - onConfirm: updateRewriteRule, - dialog: false, - rule: rewriteRulesProvider.rewriteRules![index], - onDelete: (rule) => showDialog( - context: context, - builder: (context) => DeleteDnsRewrite( - onConfirm: () => deleteDnsRewrite(rule) - ) - ), - ), - backgroundColor: Colors.transparent, - isScrollControlled: true, - ) - } - }, - borderRadius: BorderRadius.circular(10), - child: Padding( - padding: const EdgeInsets.only( - left: 16, top: 16, bottom: 16, right: 8 - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Text( - "${AppLocalizations.of(context)!.domain}: ", - style: TextStyle( - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.onSurface - ), - ), - Text( - rewriteRulesProvider.rewriteRules![index].domain, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurface - ), - ), - ], - ), - const SizedBox(height: 3), - Row( - children: [ - Text( - "${AppLocalizations.of(context)!.answer}: ", - style: TextStyle( - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.onSurface - ), - ), - Text( - rewriteRulesProvider.rewriteRules![index].answer, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurface - ), - ), - ], - ), - ], - ), - Icon( - Icons.keyboard_arrow_right_rounded, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ) - ], - ), - ), - ), - ) - ), - ); - } - else { - return Center( - child: Text( - AppLocalizations.of(context)!.noRewriteRules, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - ); - } - - case LoadStatus.error: - return SizedBox( - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Icon( - Icons.error, - color: Colors.red, - size: 50, - ), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.rewriteRulesNotLoaded, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ), - ); - - default: - return const SizedBox(); - } - } - return Scaffold( appBar: AppBar( title: Text(AppLocalizations.of(context)!.dnsRewrites), + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, centerTitle: false, ), body: Stack( children: [ - generateBody(), + Builder( + builder: (context) { + switch (rewriteRulesProvider.loadStatus) { + case LoadStatus.loading: + return SizedBox( + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const CircularProgressIndicator(), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.loadingRewriteRules, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ), + ); + + case LoadStatus.loaded: + if (rewriteRulesProvider.rewriteRules!.isNotEmpty) { + return RefreshIndicator( + onRefresh: () async { + final result = await rewriteRulesProvider.fetchRules(); + if (result == false) { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.rewriteRulesNotLoaded, + color: Colors.red + ); + } + }, + child: ListView.builder( + controller: scrollController, + padding: const EdgeInsets.only(top: 0), + itemCount: rewriteRulesProvider.rewriteRules!.length, + itemBuilder: (context, index) => Card( + margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: InkWell( + onTap: () => { + if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) { + showDialog( + context: context, + builder: (context) => DnsRewriteModal( + onConfirm: updateRewriteRule, + dialog: true, + rule: rewriteRulesProvider.rewriteRules![index], + onDelete: (rule) => showDialog( + context: context, + builder: (context) => DeleteDnsRewrite( + onConfirm: () => deleteDnsRewrite(rule) + ) + ), + ), + ) + } + else { + showModalBottomSheet( + context: context, + useRootNavigator: true, + builder: (context) => DnsRewriteModal( + onConfirm: updateRewriteRule, + dialog: false, + rule: rewriteRulesProvider.rewriteRules![index], + onDelete: (rule) => showDialog( + context: context, + builder: (context) => DeleteDnsRewrite( + onConfirm: () => deleteDnsRewrite(rule) + ) + ), + ), + backgroundColor: Colors.transparent, + isScrollControlled: true, + ) + } + }, + borderRadius: BorderRadius.circular(10), + child: Padding( + padding: const EdgeInsets.only( + left: 16, top: 16, bottom: 16, right: 8 + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + "${AppLocalizations.of(context)!.domain}: ", + style: TextStyle( + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.onSurface + ), + ), + Text( + rewriteRulesProvider.rewriteRules![index].domain, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface + ), + ), + ], + ), + const SizedBox(height: 3), + Row( + children: [ + Text( + "${AppLocalizations.of(context)!.answer}: ", + style: TextStyle( + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.onSurface + ), + ), + Text( + rewriteRulesProvider.rewriteRules![index].answer, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface + ), + ), + ], + ), + ], + ), + Icon( + Icons.keyboard_arrow_right_rounded, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ) + ], + ), + ), + ), + ) + ), + ); + } + else { + return Center( + child: Text( + AppLocalizations.of(context)!.noRewriteRules, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + ); + } + + case LoadStatus.error: + return SizedBox( + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Icon( + Icons.error, + color: Colors.red, + size: 50, + ), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.rewriteRulesNotLoaded, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ), + ); + + default: + return const SizedBox(); + } + }, + ), AnimatedPositioned( duration: const Duration(milliseconds: 100), curve: Curves.easeInOut, @@ -344,6 +347,7 @@ class _DnsRewritesScreenState extends State { else { showModalBottomSheet( context: context, + useRootNavigator: true, builder: (context) => DnsRewriteModal( onConfirm: addDnsRewrite, dialog: false, diff --git a/lib/screens/settings/encryption/encryption.dart b/lib/screens/settings/encryption/encryption.dart index de75918..3c4744c 100644 --- a/lib/screens/settings/encryption/encryption.dart +++ b/lib/screens/settings/encryption/encryption.dart @@ -14,6 +14,7 @@ import 'package:adguard_home_manager/screens/settings/encryption/encryption_func import 'package:adguard_home_manager/screens/settings/encryption/error_message.dart'; import 'package:adguard_home_manager/classes/process_modal.dart'; +import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:adguard_home_manager/functions/base64.dart'; import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; @@ -272,379 +273,10 @@ class _EncryptionSettingsWidgetState extends State { } } - Widget generateBody() { - switch (loadStatus) { - case 0: - return SizedBox( - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const CircularProgressIndicator(), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.loadingEncryptionSettings, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ) - ); - - case 1: - return ListView( - children: [ - EncryptionMasterSwitch( - value: enabled, - onChange: (value) { - setState(() => enabled = value); - onEditValidate(); - } - ), - SectionLabel( - label: AppLocalizations.of(context)!.serverConfiguration, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), - ), - EncryptionTextField( - enabled: enabled, - controller: domainNameController, - icon: Icons.link_rounded, - onChanged: (value) { - setState(() => domainError = validateDomain(context, value)); - onEditValidate(); - }, - errorText: domainError, - label: AppLocalizations.of(context)!.domainName, - helperText: AppLocalizations.of(context)!.domainNameDescription, - ), - const SizedBox(height: 10), - CustomSwitchListTile( - value: redirectHttps, - onChanged: (value) { - setState(() => redirectHttps = value); - onEditValidate(); - }, - title: AppLocalizations.of(context)!.redirectHttps, - disabled: !enabled, - ), - const SizedBox(height: 10), - Wrap( - children: [ - FractionallySizedBox( - widthFactor: width > 900 ? 0.33 : 1, - child: EncryptionTextField( - enabled: enabled, - controller: httpsPortController, - icon: Icons.numbers_rounded, - onChanged: (value) { - setState(() => httpsPortError = validatePort(context, value)); - onEditValidate(); - }, - errorText: httpsPortError, - label: AppLocalizations.of(context)!.httpsPort, - keyboardType: TextInputType.number, - ), - ), - Padding( - padding: width <= 900 - ? const EdgeInsets.symmetric(vertical: 24) - : const EdgeInsets.all(0), - child: FractionallySizedBox( - widthFactor: width > 900 ? 0.33 : 1, - child: EncryptionTextField( - enabled: enabled, - controller: tlsPortController, - icon: Icons.numbers_rounded, - onChanged: (value) { - setState(() => tlsPortError = validatePort(context, value)); - onEditValidate(); - }, - errorText: tlsPortError, - label: AppLocalizations.of(context)!.tlsPort, - keyboardType: TextInputType.number, - ), - ), - ), - FractionallySizedBox( - widthFactor: width > 900 ? 0.33 : 1, - child: EncryptionTextField( - enabled: enabled, - controller: dnsOverQuicPortController, - icon: Icons.numbers_rounded, - onChanged: (value) { - setState(() => dnsOverQuicPortError = validatePort(context, value)); - onEditValidate(); - }, - errorText: dnsOverQuicPortError, - label: AppLocalizations.of(context)!.dnsOverQuicPort, - keyboardType: TextInputType.number, - ), - ), - ], - ), - SectionLabel( - label: AppLocalizations.of(context)!.certificates, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), - ), - Card( - margin: const EdgeInsets.symmetric(horizontal: 16), - child: Padding( - padding: const EdgeInsets.all(20), - child: Row( - children: [ - Icon( - Icons.info_rounded, - color: Theme.of(context).listTileTheme.iconColor, - ), - const SizedBox(width: 20), - Flexible( - child: Text( - AppLocalizations.of(context)!.certificatesDescription, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurface, - ), - ) - ) - ], - ), - ), - ), - const SizedBox(height: 20), - RadioListTile( - value: 0, - groupValue: certificateOption, - onChanged: enabled == true - ? (value) { - setState(() => certificateOption = int.parse(value.toString())); - onEditValidate(); - } - : null, - title: Text( - AppLocalizations.of(context)!.certificateFilePath, - style: const TextStyle( - fontWeight: FontWeight.normal - ), - ), - ), - RadioListTile( - value: 1, - groupValue: certificateOption, - onChanged: enabled == true - ? (value) { - setState(() => certificateOption = int.parse(value.toString())); - onEditValidate(); - } - : null, - title: Text( - AppLocalizations.of(context)!.pasteCertificateContent, - style: const TextStyle( - fontWeight: FontWeight.normal - ), - ), - ), - const SizedBox(height: 10), - if (certificateOption == 0) EncryptionTextField( - enabled: enabled, - controller: certificatePathController, - icon: Icons.description_rounded, - onChanged: (value) { - setState(() => certificatePathError = validatePath(context, value)); - onEditValidate(); - }, - label: AppLocalizations.of(context)!.certificatePath, - errorText: certificatePathError, - ), - if (certificateOption == 1) EncryptionTextField( - enabled: enabled, - controller: certificateContentController, - icon: Icons.description_rounded, - onChanged: (value) { - setState(() => certificateContentError = validateCertificate(context, value)); - onEditValidate(); - }, - label: AppLocalizations.of(context)!.certificateContent, - errorText: certificateContentError, - multiline: true, - keyboardType: TextInputType.multiline, - ), - if (certKeyValid != null && (certificateContentController.text != '' || certificatePathController.text != '')) ...[ - const SizedBox(height: 20), - if (certKeyValid!['valid_chain'] != null) ...[ - Status( - valid: certKeyValid!['valid_chain'], - label: certKeyValid!['valid_chain'] == true - ? AppLocalizations.of(context)!.validCertificateChain - : AppLocalizations.of(context)!.invalidCertificateChain, - ), - const SizedBox(height: 10), - ], - if (certKeyValid!['subject'] != null) ...[ - Status( - valid: true, - label: "${AppLocalizations.of(context)!.subject}: ${certKeyValid!['subject']}" - ), - const SizedBox(height: 10), - ], - if (certKeyValid!['issuer'] != null) ...[ - Status( - valid: true, - label: "${AppLocalizations.of(context)!.issuer}: ${certKeyValid!['issuer']}" - ), - const SizedBox(height: 10), - ], - if (certKeyValid!['not_after'] != null) ...[ - Status( - valid: true, - label: "${AppLocalizations.of(context)!.expirationDate}: ${certKeyValid!['not_after']}" - ), - const SizedBox(height: 10), - ], - if (certKeyValid!['dns_names'] != null) ...[ - Status( - valid: true, - label: "${AppLocalizations.of(context)!.hostNames}: ${certKeyValid!['dns_names'].join(', ')}" - ), - const SizedBox(height: 10), - ], - ], - SectionLabel( - label: AppLocalizations.of(context)!.privateKey, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), - ), - RadioListTile( - value: 0, - groupValue: privateKeyOption, - onChanged: enabled == true - ? (value) { - setState(() => privateKeyOption = int.parse(value.toString())); - onEditValidate(); - } - : null, - title: Text( - AppLocalizations.of(context)!.privateKeyFile, - style: const TextStyle( - fontWeight: FontWeight.normal - ), - ), - ), - RadioListTile( - value: 1, - groupValue: privateKeyOption, - onChanged: enabled == true - ? (value) { - setState(() => privateKeyOption = int.parse(value.toString())); - onEditValidate(); - } - : null, - title: Text( - AppLocalizations.of(context)!.pastePrivateKey, - style: const TextStyle( - fontWeight: FontWeight.normal - ), - ), - ), - if (privateKeyOption == 0) const SizedBox(height: 10), - if (privateKeyOption == 1) ...[ - CustomSwitchListTile( - value: usePreviouslySavedKey, - onChanged: (value) => setState(() => usePreviouslySavedKey = value), - title: AppLocalizations.of(context)!.usePreviousKey, - ), - const SizedBox(height: 10) - ], - if (privateKeyOption == 0) EncryptionTextField( - enabled: enabled, - controller: privateKeyPathController, - icon: Icons.description_rounded, - onChanged: (value) { - setState(() => privateKeyPathError = validatePath(context, value)); - onEditValidate(); - }, - label: AppLocalizations.of(context)!.privateKeyPath, - errorText: privateKeyPathError, - ), - if (privateKeyOption == 1) EncryptionTextField( - enabled: enabled == true - ? !usePreviouslySavedKey - : false, - controller: pastePrivateKeyController, - icon: Icons.description_rounded, - onChanged: (value) { - setState(() => pastePrivateKeyError = validatePrivateKey(context, value)); - onEditValidate(); - }, - label: AppLocalizations.of(context)!.pastePrivateKey, - errorText: pastePrivateKeyError, - keyboardType: TextInputType.multiline, - multiline: true, - ), - const SizedBox(height: 20), - if (certKeyValid != null && (privateKeyPathController.text != '' || pastePrivateKeyController.text != '' || usePreviouslySavedKey == true)) ...[ - if (certKeyValid!['valid_key'] != null) ...[ - Status( - valid: certKeyValid!['valid_key'], - label: certKeyValid!['valid_key'] == true - ? AppLocalizations.of(context)!.validPrivateKey - : AppLocalizations.of(context)!.invalidPrivateKey, - ), - const SizedBox(height: 10) - ], - if (certKeyValid!['valid_pair'] != null && certKeyValid!['valid_pair'] == false) ...[ - Status( - valid: false, - label: AppLocalizations.of(context)!.keysNotMatch, - ), - const SizedBox(height: 10) - ], - if (certKeyValid!['key_type'] != null) ...[ - Status( - valid: true, - label: "${AppLocalizations.of(context)!.keyType}: ${certKeyValid!['key_type']}" - ), - const SizedBox(height: 10), - ], - const SizedBox(height: 10) - ] - ], - ); - - case 2: - return SizedBox( - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Icon( - Icons.error, - color: Colors.red, - size: 50, - ), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.encryptionSettingsNotLoaded, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ), - ); - - default: - return const SizedBox(); - } - } - return Scaffold( appBar: AppBar( title: Text(AppLocalizations.of(context)!.encryptionSettings), + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, centerTitle: false, actions: [ IconButton( @@ -668,7 +300,377 @@ class _EncryptionSettingsWidgetState extends State { const SizedBox(width: 10), ], ), - body: generateBody(), + body: Builder( + builder: (context) { + switch (loadStatus) { + case 0: + return SizedBox( + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const CircularProgressIndicator(), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.loadingEncryptionSettings, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ) + ); + + case 1: + return ListView( + children: [ + EncryptionMasterSwitch( + value: enabled, + onChange: (value) { + setState(() => enabled = value); + onEditValidate(); + } + ), + SectionLabel( + label: AppLocalizations.of(context)!.serverConfiguration, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), + ), + EncryptionTextField( + enabled: enabled, + controller: domainNameController, + icon: Icons.link_rounded, + onChanged: (value) { + setState(() => domainError = validateDomain(context, value)); + onEditValidate(); + }, + errorText: domainError, + label: AppLocalizations.of(context)!.domainName, + helperText: AppLocalizations.of(context)!.domainNameDescription, + ), + const SizedBox(height: 10), + CustomSwitchListTile( + value: redirectHttps, + onChanged: (value) { + setState(() => redirectHttps = value); + onEditValidate(); + }, + title: AppLocalizations.of(context)!.redirectHttps, + disabled: !enabled, + ), + const SizedBox(height: 10), + Wrap( + children: [ + FractionallySizedBox( + widthFactor: width > 900 ? 0.33 : 1, + child: EncryptionTextField( + enabled: enabled, + controller: httpsPortController, + icon: Icons.numbers_rounded, + onChanged: (value) { + setState(() => httpsPortError = validatePort(context, value)); + onEditValidate(); + }, + errorText: httpsPortError, + label: AppLocalizations.of(context)!.httpsPort, + keyboardType: TextInputType.number, + ), + ), + Padding( + padding: width <= 900 + ? const EdgeInsets.symmetric(vertical: 24) + : const EdgeInsets.all(0), + child: FractionallySizedBox( + widthFactor: width > 900 ? 0.33 : 1, + child: EncryptionTextField( + enabled: enabled, + controller: tlsPortController, + icon: Icons.numbers_rounded, + onChanged: (value) { + setState(() => tlsPortError = validatePort(context, value)); + onEditValidate(); + }, + errorText: tlsPortError, + label: AppLocalizations.of(context)!.tlsPort, + keyboardType: TextInputType.number, + ), + ), + ), + FractionallySizedBox( + widthFactor: width > 900 ? 0.33 : 1, + child: EncryptionTextField( + enabled: enabled, + controller: dnsOverQuicPortController, + icon: Icons.numbers_rounded, + onChanged: (value) { + setState(() => dnsOverQuicPortError = validatePort(context, value)); + onEditValidate(); + }, + errorText: dnsOverQuicPortError, + label: AppLocalizations.of(context)!.dnsOverQuicPort, + keyboardType: TextInputType.number, + ), + ), + ], + ), + SectionLabel( + label: AppLocalizations.of(context)!.certificates, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), + ), + Card( + margin: const EdgeInsets.symmetric(horizontal: 16), + child: Padding( + padding: const EdgeInsets.all(20), + child: Row( + children: [ + Icon( + Icons.info_rounded, + color: Theme.of(context).listTileTheme.iconColor, + ), + const SizedBox(width: 20), + Flexible( + child: Text( + AppLocalizations.of(context)!.certificatesDescription, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface, + ), + ) + ) + ], + ), + ), + ), + const SizedBox(height: 20), + RadioListTile( + value: 0, + groupValue: certificateOption, + onChanged: enabled == true + ? (value) { + setState(() => certificateOption = int.parse(value.toString())); + onEditValidate(); + } + : null, + title: Text( + AppLocalizations.of(context)!.certificateFilePath, + style: const TextStyle( + fontWeight: FontWeight.normal + ), + ), + ), + RadioListTile( + value: 1, + groupValue: certificateOption, + onChanged: enabled == true + ? (value) { + setState(() => certificateOption = int.parse(value.toString())); + onEditValidate(); + } + : null, + title: Text( + AppLocalizations.of(context)!.pasteCertificateContent, + style: const TextStyle( + fontWeight: FontWeight.normal + ), + ), + ), + const SizedBox(height: 10), + if (certificateOption == 0) EncryptionTextField( + enabled: enabled, + controller: certificatePathController, + icon: Icons.description_rounded, + onChanged: (value) { + setState(() => certificatePathError = validatePath(context, value)); + onEditValidate(); + }, + label: AppLocalizations.of(context)!.certificatePath, + errorText: certificatePathError, + ), + if (certificateOption == 1) EncryptionTextField( + enabled: enabled, + controller: certificateContentController, + icon: Icons.description_rounded, + onChanged: (value) { + setState(() => certificateContentError = validateCertificate(context, value)); + onEditValidate(); + }, + label: AppLocalizations.of(context)!.certificateContent, + errorText: certificateContentError, + multiline: true, + keyboardType: TextInputType.multiline, + ), + if (certKeyValid != null && (certificateContentController.text != '' || certificatePathController.text != '')) ...[ + const SizedBox(height: 20), + if (certKeyValid!['valid_chain'] != null) ...[ + Status( + valid: certKeyValid!['valid_chain'], + label: certKeyValid!['valid_chain'] == true + ? AppLocalizations.of(context)!.validCertificateChain + : AppLocalizations.of(context)!.invalidCertificateChain, + ), + const SizedBox(height: 10), + ], + if (certKeyValid!['subject'] != null) ...[ + Status( + valid: true, + label: "${AppLocalizations.of(context)!.subject}: ${certKeyValid!['subject']}" + ), + const SizedBox(height: 10), + ], + if (certKeyValid!['issuer'] != null) ...[ + Status( + valid: true, + label: "${AppLocalizations.of(context)!.issuer}: ${certKeyValid!['issuer']}" + ), + const SizedBox(height: 10), + ], + if (certKeyValid!['not_after'] != null) ...[ + Status( + valid: true, + label: "${AppLocalizations.of(context)!.expirationDate}: ${certKeyValid!['not_after']}" + ), + const SizedBox(height: 10), + ], + if (certKeyValid!['dns_names'] != null) ...[ + Status( + valid: true, + label: "${AppLocalizations.of(context)!.hostNames}: ${certKeyValid!['dns_names'].join(', ')}" + ), + const SizedBox(height: 10), + ], + ], + SectionLabel( + label: AppLocalizations.of(context)!.privateKey, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), + ), + RadioListTile( + value: 0, + groupValue: privateKeyOption, + onChanged: enabled == true + ? (value) { + setState(() => privateKeyOption = int.parse(value.toString())); + onEditValidate(); + } + : null, + title: Text( + AppLocalizations.of(context)!.privateKeyFile, + style: const TextStyle( + fontWeight: FontWeight.normal + ), + ), + ), + RadioListTile( + value: 1, + groupValue: privateKeyOption, + onChanged: enabled == true + ? (value) { + setState(() => privateKeyOption = int.parse(value.toString())); + onEditValidate(); + } + : null, + title: Text( + AppLocalizations.of(context)!.pastePrivateKey, + style: const TextStyle( + fontWeight: FontWeight.normal + ), + ), + ), + if (privateKeyOption == 0) const SizedBox(height: 10), + if (privateKeyOption == 1) ...[ + CustomSwitchListTile( + value: usePreviouslySavedKey, + onChanged: (value) => setState(() => usePreviouslySavedKey = value), + title: AppLocalizations.of(context)!.usePreviousKey, + ), + const SizedBox(height: 10) + ], + if (privateKeyOption == 0) EncryptionTextField( + enabled: enabled, + controller: privateKeyPathController, + icon: Icons.description_rounded, + onChanged: (value) { + setState(() => privateKeyPathError = validatePath(context, value)); + onEditValidate(); + }, + label: AppLocalizations.of(context)!.privateKeyPath, + errorText: privateKeyPathError, + ), + if (privateKeyOption == 1) EncryptionTextField( + enabled: enabled == true + ? !usePreviouslySavedKey + : false, + controller: pastePrivateKeyController, + icon: Icons.description_rounded, + onChanged: (value) { + setState(() => pastePrivateKeyError = validatePrivateKey(context, value)); + onEditValidate(); + }, + label: AppLocalizations.of(context)!.pastePrivateKey, + errorText: pastePrivateKeyError, + keyboardType: TextInputType.multiline, + multiline: true, + ), + const SizedBox(height: 20), + if (certKeyValid != null && (privateKeyPathController.text != '' || pastePrivateKeyController.text != '' || usePreviouslySavedKey == true)) ...[ + if (certKeyValid!['valid_key'] != null) ...[ + Status( + valid: certKeyValid!['valid_key'], + label: certKeyValid!['valid_key'] == true + ? AppLocalizations.of(context)!.validPrivateKey + : AppLocalizations.of(context)!.invalidPrivateKey, + ), + const SizedBox(height: 10) + ], + if (certKeyValid!['valid_pair'] != null && certKeyValid!['valid_pair'] == false) ...[ + Status( + valid: false, + label: AppLocalizations.of(context)!.keysNotMatch, + ), + const SizedBox(height: 10) + ], + if (certKeyValid!['key_type'] != null) ...[ + Status( + valid: true, + label: "${AppLocalizations.of(context)!.keyType}: ${certKeyValid!['key_type']}" + ), + const SizedBox(height: 10), + ], + const SizedBox(height: 10) + ] + ], + ); + + case 2: + return SizedBox( + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Icon( + Icons.error, + color: Colors.red, + size: 50, + ), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.encryptionSettingsNotLoaded, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ), + ); + + default: + return const SizedBox(); + } + }, + ) ); } } \ 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 9ead014..cef6a8f 100644 --- a/lib/screens/settings/general_settings/general_settings.dart +++ b/lib/screens/settings/general_settings/general_settings.dart @@ -13,6 +13,7 @@ import 'package:adguard_home_manager/widgets/custom_list_tile.dart'; import 'package:adguard_home_manager/widgets/section_label.dart'; import 'package:adguard_home_manager/functions/check_app_updates.dart'; +import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/functions/open_url.dart'; import 'package:adguard_home_manager/functions/app_update_download_link.dart'; @@ -34,6 +35,8 @@ class _GeneralSettingsState extends State { Widget build(BuildContext context) { final appConfigProvider = Provider.of(context); + final width = MediaQuery.of(context).size.width; + Future updateSettings({ required bool newStatus, required Future Function(bool) function @@ -113,6 +116,7 @@ class _GeneralSettingsState extends State { return Scaffold( appBar: AppBar( title: Text(AppLocalizations.of(context)!.generalSettings), + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, ), body: ListView( children: [ diff --git a/lib/screens/settings/general_settings/reorderable_top_items_home.dart b/lib/screens/settings/general_settings/reorderable_top_items_home.dart index 75ad9cc..b649ff5 100644 --- a/lib/screens/settings/general_settings/reorderable_top_items_home.dart +++ b/lib/screens/settings/general_settings/reorderable_top_items_home.dart @@ -1,5 +1,6 @@ // ignore_for_file: use_build_context_synchronously +import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -91,6 +92,8 @@ class _ReorderableTopItemsHomeState extends State { Widget build(BuildContext context) { final appConfigProvider = Provider.of(context); + final width = MediaQuery.of(context).size.width; + Widget tile(HomeTopItems title) { switch (title) { case HomeTopItems.queriedDomains: @@ -177,6 +180,7 @@ class _ReorderableTopItemsHomeState extends State { child: Scaffold( appBar: AppBar( title: Text(AppLocalizations.of(context)!.topItemsOrder), + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, actions: [ IconButton( onPressed: !listEquals(appConfigProvider.homeTopItemsOrder, persistHomeTopItemsList) diff --git a/lib/screens/settings/safe_search_settings.dart b/lib/screens/settings/safe_search_settings.dart index 461b6a6..9ef8359 100644 --- a/lib/screens/settings/safe_search_settings.dart +++ b/lib/screens/settings/safe_search_settings.dart @@ -1,5 +1,6 @@ // ignore_for_file: use_build_context_synchronously +import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -70,6 +71,8 @@ class _SafeSearchSettingsScreenState extends State { final statusProvider = Provider.of(context); final appConfigProvider = Provider.of(context); + final width = MediaQuery.of(context).size.width; + void saveConfig() async { ProcessModal processModal = ProcessModal(context: context); processModal.open(AppLocalizations.of(context)!.savingSettings); @@ -104,167 +107,10 @@ class _SafeSearchSettingsScreenState extends State { } } - Widget body() { - switch (statusProvider.loadStatus) { - case LoadStatus.loading: - return SizedBox( - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const CircularProgressIndicator(), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.loadingSafeSearchSettings, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ), - ); - - case LoadStatus.loaded: - return RefreshIndicator( - onRefresh: requestSafeSearchSettings, - child: ListView( - children: [ - Padding( - padding: const EdgeInsets.only( - top: 16, - left: 16, - right: 16, - bottom: 8 - ), - child: Material( - color: Colors.transparent, - borderRadius: BorderRadius.circular(28), - child: InkWell( - borderRadius: BorderRadius.circular(28), - onTap: () => setState(() => generalEnabled = !generalEnabled), - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 24, - vertical: 12 - ), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.primary.withOpacity(0.1), - borderRadius: BorderRadius.circular(28) - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - AppLocalizations.of(context)!.enableSafeSearch, - style: const TextStyle( - fontSize: 18 - ), - ), - Switch( - value: generalEnabled, - onChanged: (value) => setState(() => generalEnabled = value) - ) - ], - ), - ), - ), - ), - ), - CustomCheckboxListTile( - value: bingEnabled, - onChanged: (value) => setState(() => bingEnabled = value), - title: "Bing", - padding: const EdgeInsets.only( - top: 8, left: 40, right: 40, bottom: 8 - ), - disabled: !generalEnabled, - ), - CustomCheckboxListTile( - value: duckduckgoEnabled, - onChanged: (value) => setState(() => duckduckgoEnabled = value), - title: "DuckDuckGo", - padding: const EdgeInsets.only( - top: 8, left: 40, right: 40, bottom: 8 - ), - disabled: !generalEnabled, - ), - CustomCheckboxListTile( - value: googleEnabled, - onChanged: (value) => setState(() => googleEnabled = value), - title: "Google", - padding: const EdgeInsets.only( - top: 8, left: 40, right: 40, bottom: 8 - ), - disabled: !generalEnabled, - ), - CustomCheckboxListTile( - value: pixabayEnabled, - onChanged: (value) => setState(() => pixabayEnabled = value), - title: "Pixabay", - padding: const EdgeInsets.only( - top: 8, left: 40, right: 40, bottom: 8 - ), - disabled: !generalEnabled, - ), - CustomCheckboxListTile( - value: yandexEnabled, - onChanged: (value) => setState(() => yandexEnabled = value), - title: "Yandex", - padding: const EdgeInsets.only( - top: 8, left: 40, right: 40, bottom: 8 - ), - disabled: !generalEnabled, - ), - CustomCheckboxListTile( - value: youtubeEnabled, - onChanged: (value) => setState(() => youtubeEnabled = value), - title: "YouTube", - padding: const EdgeInsets.only( - top: 8, left: 40, right: 40, bottom: 8 - ), - disabled: !generalEnabled, - ), - ], - ), - ); - - case LoadStatus.error: - return SizedBox( - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Icon( - Icons.error, - color: Colors.red, - size: 50, - ), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.safeSearchSettingsNotLoaded, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ), - ); - - - default: - return const SizedBox(); - } - } - return Scaffold( appBar: AppBar( title: Text(AppLocalizations.of(context)!.safeSearchSettings), + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, centerTitle: false, actions: [ IconButton( @@ -277,7 +123,165 @@ class _SafeSearchSettingsScreenState extends State { const SizedBox(width: 8) ], ), - body: body(), + body: Builder( + builder: (context) { + switch (statusProvider.loadStatus) { + case LoadStatus.loading: + return SizedBox( + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const CircularProgressIndicator(), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.loadingSafeSearchSettings, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ), + ); + + case LoadStatus.loaded: + return RefreshIndicator( + onRefresh: requestSafeSearchSettings, + child: ListView( + children: [ + Padding( + padding: const EdgeInsets.only( + top: 16, + left: 16, + right: 16, + bottom: 8 + ), + child: Material( + color: Colors.transparent, + borderRadius: BorderRadius.circular(28), + child: InkWell( + borderRadius: BorderRadius.circular(28), + onTap: () => setState(() => generalEnabled = !generalEnabled), + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 24, + vertical: 12 + ), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(28) + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + AppLocalizations.of(context)!.enableSafeSearch, + style: const TextStyle( + fontSize: 18 + ), + ), + Switch( + value: generalEnabled, + onChanged: (value) => setState(() => generalEnabled = value) + ) + ], + ), + ), + ), + ), + ), + CustomCheckboxListTile( + value: bingEnabled, + onChanged: (value) => setState(() => bingEnabled = value), + title: "Bing", + padding: const EdgeInsets.only( + top: 8, left: 40, right: 40, bottom: 8 + ), + disabled: !generalEnabled, + ), + CustomCheckboxListTile( + value: duckduckgoEnabled, + onChanged: (value) => setState(() => duckduckgoEnabled = value), + title: "DuckDuckGo", + padding: const EdgeInsets.only( + top: 8, left: 40, right: 40, bottom: 8 + ), + disabled: !generalEnabled, + ), + CustomCheckboxListTile( + value: googleEnabled, + onChanged: (value) => setState(() => googleEnabled = value), + title: "Google", + padding: const EdgeInsets.only( + top: 8, left: 40, right: 40, bottom: 8 + ), + disabled: !generalEnabled, + ), + CustomCheckboxListTile( + value: pixabayEnabled, + onChanged: (value) => setState(() => pixabayEnabled = value), + title: "Pixabay", + padding: const EdgeInsets.only( + top: 8, left: 40, right: 40, bottom: 8 + ), + disabled: !generalEnabled, + ), + CustomCheckboxListTile( + value: yandexEnabled, + onChanged: (value) => setState(() => yandexEnabled = value), + title: "Yandex", + padding: const EdgeInsets.only( + top: 8, left: 40, right: 40, bottom: 8 + ), + disabled: !generalEnabled, + ), + CustomCheckboxListTile( + value: youtubeEnabled, + onChanged: (value) => setState(() => youtubeEnabled = value), + title: "YouTube", + padding: const EdgeInsets.only( + top: 8, left: 40, right: 40, bottom: 8 + ), + disabled: !generalEnabled, + ), + ], + ), + ); + + case LoadStatus.error: + return SizedBox( + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Icon( + Icons.error, + color: Colors.red, + size: 50, + ), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.safeSearchSettingsNotLoaded, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ), + ); + + + default: + return const SizedBox(); + } + }, + ) ); } } \ No newline at end of file diff --git a/lib/screens/settings/server_info/server_info.dart b/lib/screens/settings/server_info/server_info.dart index a6a9fcc..2a51a16 100644 --- a/lib/screens/settings/server_info/server_info.dart +++ b/lib/screens/settings/server_info/server_info.dart @@ -1,4 +1,5 @@ import 'package:adguard_home_manager/constants/enums.dart'; +import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:animations/animations.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -66,121 +67,123 @@ class _ServerInformationWidgetState extends State { @override Widget build(BuildContext context) { - Widget generateBody() { - switch (serverInfo.loadStatus) { - case LoadStatus.loading: - return SizedBox( - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const CircularProgressIndicator(), - const SizedBox(height: 30), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Text( - AppLocalizations.of(context)!.loadingServerInfo, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - ) - ], - ), - ); - - case LoadStatus.loaded: - return ListView( - children: [ - CustomListTile( - title: AppLocalizations.of(context)!.dnsAddresses, - subtitle: AppLocalizations.of(context)!.seeDnsAddresses, - onTap: () { - showModal( - context: context, - builder: (context) => DnsAddressesModal( - dnsAddresses: serverInfo.data!.dnsAddresses - ) - ); - }, - ), - CustomListTile( - title: AppLocalizations.of(context)!.dnsPort, - subtitle: serverInfo.data!.dnsPort.toString(), - ), - CustomListTile( - title: AppLocalizations.of(context)!.httpPort, - subtitle: serverInfo.data!.httpPort.toString(), - ), - CustomListTile( - title: AppLocalizations.of(context)!.protectionEnabled, - subtitle: serverInfo.data!.protectionEnabled == true - ? AppLocalizations.of(context)!.yes - : AppLocalizations.of(context)!.no, - ), - CustomListTile( - title: AppLocalizations.of(context)!.dhcpAvailable, - subtitle: serverInfo.data!.dhcpAvailable == true - ? AppLocalizations.of(context)!.yes - : AppLocalizations.of(context)!.no, - ), - CustomListTile( - title: AppLocalizations.of(context)!.serverRunning, - subtitle: serverInfo.data!.running == true - ? AppLocalizations.of(context)!.yes - : AppLocalizations.of(context)!.no, - ), - CustomListTile( - title: AppLocalizations.of(context)!.serverVersion, - subtitle: serverInfo.data!.version, - ), - CustomListTile( - title: AppLocalizations.of(context)!.serverLanguage, - subtitle: serverInfo.data!.language, - ), - ] - ); - - case LoadStatus.error: - return SizedBox( - width: double.maxFinite, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Icon( - Icons.error, - color: Colors.red, - size: 50, - ), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.serverInfoNotLoaded, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ], - ), - ); - - default: - return const SizedBox(); - } - } - + final width = MediaQuery.of(context).size.width; return Scaffold( appBar: AppBar( title: Text(AppLocalizations.of(context)!.serverInformation), + surfaceTintColor: isDesktop(width) ? Colors.transparent : null, centerTitle: false, ), - body: generateBody() + body: Builder( + builder: (context) { + switch (serverInfo.loadStatus) { + case LoadStatus.loading: + return SizedBox( + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const CircularProgressIndicator(), + const SizedBox(height: 30), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Text( + AppLocalizations.of(context)!.loadingServerInfo, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + ) + ], + ), + ); + + case LoadStatus.loaded: + return ListView( + children: [ + CustomListTile( + title: AppLocalizations.of(context)!.dnsAddresses, + subtitle: AppLocalizations.of(context)!.seeDnsAddresses, + onTap: () { + showModal( + context: context, + builder: (context) => DnsAddressesModal( + dnsAddresses: serverInfo.data!.dnsAddresses + ) + ); + }, + ), + CustomListTile( + title: AppLocalizations.of(context)!.dnsPort, + subtitle: serverInfo.data!.dnsPort.toString(), + ), + CustomListTile( + title: AppLocalizations.of(context)!.httpPort, + subtitle: serverInfo.data!.httpPort.toString(), + ), + CustomListTile( + title: AppLocalizations.of(context)!.protectionEnabled, + subtitle: serverInfo.data!.protectionEnabled == true + ? AppLocalizations.of(context)!.yes + : AppLocalizations.of(context)!.no, + ), + CustomListTile( + title: AppLocalizations.of(context)!.dhcpAvailable, + subtitle: serverInfo.data!.dhcpAvailable == true + ? AppLocalizations.of(context)!.yes + : AppLocalizations.of(context)!.no, + ), + CustomListTile( + title: AppLocalizations.of(context)!.serverRunning, + subtitle: serverInfo.data!.running == true + ? AppLocalizations.of(context)!.yes + : AppLocalizations.of(context)!.no, + ), + CustomListTile( + title: AppLocalizations.of(context)!.serverVersion, + subtitle: serverInfo.data!.version, + ), + CustomListTile( + title: AppLocalizations.of(context)!.serverLanguage, + subtitle: serverInfo.data!.language, + ), + ] + ); + + case LoadStatus.error: + return SizedBox( + width: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Icon( + Icons.error, + color: Colors.red, + size: 50, + ), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.serverInfoNotLoaded, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ), + ); + + default: + return const SizedBox(); + } + }, + ) ); } } \ No newline at end of file diff --git a/lib/screens/settings/settings.dart b/lib/screens/settings/settings.dart index 87d23ef..0f01b44 100644 --- a/lib/screens/settings/settings.dart +++ b/lib/screens/settings/settings.dart @@ -189,7 +189,9 @@ class SettingsWidget extends StatelessWidget { title: AppLocalizations.of(context)!.dnsSettings, subtitle: AppLocalizations.of(context)!.dnsSettingsDescription, thisItem: 3, - screenToNavigate: const DnsSettings(), + screenToNavigate: DnsSettings( + splitView: twoColumns, + ), ), settingsTile( icon: Icons.security_rounded, From a0772032b49bb0ca1174d5b9d7eddc0ba85e7e88 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 29 Oct 2023 02:49:34 +0100 Subject: [PATCH 4/6] Removed unused imports --- lib/screens/filters/add_button.dart | 1 - lib/screens/logs/log_tile.dart | 2 -- lib/screens/settings/dns/dns.dart | 1 - lib/widgets/bottom_nav_bar.dart | 2 -- lib/widgets/layout.dart | 1 - 5 files changed, 7 deletions(-) diff --git a/lib/screens/filters/add_button.dart b/lib/screens/filters/add_button.dart index 7afccf0..e96bb51 100644 --- a/lib/screens/filters/add_button.dart +++ b/lib/screens/filters/add_button.dart @@ -2,7 +2,6 @@ import 'dart:io'; -import 'package:adguard_home_manager/routes/router_globals.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; diff --git a/lib/screens/logs/log_tile.dart b/lib/screens/logs/log_tile.dart index 40f3b0d..14c5262 100644 --- a/lib/screens/logs/log_tile.dart +++ b/lib/screens/logs/log_tile.dart @@ -34,8 +34,6 @@ class LogTile extends StatelessWidget { Widget build(BuildContext context) { final appConfigProvider = Provider.of(context); - final width = MediaQuery.of(context).size.width; - Widget logStatusWidget({ required IconData icon, required Color color, diff --git a/lib/screens/settings/dns/dns.dart b/lib/screens/settings/dns/dns.dart index 1aa63fd..7d0760b 100644 --- a/lib/screens/settings/dns/dns.dart +++ b/lib/screens/settings/dns/dns.dart @@ -1,6 +1,5 @@ // ignore_for_file: use_build_context_synchronously -import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_split_view/flutter_split_view.dart'; import 'package:provider/provider.dart'; diff --git a/lib/widgets/bottom_nav_bar.dart b/lib/widgets/bottom_nav_bar.dart index 0f16dcd..490138c 100644 --- a/lib/widgets/bottom_nav_bar.dart +++ b/lib/widgets/bottom_nav_bar.dart @@ -3,7 +3,6 @@ import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/config/app_screens.dart'; -import 'package:adguard_home_manager/providers/logs_provider.dart'; import 'package:adguard_home_manager/providers/servers_provider.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/models/app_screen.dart'; @@ -15,7 +14,6 @@ class BottomNavBar extends StatelessWidget { Widget build(BuildContext context) { final serversProvider = Provider.of(context); final appConfigProvider = Provider.of(context); - final logsProvider = Provider.of(context); List screens = serversProvider.selectedServer != null && serversProvider.apiClient != null ? screensServerConnected diff --git a/lib/widgets/layout.dart b/lib/widgets/layout.dart index 3513bb9..6104b6f 100644 --- a/lib/widgets/layout.dart +++ b/lib/widgets/layout.dart @@ -1,4 +1,3 @@ -import 'package:adguard_home_manager/widgets/bottom_nav_bar.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:go_router/go_router.dart'; From f966ab7fc59aa54f601511df0be239ab0b4c1bd8 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 29 Oct 2023 02:55:37 +0100 Subject: [PATCH 5/6] Removed base --- lib/base.dart | 116 -------------------------------------------- lib/main.dart | 132 +++++++++++++++++++++++++++++++++----------------- 2 files changed, 88 insertions(+), 160 deletions(-) delete mode 100644 lib/base.dart diff --git a/lib/base.dart b/lib/base.dart deleted file mode 100644 index d57dd72..0000000 --- a/lib/base.dart +++ /dev/null @@ -1,116 +0,0 @@ -// ignore_for_file: use_build_context_synchronously, depend_on_referenced_packages - -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:animations/animations.dart'; -import 'package:flutter_dotenv/flutter_dotenv.dart'; -import 'package:provider/provider.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; -import 'package:flutter/services.dart'; - -import 'package:adguard_home_manager/widgets/bottom_nav_bar.dart'; -import 'package:adguard_home_manager/widgets/menu_bar.dart'; -import 'package:adguard_home_manager/widgets/update_modal.dart'; -import 'package:adguard_home_manager/widgets/navigation_rail.dart'; - -import 'package:adguard_home_manager/providers/app_config_provider.dart'; -import 'package:adguard_home_manager/functions/check_app_updates.dart'; -import 'package:adguard_home_manager/functions/open_url.dart'; -import 'package:adguard_home_manager/models/app_screen.dart'; -import 'package:adguard_home_manager/config/app_screens.dart'; -import 'package:adguard_home_manager/providers/servers_provider.dart'; - -class Base extends StatefulWidget { - const Base({Key? key}) : super(key: key); - - @override - State createState() => _BaseState(); -} - -class _BaseState extends State with WidgetsBindingObserver { - int selectedScreen = 0; - - @override - void initState() { - WidgetsBinding.instance.addObserver(this); - - super.initState(); - - WidgetsBinding.instance.addPostFrameCallback((_) async { - final appConfigProvider = Provider.of(context, listen: false); - final result = await checkAppUpdates( - currentBuildNumber: appConfigProvider.getAppInfo!.buildNumber, - installationSource: appConfigProvider.installationSource, - setUpdateAvailable: appConfigProvider.setAppUpdatesAvailable, - isBeta: appConfigProvider.getAppInfo!.version.contains('beta'), - ); - - if (result != null && appConfigProvider.doNotRememberVersion != result.tagName) { - await showDialog( - context: context, - builder: (context) => UpdateModal( - gitHubRelease: result, - onDownload: (link, version) => openUrl(link), - ), - ); - } - }); - } - - @override - Widget build(BuildContext context) { - final serversProvider = Provider.of(context); - final appConfigProvider = Provider.of(context); - - final width = MediaQuery.of(context).size.width; - - List screens = serversProvider.selectedServer != null - ? screensServerConnected - : screensSelectServer; - - if (kDebugMode && dotenv.env['ENABLE_SENTRY'] == "true") { - Sentry.captureMessage("Debug mode"); - } - - return CustomMenuBar( - child: AnnotatedRegion( - value: SystemUiOverlayStyle( - statusBarColor: Colors.transparent, - statusBarBrightness: Theme.of(context).brightness == Brightness.light - ? Brightness.light - : Brightness.dark, - statusBarIconBrightness: Theme.of(context).brightness == Brightness.light - ? Brightness.dark - : Brightness.light, - systemNavigationBarColor: Theme.of(context).scaffoldBackgroundColor, - systemNavigationBarIconBrightness: Theme.of(context).brightness == Brightness.light - ? Brightness.dark - : Brightness.light, - ), - child: Scaffold( - body: Row( - children: [ - if (width > 900) const SideNavigationRail(), - Expanded( - child: PageTransitionSwitcher( - duration: const Duration(milliseconds: 200), - transitionBuilder: ( - (child, primaryAnimation, secondaryAnimation) => FadeThroughTransition( - animation: primaryAnimation, - secondaryAnimation: secondaryAnimation, - child: child, - ) - ), - child: SizedBox() - ), - ), - ], - ), - bottomNavigationBar: width <= 900 - ? const BottomNavBar() - : null, - ) - ), - ); - } -} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index fafb46e..1548e4a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,9 +2,10 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/foundation.dart'; -import 'package:flutter_displaymode/flutter_displaymode.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:provider/provider.dart'; +import 'package:flutter_displaymode/flutter_displaymode.dart'; import 'package:dynamic_color/dynamic_color.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:package_info_plus/package_info_plus.dart'; @@ -15,6 +16,10 @@ import 'package:window_size/window_size.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:adguard_home_manager/widgets/menu_bar.dart'; +import 'package:adguard_home_manager/functions/check_app_updates.dart'; +import 'package:adguard_home_manager/functions/open_url.dart'; +import 'package:adguard_home_manager/widgets/update_modal.dart'; import 'package:adguard_home_manager/routes/router.dart'; import 'package:adguard_home_manager/providers/logs_provider.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; @@ -175,7 +180,7 @@ class Main extends StatefulWidget { State
createState() => _MainState(); } -class _MainState extends State
{ +class _MainState extends State
with WidgetsBindingObserver { List modes = []; DisplayMode? active; DisplayMode? preferred; @@ -195,54 +200,93 @@ class _MainState extends State
{ @override void initState() { displayMode(); + + WidgetsBinding.instance.addObserver(this); + super.initState(); + + WidgetsBinding.instance.addPostFrameCallback((_) async { + final appConfigProvider = Provider.of(context, listen: false); + final result = await checkAppUpdates( + currentBuildNumber: appConfigProvider.getAppInfo!.buildNumber, + installationSource: appConfigProvider.installationSource, + setUpdateAvailable: appConfigProvider.setAppUpdatesAvailable, + isBeta: appConfigProvider.getAppInfo!.version.contains('beta'), + ); + if (result != null && appConfigProvider.doNotRememberVersion != result.tagName && mounted) { + await showDialog( + context: context, + builder: (context) => UpdateModal( + gitHubRelease: result, + onDownload: (link, version) => openUrl(link), + ), + ); + } + }); } @override Widget build(BuildContext context) { final appConfigProvider = Provider.of(context); - - return DynamicColorBuilder( - builder: (lightDynamic, darkDynamic) => MaterialApp.router( - title: 'AdGuard Home Manager', - theme: appConfigProvider.androidDeviceInfo != null && appConfigProvider.androidDeviceInfo!.version.sdkInt >= 31 - ? appConfigProvider.useDynamicColor == true - ? lightTheme(lightDynamic) - : lightThemeOldVersions(colors[appConfigProvider.staticColor]) - : lightThemeOldVersions(colors[appConfigProvider.staticColor]), - darkTheme: appConfigProvider.androidDeviceInfo != null && appConfigProvider.androidDeviceInfo!.version.sdkInt >= 31 - ? appConfigProvider.useDynamicColor == true - ? darkTheme(darkDynamic) - : darkThemeOldVersions(colors[appConfigProvider.staticColor]) - : darkThemeOldVersions(colors[appConfigProvider.staticColor]), - themeMode: appConfigProvider.selectedTheme, - debugShowCheckedModeBanner: false, - localizationsDelegates: const [ - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - AppLocalizations.delegate, - ], - supportedLocales: const [ - Locale('en', ''), - Locale('es', ''), - Locale('zh', ''), - Locale('zh', 'CN'), - Locale('pl', ''), - Locale('tr', '') - ], - scaffoldMessengerKey: scaffoldMessengerKey, - builder: (context, child) { - return MediaQuery( - data: MediaQuery.of(context).copyWith( - textScaleFactor: !(Platform.isAndroid || Platform.isIOS) - ? 0.9 - : 1.0 - ), - child: child!, - ); - }, - routerConfig: goRouter, + + return AnnotatedRegion( + value: SystemUiOverlayStyle( + statusBarColor: Colors.transparent, + statusBarBrightness: Theme.of(context).brightness == Brightness.light + ? Brightness.light + : Brightness.dark, + statusBarIconBrightness: Theme.of(context).brightness == Brightness.light + ? Brightness.dark + : Brightness.light, + systemNavigationBarColor: Theme.of(context).scaffoldBackgroundColor, + systemNavigationBarIconBrightness: Theme.of(context).brightness == Brightness.light + ? Brightness.dark + : Brightness.light, + ), + child: DynamicColorBuilder( + builder: (lightDynamic, darkDynamic) => MaterialApp.router( + title: 'AdGuard Home Manager', + theme: appConfigProvider.androidDeviceInfo != null && appConfigProvider.androidDeviceInfo!.version.sdkInt >= 31 + ? appConfigProvider.useDynamicColor == true + ? lightTheme(lightDynamic) + : lightThemeOldVersions(colors[appConfigProvider.staticColor]) + : lightThemeOldVersions(colors[appConfigProvider.staticColor]), + darkTheme: appConfigProvider.androidDeviceInfo != null && appConfigProvider.androidDeviceInfo!.version.sdkInt >= 31 + ? appConfigProvider.useDynamicColor == true + ? darkTheme(darkDynamic) + : darkThemeOldVersions(colors[appConfigProvider.staticColor]) + : darkThemeOldVersions(colors[appConfigProvider.staticColor]), + themeMode: appConfigProvider.selectedTheme, + debugShowCheckedModeBanner: false, + localizationsDelegates: const [ + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + AppLocalizations.delegate, + ], + supportedLocales: const [ + Locale('en', ''), + Locale('es', ''), + Locale('zh', ''), + Locale('zh', 'CN'), + Locale('pl', ''), + Locale('tr', '') + ], + scaffoldMessengerKey: scaffoldMessengerKey, + builder: (context, child) { + return CustomMenuBar( + child: MediaQuery( + data: MediaQuery.of(context).copyWith( + textScaleFactor: !(Platform.isAndroid || Platform.isIOS) + ? 0.9 + : 1.0 + ), + child: child!, + ), + ); + }, + routerConfig: goRouter, + ), ), ); } From 5c50a486c42e7f69e2011315289737fc60fe765f Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 29 Oct 2023 03:04:45 +0100 Subject: [PATCH 6/6] Changes --- lib/routes/routes.dart | 56 +++++++++++++++++++++++++++++---- lib/widgets/bottom_nav_bar.dart | 15 --------- 2 files changed, 50 insertions(+), 21 deletions(-) diff --git a/lib/routes/routes.dart b/lib/routes/routes.dart index a216446..9a79603 100644 --- a/lib/routes/routes.dart +++ b/lib/routes/routes.dart @@ -1,3 +1,4 @@ +import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:adguard_home_manager/screens/home/home.dart'; @@ -8,6 +9,7 @@ import 'package:adguard_home_manager/screens/logs/logs.dart'; import 'package:adguard_home_manager/screens/settings/settings.dart'; import 'package:adguard_home_manager/widgets/layout.dart'; +import 'package:adguard_home_manager/functions/desktop_mode.dart'; import 'package:adguard_home_manager/routes/router_globals.dart'; import 'package:adguard_home_manager/constants/routes_names.dart'; @@ -27,7 +29,14 @@ final List routes = [ GoRoute( parentNavigatorKey: homeNavigatorKey, path: RoutesNames.home, - builder: (context, state) => const Home(), + pageBuilder: (context, state) { + if (isDesktop(MediaQuery.of(context).size.width)) { + return const NoTransitionPage(child: Home()); + } + else { + return const MaterialPage(child: Home()); + } + }, ), ] ), @@ -37,7 +46,14 @@ final List routes = [ GoRoute( parentNavigatorKey: clientsNavigatorKey, path: RoutesNames.clients, - builder: (context, state) => const Clients(), + pageBuilder: (context, state) { + if (isDesktop(MediaQuery.of(context).size.width)) { + return const NoTransitionPage(child: Clients()); + } + else { + return const MaterialPage(child: Clients()); + } + }, ) ] ), @@ -47,7 +63,14 @@ final List routes = [ GoRoute( parentNavigatorKey: logsNavigatorKey, path: RoutesNames.logs, - builder: (context, state) => const Logs(), + pageBuilder: (context, state) { + if (isDesktop(MediaQuery.of(context).size.width)) { + return const NoTransitionPage(child: Logs()); + } + else { + return const MaterialPage(child: Logs()); + } + }, ) ] ), @@ -57,7 +80,14 @@ final List routes = [ GoRoute( parentNavigatorKey: filtersNavigatorKey, path: RoutesNames.filters, - builder: (context, state) => const Filters(), + pageBuilder: (context, state) { + if (isDesktop(MediaQuery.of(context).size.width)) { + return const NoTransitionPage(child: Filters()); + } + else { + return const MaterialPage(child: Filters()); + } + }, ) ] ), @@ -67,7 +97,14 @@ final List routes = [ GoRoute( parentNavigatorKey: settingsNavigatorKey, path: RoutesNames.settings, - builder: (context, state) => const Settings(), + pageBuilder: (context, state) { + if (isDesktop(MediaQuery.of(context).size.width)) { + return const NoTransitionPage(child: Settings()); + } + else { + return const MaterialPage(child: Settings()); + } + }, ) ] ), @@ -76,7 +113,14 @@ final List routes = [ routes: [ GoRoute( path: RoutesNames.connect, - builder: (context, state) => const Connect(), + pageBuilder: (context, state) { + if (isDesktop(MediaQuery.of(context).size.width)) { + return const NoTransitionPage(child: Connect()); + } + else { + return const MaterialPage(child: Connect()); + } + }, ) ] ), diff --git a/lib/widgets/bottom_nav_bar.dart b/lib/widgets/bottom_nav_bar.dart index 490138c..b5ff9df 100644 --- a/lib/widgets/bottom_nav_bar.dart +++ b/lib/widgets/bottom_nav_bar.dart @@ -82,21 +82,6 @@ class BottomNavBar extends StatelessWidget { ), label: translatedName(screen.name) )).toList(), - // onDestinationSelected: (value) { - // // Reset clients tab to 0 when changing screen - // if (value != 1) { - // appConfigProvider.setSelectedClientsTab(0); - // } - // // Reset logs filters when changing screen - // if (value != 2) { - // logsProvider.resetFilters(); - // } - // // Reset settings selected screen - // if (value != screens.length-1) { - // appConfigProvider.setSelectedSettingsScreen(screen: null); - // } - // appConfigProvider.setSelectedScreen(value); - // }, ); } } \ No newline at end of file