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: