mirror of
https://github.com/JGeek00/adguard-home-manager.git
synced 2025-04-20 22:09:11 +00:00
Implemented go router
This commit is contained in:
parent
2ad739ed4f
commit
621171c5b1
23 changed files with 614 additions and 234 deletions
|
@ -101,7 +101,7 @@ class _BaseState extends State<Base> with WidgetsBindingObserver {
|
|||
child: child,
|
||||
)
|
||||
),
|
||||
child: screens[appConfigProvider.selectedScreen].body,
|
||||
child: SizedBox()
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
@ -13,12 +13,10 @@ List<AppScreen> 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<AppScreen> 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()
|
||||
)
|
||||
];
|
1
lib/config/sizes.dart
Normal file
1
lib/config/sizes.dart
Normal file
|
@ -0,0 +1 @@
|
|||
const double desktopBreakpoint = 1000;
|
19
lib/constants/routes_names.dart
Normal file
19
lib/constants/routes_names.dart
Normal file
|
@ -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";
|
||||
}
|
5
lib/functions/desktop_mode.dart
Normal file
5
lib/functions/desktop_mode.dart
Normal file
|
@ -0,0 +1,5 @@
|
|||
import 'package:adguard_home_manager/config/sizes.dart';
|
||||
|
||||
bool isDesktop(double width) {
|
||||
return width > desktopBreakpoint;
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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ú"
|
||||
}
|
|
@ -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<Main> {
|
|||
final appConfigProvider = Provider.of<AppConfigProvider>(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<Main> {
|
|||
child: child!,
|
||||
);
|
||||
},
|
||||
home: const Base(),
|
||||
routerConfig: goRouter,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
});
|
||||
}
|
20
lib/routes/router.dart
Normal file
20
lib/routes/router.dart
Normal file
|
@ -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<ServersProvider>(context, listen: false);
|
||||
if (serversProvider.selectedServer == null) {
|
||||
return RoutesNames.connect;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
initialLocation: RoutesNames.home,
|
||||
routes: routes,
|
||||
);
|
10
lib/routes/router_globals.dart
Normal file
10
lib/routes/router_globals.dart
Normal file
|
@ -0,0 +1,10 @@
|
|||
import 'package:flutter/widgets.dart';
|
||||
|
||||
final GlobalKey<NavigatorState> rootNavigatorKey = GlobalKey<NavigatorState>();
|
||||
final GlobalKey<NavigatorState> connectNavigatorKey = GlobalKey<NavigatorState>();
|
||||
final GlobalKey<NavigatorState> homeNavigatorKey = GlobalKey<NavigatorState>();
|
||||
final GlobalKey<NavigatorState> clientsNavigatorKey = GlobalKey<NavigatorState>();
|
||||
final GlobalKey<NavigatorState> clientsListNavigatorKey = GlobalKey<NavigatorState>();
|
||||
final GlobalKey<NavigatorState> logsNavigatorKey = GlobalKey<NavigatorState>();
|
||||
final GlobalKey<NavigatorState> filtersNavigatorKey = GlobalKey<NavigatorState>();
|
||||
final GlobalKey<NavigatorState> settingsNavigatorKey = GlobalKey<NavigatorState>();
|
113
lib/routes/routes.dart
Normal file
113
lib/routes/routes.dart
Normal file
|
@ -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<RouteBase> 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(),
|
||||
)
|
||||
]
|
||||
),
|
||||
]
|
||||
)
|
||||
];
|
|
@ -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<AddedList> {
|
|||
}
|
||||
|
||||
return CustomTabContentList(
|
||||
noSliver: !(Platform.isAndroid || Platform.isIOS),
|
||||
listPadding: widget.splitView == true
|
||||
? const EdgeInsets.only(top: 8)
|
||||
: null,
|
||||
|
|
12
lib/screens/clients/client/client_placeholder.dart
Normal file
12
lib/screens/clients/client/client_placeholder.dart
Normal file
|
@ -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"),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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<LogsListClient> {
|
|||
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<LogsListClient> {
|
|||
serversProvider.apiClient!.getLogs(
|
||||
count: logsQuantity,
|
||||
offset: offst,
|
||||
search: '"${widget.ip}"'
|
||||
search: '"${widget.id}"'
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -90,11 +84,11 @@ class _LogsListClientState extends State<LogsListClient> {
|
|||
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<AppConfigProvider>(context, listen: false).addLog(result['log']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -116,8 +110,6 @@ class _LogsListClientState extends State<LogsListClient> {
|
|||
@override
|
||||
void initState() {
|
||||
scrollController = ScrollController()..addListener(scrollListener);
|
||||
fetchLogs(inOffset: 0);
|
||||
setState(() => previousIp = widget.ip);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
|
@ -125,15 +117,15 @@ class _LogsListClientState extends State<LogsListClient> {
|
|||
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<LogsListClient> {
|
|||
),
|
||||
);
|
||||
|
||||
case 1:
|
||||
case LoadStatus.loaded:
|
||||
if (logsData!.data.isNotEmpty) {
|
||||
return RefreshIndicator(
|
||||
onRefresh: fetchLogs,
|
||||
|
@ -217,7 +209,7 @@ class _LogsListClientState extends State<LogsListClient> {
|
|||
);
|
||||
}
|
||||
|
||||
case 2:
|
||||
case LoadStatus.error:
|
||||
return SizedBox(
|
||||
width: double.maxFinite,
|
||||
child: Column(
|
||||
|
@ -249,7 +241,7 @@ class _LogsListClientState extends State<LogsListClient> {
|
|||
|
||||
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)) ...[
|
||||
|
|
|
@ -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<Clients> createState() => _ClientsState();
|
||||
|
@ -53,9 +54,7 @@ class _ClientsState extends State<Clients> with TickerProviderStateMixin {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final serversProvider = Provider.of<ServersProvider>(context);
|
||||
final clientsProvider = Provider.of<ClientsProvider>(context);
|
||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
|
||||
|
@ -96,163 +95,119 @@ class _ClientsState extends State<Clients> 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)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -97,18 +97,10 @@ class _ClientsDesktopViewState extends State<ClientsDesktopView> 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<ClientsDesktopView> 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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<StatusProvider>(context);
|
||||
final appConfigProvider = Provider.of<AppConfigProvider>(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(
|
||||
|
|
|
@ -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);
|
||||
// },
|
||||
);
|
||||
}
|
||||
}
|
250
lib/widgets/layout.dart
Normal file
250
lib/widgets/layout.dart
Normal file
|
@ -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<Layout> createState() => _LayoutState();
|
||||
}
|
||||
|
||||
class _LayoutState extends State<Layout> {
|
||||
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<ServersProvider>(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,
|
||||
),
|
||||
),
|
||||
)
|
||||
]),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
24
pubspec.lock
24
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:
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Add table
Reference in a new issue