mirror of
https://github.com/JGeek00/adguard-home-manager.git
synced 2025-04-22 14:59:12 +00:00
Merge branch 'new-router' into beta
This commit is contained in:
commit
d14dca8bd8
54 changed files with 3121 additions and 2816 deletions
116
lib/base.dart
116
lib/base.dart
|
@ -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<Base> createState() => _BaseState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _BaseState extends State<Base> with WidgetsBindingObserver {
|
|
||||||
int selectedScreen = 0;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
WidgetsBinding.instance.addObserver(this);
|
|
||||||
|
|
||||||
super.initState();
|
|
||||||
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
|
||||||
final appConfigProvider = Provider.of<AppConfigProvider>(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<ServersProvider>(context);
|
|
||||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
|
||||||
|
|
||||||
final width = MediaQuery.of(context).size.width;
|
|
||||||
|
|
||||||
List<AppScreen> screens = serversProvider.selectedServer != null
|
|
||||||
? screensServerConnected
|
|
||||||
: screensSelectServer;
|
|
||||||
|
|
||||||
if (kDebugMode && dotenv.env['ENABLE_SENTRY'] == "true") {
|
|
||||||
Sentry.captureMessage("Debug mode");
|
|
||||||
}
|
|
||||||
|
|
||||||
return CustomMenuBar(
|
|
||||||
child: AnnotatedRegion<SystemUiOverlayStyle>(
|
|
||||||
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: screens[appConfigProvider.selectedScreen].body,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
bottomNavigationBar: width <= 900
|
|
||||||
? const BottomNavBar()
|
|
||||||
: null,
|
|
||||||
)
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,24 +1,15 @@
|
||||||
import 'package:flutter/material.dart';
|
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';
|
import 'package:adguard_home_manager/models/app_screen.dart';
|
||||||
|
|
||||||
List<AppScreen> screensSelectServer = [
|
List<AppScreen> screensSelectServer = [
|
||||||
const AppScreen(
|
const AppScreen(
|
||||||
name: "connect",
|
name: "connect",
|
||||||
icon: Icons.link_rounded,
|
icon: Icons.link_rounded,
|
||||||
body: Connect(),
|
|
||||||
),
|
),
|
||||||
const AppScreen(
|
const AppScreen(
|
||||||
name: "settings",
|
name: "settings",
|
||||||
icon: Icons.settings_rounded,
|
icon: Icons.settings_rounded,
|
||||||
body: Settings()
|
|
||||||
)
|
)
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -26,26 +17,21 @@ List<AppScreen> screensServerConnected = [
|
||||||
const AppScreen(
|
const AppScreen(
|
||||||
name: "home",
|
name: "home",
|
||||||
icon: Icons.home_rounded,
|
icon: Icons.home_rounded,
|
||||||
body: Home(),
|
|
||||||
),
|
),
|
||||||
const AppScreen(
|
const AppScreen(
|
||||||
name: "clients",
|
name: "clients",
|
||||||
icon: Icons.devices,
|
icon: Icons.devices,
|
||||||
body: Clients()
|
|
||||||
),
|
),
|
||||||
const AppScreen(
|
const AppScreen(
|
||||||
name: "logs",
|
name: "logs",
|
||||||
icon: Icons.list_alt_rounded,
|
icon: Icons.list_alt_rounded,
|
||||||
body: Logs(),
|
|
||||||
),
|
),
|
||||||
const AppScreen(
|
const AppScreen(
|
||||||
name: "filters",
|
name: "filters",
|
||||||
icon: Icons.shield_rounded,
|
icon: Icons.shield_rounded,
|
||||||
body: Filters(),
|
|
||||||
),
|
),
|
||||||
const AppScreen(
|
const AppScreen(
|
||||||
name: "settings",
|
name: "settings",
|
||||||
icon: Icons.settings_rounded,
|
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;
|
31
lib/constants/routes_names.dart
Normal file
31
lib/constants/routes_names.dart
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
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";
|
||||||
|
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";
|
||||||
|
}
|
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",
|
"showChart": "Show chart",
|
||||||
"hideChart": "Hide chart",
|
"hideChart": "Hide chart",
|
||||||
"showTopItemsChart": "Show top items 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",
|
"showChart": "Mostrar gráfico",
|
||||||
"hideChart": "Ocultar gráfico",
|
"hideChart": "Ocultar gráfico",
|
||||||
"showTopItemsChart": "Mostrar gráfico en top de items",
|
"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ú"
|
||||||
}
|
}
|
133
lib/main.dart
133
lib/main.dart
|
@ -2,9 +2,10 @@ import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/foundation.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:flutter_dotenv/flutter_dotenv.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:flutter_displaymode/flutter_displaymode.dart';
|
||||||
import 'package:dynamic_color/dynamic_color.dart';
|
import 'package:dynamic_color/dynamic_color.dart';
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
|
@ -15,8 +16,11 @@ import 'package:window_size/window_size.dart';
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
import 'package:adguard_home_manager/base.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/logs_provider.dart';
|
||||||
import 'package:adguard_home_manager/providers/app_config_provider.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/clients_provider.dart';
|
||||||
|
@ -176,7 +180,7 @@ class Main extends StatefulWidget {
|
||||||
State<Main> createState() => _MainState();
|
State<Main> createState() => _MainState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MainState extends State<Main> {
|
class _MainState extends State<Main> with WidgetsBindingObserver {
|
||||||
List<DisplayMode> modes = <DisplayMode>[];
|
List<DisplayMode> modes = <DisplayMode>[];
|
||||||
DisplayMode? active;
|
DisplayMode? active;
|
||||||
DisplayMode? preferred;
|
DisplayMode? preferred;
|
||||||
|
@ -196,54 +200,93 @@ class _MainState extends State<Main> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
displayMode();
|
displayMode();
|
||||||
|
|
||||||
|
WidgetsBinding.instance.addObserver(this);
|
||||||
|
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
|
final appConfigProvider = Provider.of<AppConfigProvider>(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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||||
|
|
||||||
return DynamicColorBuilder(
|
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||||
builder: (lightDynamic, darkDynamic) => MaterialApp(
|
value: SystemUiOverlayStyle(
|
||||||
title: 'AdGuard Home Manager',
|
statusBarColor: Colors.transparent,
|
||||||
theme: appConfigProvider.androidDeviceInfo != null && appConfigProvider.androidDeviceInfo!.version.sdkInt >= 31
|
statusBarBrightness: Theme.of(context).brightness == Brightness.light
|
||||||
? appConfigProvider.useDynamicColor == true
|
? Brightness.light
|
||||||
? lightTheme(lightDynamic)
|
: Brightness.dark,
|
||||||
: lightThemeOldVersions(colors[appConfigProvider.staticColor])
|
statusBarIconBrightness: Theme.of(context).brightness == Brightness.light
|
||||||
: lightThemeOldVersions(colors[appConfigProvider.staticColor]),
|
? Brightness.dark
|
||||||
darkTheme: appConfigProvider.androidDeviceInfo != null && appConfigProvider.androidDeviceInfo!.version.sdkInt >= 31
|
: Brightness.light,
|
||||||
? appConfigProvider.useDynamicColor == true
|
systemNavigationBarColor: Theme.of(context).scaffoldBackgroundColor,
|
||||||
? darkTheme(darkDynamic)
|
systemNavigationBarIconBrightness: Theme.of(context).brightness == Brightness.light
|
||||||
: darkThemeOldVersions(colors[appConfigProvider.staticColor])
|
? Brightness.dark
|
||||||
: darkThemeOldVersions(colors[appConfigProvider.staticColor]),
|
: Brightness.light,
|
||||||
themeMode: appConfigProvider.selectedTheme,
|
),
|
||||||
debugShowCheckedModeBanner: false,
|
child: DynamicColorBuilder(
|
||||||
localizationsDelegates: const [
|
builder: (lightDynamic, darkDynamic) => MaterialApp.router(
|
||||||
GlobalMaterialLocalizations.delegate,
|
title: 'AdGuard Home Manager',
|
||||||
GlobalWidgetsLocalizations.delegate,
|
theme: appConfigProvider.androidDeviceInfo != null && appConfigProvider.androidDeviceInfo!.version.sdkInt >= 31
|
||||||
GlobalCupertinoLocalizations.delegate,
|
? appConfigProvider.useDynamicColor == true
|
||||||
AppLocalizations.delegate,
|
? lightTheme(lightDynamic)
|
||||||
],
|
: lightThemeOldVersions(colors[appConfigProvider.staticColor])
|
||||||
supportedLocales: const [
|
: lightThemeOldVersions(colors[appConfigProvider.staticColor]),
|
||||||
Locale('en', ''),
|
darkTheme: appConfigProvider.androidDeviceInfo != null && appConfigProvider.androidDeviceInfo!.version.sdkInt >= 31
|
||||||
Locale('es', ''),
|
? appConfigProvider.useDynamicColor == true
|
||||||
Locale('zh', ''),
|
? darkTheme(darkDynamic)
|
||||||
Locale('zh', 'CN'),
|
: darkThemeOldVersions(colors[appConfigProvider.staticColor])
|
||||||
Locale('pl', ''),
|
: darkThemeOldVersions(colors[appConfigProvider.staticColor]),
|
||||||
Locale('tr', '')
|
themeMode: appConfigProvider.selectedTheme,
|
||||||
],
|
debugShowCheckedModeBanner: false,
|
||||||
scaffoldMessengerKey: scaffoldMessengerKey,
|
localizationsDelegates: const [
|
||||||
builder: (context, child) {
|
GlobalMaterialLocalizations.delegate,
|
||||||
return MediaQuery(
|
GlobalWidgetsLocalizations.delegate,
|
||||||
data: MediaQuery.of(context).copyWith(
|
GlobalCupertinoLocalizations.delegate,
|
||||||
textScaleFactor: !(Platform.isAndroid || Platform.isIOS)
|
AppLocalizations.delegate,
|
||||||
? 0.9
|
],
|
||||||
: 1.0
|
supportedLocales: const [
|
||||||
),
|
Locale('en', ''),
|
||||||
child: child!,
|
Locale('es', ''),
|
||||||
);
|
Locale('zh', ''),
|
||||||
},
|
Locale('zh', 'CN'),
|
||||||
home: const Base(),
|
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,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,14 +4,12 @@ class AppScreen {
|
||||||
final String name;
|
final String name;
|
||||||
final IconData icon;
|
final IconData icon;
|
||||||
final PreferredSizeWidget? appBar;
|
final PreferredSizeWidget? appBar;
|
||||||
final Widget body;
|
|
||||||
final Widget? fab;
|
final Widget? fab;
|
||||||
|
|
||||||
const AppScreen({
|
const AppScreen({
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.icon,
|
required this.icon,
|
||||||
this.appBar,
|
this.appBar,
|
||||||
required this.body,
|
|
||||||
this.fab
|
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> logsNavigatorKey = GlobalKey<NavigatorState>();
|
||||||
|
final GlobalKey<NavigatorState> filtersNavigatorKey = GlobalKey<NavigatorState>();
|
||||||
|
final GlobalKey<NavigatorState> settingsNavigatorKey = GlobalKey<NavigatorState>();
|
||||||
|
final GlobalKey<NavigatorState> settingsListNavigatorKey = GlobalKey<NavigatorState>();
|
129
lib/routes/routes.dart
Normal file
129
lib/routes/routes.dart
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
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/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';
|
||||||
|
|
||||||
|
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,
|
||||||
|
pageBuilder: (context, state) {
|
||||||
|
if (isDesktop(MediaQuery.of(context).size.width)) {
|
||||||
|
return const NoTransitionPage(child: Home());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return const MaterialPage(child: Home());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
StatefulShellBranch(
|
||||||
|
navigatorKey: clientsNavigatorKey,
|
||||||
|
routes: [
|
||||||
|
GoRoute(
|
||||||
|
parentNavigatorKey: clientsNavigatorKey,
|
||||||
|
path: RoutesNames.clients,
|
||||||
|
pageBuilder: (context, state) {
|
||||||
|
if (isDesktop(MediaQuery.of(context).size.width)) {
|
||||||
|
return const NoTransitionPage(child: Clients());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return const MaterialPage(child: Clients());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
]
|
||||||
|
),
|
||||||
|
StatefulShellBranch(
|
||||||
|
navigatorKey: logsNavigatorKey,
|
||||||
|
routes: [
|
||||||
|
GoRoute(
|
||||||
|
parentNavigatorKey: logsNavigatorKey,
|
||||||
|
path: RoutesNames.logs,
|
||||||
|
pageBuilder: (context, state) {
|
||||||
|
if (isDesktop(MediaQuery.of(context).size.width)) {
|
||||||
|
return const NoTransitionPage(child: Logs());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return const MaterialPage(child: Logs());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
]
|
||||||
|
),
|
||||||
|
StatefulShellBranch(
|
||||||
|
navigatorKey: filtersNavigatorKey,
|
||||||
|
routes: [
|
||||||
|
GoRoute(
|
||||||
|
parentNavigatorKey: filtersNavigatorKey,
|
||||||
|
path: RoutesNames.filters,
|
||||||
|
pageBuilder: (context, state) {
|
||||||
|
if (isDesktop(MediaQuery.of(context).size.width)) {
|
||||||
|
return const NoTransitionPage(child: Filters());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return const MaterialPage(child: Filters());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
]
|
||||||
|
),
|
||||||
|
StatefulShellBranch(
|
||||||
|
navigatorKey: settingsNavigatorKey,
|
||||||
|
routes: [
|
||||||
|
GoRoute(
|
||||||
|
parentNavigatorKey: settingsNavigatorKey,
|
||||||
|
path: RoutesNames.settings,
|
||||||
|
pageBuilder: (context, state) {
|
||||||
|
if (isDesktop(MediaQuery.of(context).size.width)) {
|
||||||
|
return const NoTransitionPage(child: Settings());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return const MaterialPage(child: Settings());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
]
|
||||||
|
),
|
||||||
|
StatefulShellBranch(
|
||||||
|
navigatorKey: connectNavigatorKey,
|
||||||
|
routes: [
|
||||||
|
GoRoute(
|
||||||
|
path: RoutesNames.connect,
|
||||||
|
pageBuilder: (context, state) {
|
||||||
|
if (isDesktop(MediaQuery.of(context).size.width)) {
|
||||||
|
return const NoTransitionPage(child: Connect());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return const MaterialPage(child: Connect());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
]
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
];
|
|
@ -1,7 +1,5 @@
|
||||||
// ignore_for_file: use_build_context_synchronously
|
// ignore_for_file: use_build_context_synchronously
|
||||||
|
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:animations/animations.dart';
|
import 'package:animations/animations.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
|
@ -157,7 +155,6 @@ class _AddedListState extends State<AddedList> {
|
||||||
}
|
}
|
||||||
|
|
||||||
return CustomTabContentList(
|
return CustomTabContentList(
|
||||||
noSliver: !(Platform.isAndroid || Platform.isIOS),
|
|
||||||
listPadding: widget.splitView == true
|
listPadding: widget.splitView == true
|
||||||
? const EdgeInsets.only(top: 8)
|
? const EdgeInsets.only(top: 8)
|
||||||
: null,
|
: 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"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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_tile.dart';
|
||||||
import 'package:adguard_home_manager/screens/logs/log_details_screen.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/models/logs.dart';
|
||||||
import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
||||||
import 'package:adguard_home_manager/providers/servers_provider.dart';
|
import 'package:adguard_home_manager/providers/servers_provider.dart';
|
||||||
|
@ -17,13 +18,15 @@ class LogsListClient extends StatefulWidget {
|
||||||
final String? name;
|
final String? name;
|
||||||
final ServersProvider serversProvider;
|
final ServersProvider serversProvider;
|
||||||
final AppConfigProvider appConfigProvider;
|
final AppConfigProvider appConfigProvider;
|
||||||
|
final bool splitView;
|
||||||
|
|
||||||
const LogsListClient({
|
const LogsListClient({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.ip,
|
required this.ip,
|
||||||
this.name,
|
this.name,
|
||||||
required this.serversProvider,
|
required this.serversProvider,
|
||||||
required this.appConfigProvider
|
required this.appConfigProvider,
|
||||||
|
required this.splitView,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -131,126 +134,13 @@ class _LogsListClientState extends State<LogsListClient> {
|
||||||
setState(() => previousIp = widget.ip);
|
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
|
|
||||||
)
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
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(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(widget.name != null && widget.name != '' ? widget.name! : widget.ip),
|
title: Text(widget.name != null && widget.name != '' ? widget.name! : widget.ip),
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
|
surfaceTintColor: isDesktop(MediaQuery.of(context).size.width)
|
||||||
|
? Colors.transparent
|
||||||
|
: null,
|
||||||
actions: [
|
actions: [
|
||||||
if (!(Platform.isAndroid || Platform.isIOS)) ...[
|
if (!(Platform.isAndroid || Platform.isIOS)) ...[
|
||||||
IconButton(
|
IconButton(
|
||||||
|
@ -262,7 +152,124 @@ class _LogsListClientState extends State<LogsListClient> {
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
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();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,21 +1,10 @@
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_split_view/flutter_split_view.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:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
import 'package:adguard_home_manager/screens/clients/clients_list.dart';
|
import 'package:adguard_home_manager/screens/clients/clients_lists.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/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/models/clients.dart';
|
||||||
import 'package:adguard_home_manager/providers/servers_provider.dart';
|
|
||||||
|
|
||||||
class Clients extends StatefulWidget {
|
class Clients extends StatefulWidget {
|
||||||
const Clients({Key? key}) : super(key: key);
|
const Clients({Key? key}) : super(key: key);
|
||||||
|
@ -25,234 +14,42 @@ class Clients extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ClientsState extends State<Clients> with TickerProviderStateMixin {
|
class _ClientsState extends State<Clients> with TickerProviderStateMixin {
|
||||||
late TabController tabController;
|
|
||||||
final ScrollController scrollController = ScrollController();
|
|
||||||
|
|
||||||
bool searchMode = false;
|
|
||||||
final TextEditingController searchController = TextEditingController();
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
final clientsProvider = Provider.of<ClientsProvider>(context, listen: false);
|
|
||||||
clientsProvider.fetchClients(updateLoading: true);
|
|
||||||
|
|
||||||
super.initState();
|
|
||||||
tabController = TabController(
|
|
||||||
initialIndex: 0,
|
|
||||||
length: 2,
|
|
||||||
vsync: this,
|
|
||||||
);
|
|
||||||
tabController.addListener(
|
|
||||||
() => Provider.of<AppConfigProvider>(context, listen: false).setSelectedClientsTab(tabController.index)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<AutoClient> generateClientsList(List<AutoClient> clients, List<String> ips) {
|
List<AutoClient> generateClientsList(List<AutoClient> clients, List<String> ips) {
|
||||||
return clients.where((client) => ips.contains(client.ip)).toList();
|
return clients.where((client) => ips.contains(client.ip)).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final serversProvider = Provider.of<ServersProvider>(context);
|
return LayoutBuilder(
|
||||||
final clientsProvider = Provider.of<ClientsProvider>(context);
|
builder: (context, constraints) {
|
||||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
if (constraints.maxWidth > 1000) {
|
||||||
|
return SplitView.material(
|
||||||
final width = MediaQuery.of(context).size.width;
|
hideDivider: true,
|
||||||
|
flexWidth: const FlexWidth(mainViewFlexWidth: 1, secondaryViewFlexWidth: 2),
|
||||||
PreferredSizeWidget tabBar() {
|
placeholder: Center(
|
||||||
return TabBar(
|
child: Padding(
|
||||||
controller: tabController,
|
padding: const EdgeInsets.all(24),
|
||||||
unselectedLabelColor: Theme.of(context).colorScheme.onSurfaceVariant,
|
child: Text(
|
||||||
tabs: [
|
AppLocalizations.of(context)!.selectClientLeftColumn,
|
||||||
Tab(
|
textAlign: TextAlign.center,
|
||||||
child: Row(
|
style: TextStyle(
|
||||||
mainAxisSize: MainAxisSize.min,
|
fontSize: 24,
|
||||||
children: [
|
color: Theme.of(context).colorScheme.onSurfaceVariant
|
||||||
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) => Navigator.push(context, MaterialPageRoute(
|
|
||||||
builder: (context) => LogsListClient(
|
|
||||||
ip: client.ip,
|
|
||||||
serversProvider: serversProvider,
|
|
||||||
appConfigProvider: appConfigProvider
|
|
||||||
)
|
|
||||||
)),
|
|
||||||
splitView: false,
|
|
||||||
sliver: sliver,
|
|
||||||
),
|
|
||||||
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,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
child: const ClientsLists(
|
||||||
),
|
splitView: true,
|
||||||
child: ClientsDesktopView(
|
)
|
||||||
serversProvider: serversProvider,
|
);
|
||||||
appConfigProvider: appConfigProvider,
|
}
|
||||||
)
|
else {
|
||||||
);
|
return const ClientsLists(
|
||||||
}
|
splitView: false,
|
||||||
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)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,244 +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<ClientsDesktopView> createState() => _ClientsDesktopViewState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ClientsDesktopViewState extends State<ClientsDesktopView> 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<ServersProvider>(context);
|
|
||||||
final clientsProvider = Provider.of<ClientsProvider>(context);
|
|
||||||
final appConfigProvider = Provider.of<AppConfigProvider>(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;
|
|
||||||
SplitView.of(context).setSecondary(
|
|
||||||
LogsListClient(
|
|
||||||
ip: client.ip,
|
|
||||||
name: client.name,
|
|
||||||
serversProvider: serversProvider,
|
|
||||||
appConfigProvider: appConfigProvider,
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
selectedClient: selectedActiveClient,
|
|
||||||
splitView: true,
|
|
||||||
sliver: sliver,
|
|
||||||
),
|
|
||||||
AddedList(
|
|
||||||
scrollController: scrollController,
|
|
||||||
data: clientsProvider.loadStatus == LoadStatus.loaded
|
|
||||||
? clientsProvider.filteredAddedClients : [],
|
|
||||||
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,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -10,21 +10,17 @@ import 'package:adguard_home_manager/providers/clients_provider.dart';
|
||||||
import 'package:adguard_home_manager/models/clients.dart';
|
import 'package:adguard_home_manager/models/clients.dart';
|
||||||
|
|
||||||
class ClientsList extends StatelessWidget {
|
class ClientsList extends StatelessWidget {
|
||||||
final ScrollController scrollController;
|
|
||||||
final List<AutoClient> data;
|
final List<AutoClient> data;
|
||||||
final void Function(AutoClient) onClientSelected;
|
final void Function(AutoClient) onClientSelected;
|
||||||
final AutoClient? selectedClient;
|
final AutoClient? selectedClient;
|
||||||
final bool splitView;
|
final bool splitView;
|
||||||
final bool sliver;
|
|
||||||
|
|
||||||
const ClientsList({
|
const ClientsList({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.scrollController,
|
|
||||||
required this.data,
|
required this.data,
|
||||||
required this.onClientSelected,
|
required this.onClientSelected,
|
||||||
this.selectedClient,
|
this.selectedClient,
|
||||||
required this.splitView,
|
required this.splitView,
|
||||||
required this.sliver
|
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -35,7 +31,6 @@ class ClientsList extends StatelessWidget {
|
||||||
listPadding: splitView == true
|
listPadding: splitView == true
|
||||||
? const EdgeInsets.only(top: 8)
|
? const EdgeInsets.only(top: 8)
|
||||||
: null,
|
: null,
|
||||||
noSliver: !sliver,
|
|
||||||
loadingGenerator: () => SizedBox(
|
loadingGenerator: () => SizedBox(
|
||||||
width: double.maxFinite,
|
width: double.maxFinite,
|
||||||
height: MediaQuery.of(context).size.height-171,
|
height: MediaQuery.of(context).size.height-171,
|
||||||
|
|
222
lib/screens/clients/clients_lists.dart
Normal file
222
lib/screens/clients/clients_lists.dart
Normal file
|
@ -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<ClientsLists> createState() => _ClientsListsState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ClientsListsState extends State<ClientsLists> 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<ClientsProvider>(context, listen: false);
|
||||||
|
clientsProvider.fetchClients(updateLoading: true);
|
||||||
|
|
||||||
|
super.initState();
|
||||||
|
tabController = TabController(
|
||||||
|
initialIndex: 0,
|
||||||
|
length: 2,
|
||||||
|
vsync: this,
|
||||||
|
);
|
||||||
|
tabController.addListener(
|
||||||
|
() => Provider.of<AppConfigProvider>(context, listen: false).setSelectedClientsTab(tabController.index)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final serversProvider = Provider.of<ServersProvider>(context);
|
||||||
|
final clientsProvider = Provider.of<ClientsProvider>(context);
|
||||||
|
final appConfigProvider = Provider.of<AppConfigProvider>(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,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -134,6 +134,7 @@ class AddFiltersButton extends StatelessWidget {
|
||||||
else {
|
else {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
|
useRootNavigator: true,
|
||||||
builder: (ctx) => AddListModal(
|
builder: (ctx) => AddListModal(
|
||||||
type: type,
|
type: type,
|
||||||
onConfirm: confirmAddList,
|
onConfirm: confirmAddList,
|
||||||
|
|
|
@ -87,6 +87,7 @@ class _FiltersState extends State<Filters> {
|
||||||
else {
|
else {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
|
useRootNavigator: true,
|
||||||
builder: (context) => const CheckHostModal(
|
builder: (context) => const CheckHostModal(
|
||||||
dialog: false,
|
dialog: false,
|
||||||
),
|
),
|
||||||
|
@ -269,6 +270,7 @@ class _FiltersState extends State<Filters> {
|
||||||
else {
|
else {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
|
useRootNavigator: true,
|
||||||
builder: (context) => UpdateIntervalListsModal(
|
builder: (context) => UpdateIntervalListsModal(
|
||||||
interval: filteringProvider.filtering!.interval,
|
interval: filteringProvider.filtering!.interval,
|
||||||
onChange: setUpdateFrequency,
|
onChange: setUpdateFrequency,
|
||||||
|
@ -324,20 +326,24 @@ class _FiltersState extends State<Filters> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (width > 1200) {
|
return LayoutBuilder(
|
||||||
return FiltersTripleColumn(
|
builder: (context, constraints) {
|
||||||
onRemoveCustomRule: openRemoveCustomRuleModal,
|
if (constraints.maxWidth > 900) {
|
||||||
onOpenDetailsModal: openListDetails,
|
return FiltersTripleColumn(
|
||||||
actions: actions(),
|
onRemoveCustomRule: openRemoveCustomRuleModal,
|
||||||
);
|
onOpenDetailsModal: openListDetails,
|
||||||
}
|
actions: actions(),
|
||||||
else {
|
);
|
||||||
return FiltersTabsView(
|
}
|
||||||
appConfigProvider: appConfigProvider,
|
else {
|
||||||
actions: actions(),
|
return FiltersTabsView(
|
||||||
onRemoveCustomRule: openRemoveCustomRuleModal,
|
appConfigProvider: appConfigProvider,
|
||||||
onOpenDetailsModal: openListDetails,
|
actions: actions(),
|
||||||
);
|
onRemoveCustomRule: openRemoveCustomRuleModal,
|
||||||
}
|
onOpenDetailsModal: openListDetails,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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/custom_rules_list.dart';
|
||||||
import 'package:adguard_home_manager/screens/filters/filters_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/providers/filtering_provider.dart';
|
||||||
import 'package:adguard_home_manager/constants/enums.dart';
|
import 'package:adguard_home_manager/constants/enums.dart';
|
||||||
import 'package:adguard_home_manager/models/filtering.dart';
|
import 'package:adguard_home_manager/models/filtering.dart';
|
||||||
|
@ -47,6 +48,8 @@ class _FiltersTabsViewState extends State<FiltersTabsView> with TickerProviderSt
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final filteringProvider = Provider.of<FilteringProvider>(context);
|
final filteringProvider = Provider.of<FilteringProvider>(context);
|
||||||
|
|
||||||
|
final width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
return DefaultTabController(
|
return DefaultTabController(
|
||||||
length: 3,
|
length: 3,
|
||||||
child: NestedScrollView(
|
child: NestedScrollView(
|
||||||
|
@ -62,6 +65,7 @@ class _FiltersTabsViewState extends State<FiltersTabsView> with TickerProviderSt
|
||||||
forceElevated: innerBoxIsScrolled,
|
forceElevated: innerBoxIsScrolled,
|
||||||
centerTitle: false,
|
centerTitle: false,
|
||||||
actions: widget.actions,
|
actions: widget.actions,
|
||||||
|
surfaceTintColor: isDesktop(width) ? Colors.transparent : null,
|
||||||
bottom: TabBar(
|
bottom: TabBar(
|
||||||
controller: tabController,
|
controller: tabController,
|
||||||
isScrollable: true,
|
isScrollable: true,
|
||||||
|
|
|
@ -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/widgets/options_modal.dart';
|
||||||
|
|
||||||
import 'package:adguard_home_manager/constants/enums.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/models/menu_option.dart';
|
||||||
import 'package:adguard_home_manager/functions/snackbar.dart';
|
import 'package:adguard_home_manager/functions/snackbar.dart';
|
||||||
import 'package:adguard_home_manager/functions/copy_clipboard.dart';
|
import 'package:adguard_home_manager/functions/copy_clipboard.dart';
|
||||||
|
@ -38,6 +39,8 @@ class FiltersTripleColumn extends StatelessWidget {
|
||||||
final filteringProvider = Provider.of<FilteringProvider>(context);
|
final filteringProvider = Provider.of<FilteringProvider>(context);
|
||||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||||
|
|
||||||
|
final width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
Widget? generateSubtitle(String rule) {
|
Widget? generateSubtitle(String rule) {
|
||||||
final allowRegex = RegExp(r'^@@.*$');
|
final allowRegex = RegExp(r'^@@.*$');
|
||||||
final blockRegex = RegExp(r'^\|\|.*$');
|
final blockRegex = RegExp(r'^\|\|.*$');
|
||||||
|
@ -325,6 +328,7 @@ class FiltersTripleColumn extends StatelessWidget {
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
|
surfaceTintColor: isDesktop(width) ? Colors.transparent : null,
|
||||||
title: Text(AppLocalizations.of(context)!.filters),
|
title: Text(AppLocalizations.of(context)!.filters),
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
|
|
|
@ -219,6 +219,7 @@ class _ListDetailsScreenState extends State<ListDetailsScreen> {
|
||||||
else {
|
else {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
|
useRootNavigator: true,
|
||||||
builder: (ctx) => AddListModal(
|
builder: (ctx) => AddListModal(
|
||||||
list: list,
|
list: list,
|
||||||
type: widget.type,
|
type: widget.type,
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:adguard_home_manager/functions/desktop_mode.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.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 statusProvider = Provider.of<StatusProvider>(context);
|
||||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||||
|
|
||||||
|
final width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
final Server? server = serversProvider.selectedServer;
|
final Server? server = serversProvider.selectedServer;
|
||||||
|
|
||||||
void navigateServers() {
|
void navigateServers() {
|
||||||
|
@ -40,6 +43,7 @@ class HomeAppBar extends StatelessWidget {
|
||||||
floating: true,
|
floating: true,
|
||||||
centerTitle: false,
|
centerTitle: false,
|
||||||
forceElevated: innerBoxScrolled,
|
forceElevated: innerBoxScrolled,
|
||||||
|
surfaceTintColor: isDesktop(width) ? Colors.transparent : null,
|
||||||
leading: Stack(
|
leading: Stack(
|
||||||
children: [
|
children: [
|
||||||
Center(
|
Center(
|
||||||
|
|
|
@ -28,6 +28,7 @@ class HomeFab extends StatelessWidget {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
|
useRootNavigator: true,
|
||||||
builder: (context) => const ManagementModal(
|
builder: (context) => const ManagementModal(
|
||||||
dialog: false,
|
dialog: false,
|
||||||
),
|
),
|
||||||
|
|
|
@ -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/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/functions/open_url.dart';
|
||||||
import 'package:adguard_home_manager/constants/urls.dart';
|
import 'package:adguard_home_manager/constants/urls.dart';
|
||||||
import 'package:adguard_home_manager/classes/process_modal.dart';
|
import 'package:adguard_home_manager/classes/process_modal.dart';
|
||||||
|
@ -34,6 +35,8 @@ class LogDetailsScreen extends StatelessWidget {
|
||||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||||
final statusProvider = Provider.of<StatusProvider>(context);
|
final statusProvider = Provider.of<StatusProvider>(context);
|
||||||
|
|
||||||
|
final width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
Filter? getList(int id) {
|
Filter? getList(int id) {
|
||||||
try {
|
try {
|
||||||
return statusProvider.filteringStatus!.filters.firstWhere((filter) => filter.id == id, orElse: () {
|
return statusProvider.filteringStatus!.filters.firstWhere((filter) => filter.id == id, orElse: () {
|
||||||
|
@ -288,6 +291,7 @@ class LogDetailsScreen extends StatelessWidget {
|
||||||
floating: true,
|
floating: true,
|
||||||
centerTitle: false,
|
centerTitle: false,
|
||||||
forceElevated: innerBoxIsScrolled,
|
forceElevated: innerBoxIsScrolled,
|
||||||
|
surfaceTintColor: isDesktop(width) ? Colors.transparent : null,
|
||||||
title: Text(AppLocalizations.of(context)!.logDetails),
|
title: Text(AppLocalizations.of(context)!.logDetails),
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
|
|
|
@ -17,6 +17,7 @@ class LogTile extends StatelessWidget {
|
||||||
final bool? isLogSelected;
|
final bool? isLogSelected;
|
||||||
final void Function(Log) onLogTap;
|
final void Function(Log) onLogTap;
|
||||||
final bool? useAlwaysNormalTile;
|
final bool? useAlwaysNormalTile;
|
||||||
|
final bool twoColumns;
|
||||||
|
|
||||||
const LogTile({
|
const LogTile({
|
||||||
Key? key,
|
Key? key,
|
||||||
|
@ -25,15 +26,14 @@ class LogTile extends StatelessWidget {
|
||||||
required this.index,
|
required this.index,
|
||||||
this.isLogSelected,
|
this.isLogSelected,
|
||||||
required this.onLogTap,
|
required this.onLogTap,
|
||||||
this.useAlwaysNormalTile
|
this.useAlwaysNormalTile,
|
||||||
|
required this.twoColumns,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||||
|
|
||||||
final width = MediaQuery.of(context).size.width;
|
|
||||||
|
|
||||||
Widget logStatusWidget({
|
Widget logStatusWidget({
|
||||||
required IconData icon,
|
required IconData icon,
|
||||||
required Color color,
|
required Color color,
|
||||||
|
@ -83,7 +83,7 @@ class LogTile extends StatelessWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (width > 1100 && !(useAlwaysNormalTile == true)) {
|
if (twoColumns && !(useAlwaysNormalTile == true)) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
|
|
|
@ -1,27 +1,10 @@
|
||||||
// ignore_for_file: use_build_context_synchronously
|
// 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/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 {
|
class Logs extends StatefulWidget {
|
||||||
const Logs({Key? key}) : super(key: key);
|
const Logs({Key? key}) : super(key: key);
|
||||||
|
@ -31,554 +14,48 @@ class Logs extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _LogsState extends State<Logs> {
|
class _LogsState extends State<Logs> {
|
||||||
bool showDivider = true;
|
Log? _selectedLog;
|
||||||
|
|
||||||
Log? selectedLog;
|
|
||||||
|
|
||||||
void fetchFilteringRules() async {
|
|
||||||
final appConfigProvider = Provider.of<AppConfigProvider>(context, listen: false);
|
|
||||||
final statusProvider = Provider.of<StatusProvider>(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<ClientsProvider>(context, listen: false);
|
|
||||||
final appConfigProvider = Provider.of<AppConfigProvider>(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<LogsProvider>(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<LogsProvider>(context, listen: false);
|
|
||||||
|
|
||||||
logsProvider.fetchLogs(inOffset: 0);
|
|
||||||
fetchFilteringRules();
|
|
||||||
fetchClients();
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final serversProvider = Provider.of<ServersProvider>(context);
|
|
||||||
final statusProvider = Provider.of<StatusProvider>(context);
|
|
||||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
|
||||||
final logsProvider = Provider.of<LogsProvider>(context);
|
|
||||||
|
|
||||||
final width = MediaQuery.of(context).size.width;
|
|
||||||
|
|
||||||
void updateConfig(Map<String, dynamic> 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() {
|
return LayoutBuilder(
|
||||||
if (width > 700 || !(Platform.isAndroid || Platform.isIOS)) {
|
builder: (context, constraints) {
|
||||||
showDialog(
|
if (constraints.maxWidth > 1000) {
|
||||||
context: context,
|
return Material(
|
||||||
builder: (context) => const LogsFiltersModal(
|
color: Colors.transparent,
|
||||||
dialog: true,
|
child: Row(
|
||||||
),
|
children: [
|
||||||
barrierDismissible: false
|
Expanded(
|
||||||
);
|
flex: 2,
|
||||||
}
|
child: LogsListWidget(
|
||||||
else {
|
twoColumns: true,
|
||||||
showModalBottomSheet(
|
selectedLog: _selectedLog,
|
||||||
context: context,
|
onLogSelected: (log) => setState(() => _selectedLog = log),
|
||||||
builder: (context) => const LogsFiltersModal(
|
|
||||||
dialog: false,
|
|
||||||
),
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
isScrollControlled: true
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final Map<String, String> 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,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
Expanded(
|
||||||
)
|
flex: 3,
|
||||||
);
|
child: _selectedLog != null
|
||||||
|
? LogDetailsScreen(
|
||||||
case LoadStatus.error:
|
log: _selectedLog!,
|
||||||
return SafeArea(
|
dialog: false,
|
||||||
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,
|
|
||||||
)
|
)
|
||||||
: const SizedBox(),
|
: 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()
|
|
||||||
),
|
),
|
||||||
Expanded(
|
);
|
||||||
flex: 2,
|
}
|
||||||
child: selectedLog != null
|
else {
|
||||||
? LogDetailsScreen(
|
return LogsListWidget(
|
||||||
log: selectedLog!,
|
twoColumns: false,
|
||||||
dialog: false,
|
selectedLog: _selectedLog,
|
||||||
)
|
onLogSelected: (log) => setState(() => _selectedLog = log),
|
||||||
: const SizedBox()
|
);
|
||||||
)
|
}
|
||||||
],
|
},
|
||||||
),
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return logsScreen();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -89,6 +89,7 @@ class _LogsFiltersModalWidgetState extends State<LogsFiltersModalWidget> {
|
||||||
else {
|
else {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
|
useRootNavigator: true,
|
||||||
builder: (context) => FilterStatusModal(
|
builder: (context) => FilterStatusModal(
|
||||||
value: logsProvider.selectedResultStatus,
|
value: logsProvider.selectedResultStatus,
|
||||||
dialog: false,
|
dialog: false,
|
||||||
|
@ -113,6 +114,7 @@ class _LogsFiltersModalWidgetState extends State<LogsFiltersModalWidget> {
|
||||||
else {
|
else {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
|
useRootNavigator: true,
|
||||||
builder: (context) => ClientsModal(
|
builder: (context) => ClientsModal(
|
||||||
value: logsProvider.selectedClients,
|
value: logsProvider.selectedClients,
|
||||||
dialog: false,
|
dialog: false,
|
||||||
|
|
568
lib/screens/logs/logs_list.dart
Normal file
568
lib/screens/logs/logs_list.dart
Normal file
|
@ -0,0 +1,568 @@
|
||||||
|
// 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<LogsListWidget> createState() => _LogsListWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LogsListWidgetState extends State<LogsListWidget> {
|
||||||
|
bool showDivider = true;
|
||||||
|
|
||||||
|
void fetchFilteringRules() async {
|
||||||
|
final appConfigProvider = Provider.of<AppConfigProvider>(context, listen: false);
|
||||||
|
final statusProvider = Provider.of<StatusProvider>(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<ClientsProvider>(context, listen: false);
|
||||||
|
final appConfigProvider = Provider.of<AppConfigProvider>(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<LogsProvider>(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<LogsProvider>(context, listen: false);
|
||||||
|
|
||||||
|
logsProvider.fetchLogs(inOffset: 0);
|
||||||
|
fetchFilteringRules();
|
||||||
|
fetchClients();
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final serversProvider = Provider.of<ServersProvider>(context);
|
||||||
|
final statusProvider = Provider.of<StatusProvider>(context);
|
||||||
|
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||||
|
final logsProvider = Provider.of<LogsProvider>(context);
|
||||||
|
|
||||||
|
final width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
|
void updateConfig(Map<String, dynamic> 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,
|
||||||
|
useRootNavigator: true,
|
||||||
|
builder: (context) => const LogsFiltersModal(
|
||||||
|
dialog: false,
|
||||||
|
),
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
isScrollControlled: true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final Map<String, String> 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,
|
||||||
|
useRootNavigator: true,
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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/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/constants/enums.dart';
|
||||||
import 'package:adguard_home_manager/providers/clients_provider.dart';
|
import 'package:adguard_home_manager/providers/clients_provider.dart';
|
||||||
|
|
||||||
|
@ -35,6 +36,8 @@ class _AccessSettingsState extends State<AccessSettings> with TickerProviderStat
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final clientsProvider = Provider.of<ClientsProvider>(context);
|
final clientsProvider = Provider.of<ClientsProvider>(context);
|
||||||
|
|
||||||
|
final width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
Widget body() {
|
Widget body() {
|
||||||
return TabBarView(
|
return TabBarView(
|
||||||
controller: tabController,
|
controller: tabController,
|
||||||
|
@ -119,6 +122,7 @@ class _AccessSettingsState extends State<AccessSettings> with TickerProviderStat
|
||||||
floating: true,
|
floating: true,
|
||||||
centerTitle: false,
|
centerTitle: false,
|
||||||
forceElevated: innerBoxIsScrolled,
|
forceElevated: innerBoxIsScrolled,
|
||||||
|
surfaceTintColor: isDesktop(width) ? Colors.transparent : null,
|
||||||
bottom: tabBar()
|
bottom: tabBar()
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -361,6 +361,7 @@ class _ClientsListState extends State<ClientsList> {
|
||||||
else {
|
else {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
|
useRootNavigator: true,
|
||||||
builder: (context) => AddClientModal(
|
builder: (context) => AddClientModal(
|
||||||
type: widget.type,
|
type: widget.type,
|
||||||
onConfirm: confirmAddItem,
|
onConfirm: confirmAddItem,
|
||||||
|
|
|
@ -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/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/functions/snackbar.dart';
|
||||||
import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
||||||
|
|
||||||
|
@ -16,6 +17,8 @@ class AdvancedSettings extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||||
|
|
||||||
|
final width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
Future updateSettings({
|
Future updateSettings({
|
||||||
required bool newStatus,
|
required bool newStatus,
|
||||||
required Future Function(bool) function
|
required Future Function(bool) function
|
||||||
|
@ -40,6 +43,7 @@ class AdvancedSettings extends StatelessWidget {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(AppLocalizations.of(context)!.advancedSettings),
|
title: Text(AppLocalizations.of(context)!.advancedSettings),
|
||||||
|
surfaceTintColor: isDesktop(width) ? Colors.transparent : null,
|
||||||
),
|
),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
children: [
|
children: [
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import 'package:adguard_home_manager/functions/generate_color_translation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.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/custom_switch_list_tile.dart';
|
||||||
import 'package:adguard_home_manager/widgets/section_label.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/providers/app_config_provider.dart';
|
||||||
import 'package:adguard_home_manager/constants/colors.dart';
|
import 'package:adguard_home_manager/constants/colors.dart';
|
||||||
|
|
||||||
|
@ -56,10 +57,13 @@ class _CustomizationWidgetState extends State<CustomizationWidget> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||||
|
|
||||||
|
final width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(AppLocalizations.of(context)!.customization),
|
title: Text(AppLocalizations.of(context)!.customization),
|
||||||
centerTitle: false,
|
centerTitle: false,
|
||||||
|
surfaceTintColor: isDesktop(width) ? Colors.transparent : null,
|
||||||
),
|
),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
children: [
|
children: [
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:adguard_home_manager/functions/desktop_mode.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:animations/animations.dart';
|
import 'package:animations/animations.dart';
|
||||||
|
@ -108,6 +109,7 @@ class DhcpLeases extends StatelessWidget {
|
||||||
else {
|
else {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
|
useRootNavigator: true,
|
||||||
builder: (context) => AddStaticLeaseModal(
|
builder: (context) => AddStaticLeaseModal(
|
||||||
onConfirm: createLease,
|
onConfirm: createLease,
|
||||||
dialog: false,
|
dialog: false,
|
||||||
|
@ -120,6 +122,7 @@ class DhcpLeases extends StatelessWidget {
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
|
surfaceTintColor: isDesktop(width) ? Colors.transparent : null,
|
||||||
title: Text(
|
title: Text(
|
||||||
staticLeases == true
|
staticLeases == true
|
||||||
? AppLocalizations.of(context)!.dhcpStatic
|
? AppLocalizations.of(context)!.dhcpStatic
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// ignore_for_file: use_build_context_synchronously
|
// ignore_for_file: use_build_context_synchronously
|
||||||
|
|
||||||
|
import 'package:adguard_home_manager/functions/desktop_mode.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
@ -66,6 +67,8 @@ class _BootstrapDnsScreenState extends State<BootstrapDnsScreen> {
|
||||||
final dnsProvider = Provider.of<DnsProvider>(context);
|
final dnsProvider = Provider.of<DnsProvider>(context);
|
||||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||||
|
|
||||||
|
final width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
void saveData() async {
|
void saveData() async {
|
||||||
ProcessModal processModal = ProcessModal(context: context);
|
ProcessModal processModal = ProcessModal(context: context);
|
||||||
processModal.open(AppLocalizations.of(context)!.savingConfig);
|
processModal.open(AppLocalizations.of(context)!.savingConfig);
|
||||||
|
@ -102,6 +105,7 @@ class _BootstrapDnsScreenState extends State<BootstrapDnsScreen> {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(AppLocalizations.of(context)!.bootstrapDns),
|
title: Text(AppLocalizations.of(context)!.bootstrapDns),
|
||||||
|
surfaceTintColor: isDesktop(width) ? Colors.transparent : null,
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: validValues == true
|
onPressed: validValues == true
|
||||||
|
|
|
@ -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/widgets/custom_switch_list_tile.dart';
|
||||||
import 'package:adguard_home_manager/screens/settings/dns/clear_dns_cache_dialog.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/servers_provider.dart';
|
||||||
import 'package:adguard_home_manager/providers/dns_provider.dart';
|
import 'package:adguard_home_manager/providers/dns_provider.dart';
|
||||||
import 'package:adguard_home_manager/classes/process_modal.dart';
|
import 'package:adguard_home_manager/classes/process_modal.dart';
|
||||||
|
@ -69,6 +70,8 @@ class _CacheConfigDnsScreenState extends State<CacheConfigDnsScreen> {
|
||||||
final dnsProvider = Provider.of<DnsProvider>(context);
|
final dnsProvider = Provider.of<DnsProvider>(context);
|
||||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||||
|
|
||||||
|
final width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
void saveData() async {
|
void saveData() async {
|
||||||
ProcessModal processModal = ProcessModal(context: context);
|
ProcessModal processModal = ProcessModal(context: context);
|
||||||
processModal.open(AppLocalizations.of(context)!.savingConfig);
|
processModal.open(AppLocalizations.of(context)!.savingConfig);
|
||||||
|
@ -154,6 +157,7 @@ class _CacheConfigDnsScreenState extends State<CacheConfigDnsScreen> {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(AppLocalizations.of(context)!.dnsCacheConfig),
|
title: Text(AppLocalizations.of(context)!.dnsCacheConfig),
|
||||||
|
surfaceTintColor: isDesktop(width) ? Colors.transparent : null,
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: validData == true
|
onPressed: validData == true
|
||||||
|
|
|
@ -1,9 +1,5 @@
|
||||||
// ignore_for_file: use_build_context_synchronously
|
// 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/material.dart';
|
||||||
import 'package:flutter_split_view/flutter_split_view.dart';
|
import 'package:flutter_split_view/flutter_split_view.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
@ -17,13 +13,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/screens/settings/dns/upstream_dns.dart';
|
||||||
import 'package:adguard_home_manager/widgets/custom_list_tile.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/clear_dns_cache.dart';
|
||||||
import 'package:adguard_home_manager/functions/snackbar.dart';
|
import 'package:adguard_home_manager/functions/snackbar.dart';
|
||||||
import 'package:adguard_home_manager/providers/servers_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/providers/app_config_provider.dart';
|
||||||
|
|
||||||
class DnsSettings extends StatefulWidget {
|
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
|
@override
|
||||||
State<DnsSettings> createState() => _DnsSettingsState();
|
State<DnsSettings> createState() => _DnsSettingsState();
|
||||||
|
@ -44,106 +48,17 @@ class _DnsSettingsState extends State<DnsSettings> {
|
||||||
|
|
||||||
final width = MediaQuery.of(context).size.width;
|
final width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
void navigate(Widget widget) {
|
void navigate(Widget w) {
|
||||||
if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) {
|
if (widget.splitView) {
|
||||||
SplitView.of(context).push(widget);
|
SplitView.of(context).push(w);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Navigator.push(context, MaterialPageRoute(
|
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 {
|
void clearCache() async {
|
||||||
final result = await clearDnsCache(context, serversProvider.selectedServer!);
|
final result = await clearDnsCache(context, serversProvider.selectedServer!);
|
||||||
if (result == true) {
|
if (result == true) {
|
||||||
|
@ -165,6 +80,7 @@ class _DnsSettingsState extends State<DnsSettings> {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(AppLocalizations.of(context)!.dnsSettings),
|
title: Text(AppLocalizations.of(context)!.dnsSettings),
|
||||||
|
surfaceTintColor: isDesktop(width) ? Colors.transparent : null,
|
||||||
actions: [
|
actions: [
|
||||||
PopupMenuButton(
|
PopupMenuButton(
|
||||||
itemBuilder: (context) => [
|
itemBuilder: (context) => [
|
||||||
|
@ -200,7 +116,96 @@ class _DnsSettingsState extends State<DnsSettings> {
|
||||||
const SizedBox(width: 10)
|
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();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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/section_label.dart';
|
||||||
import 'package:adguard_home_manager/widgets/custom_switch_list_tile.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/classes/process_modal.dart';
|
||||||
import 'package:adguard_home_manager/providers/dns_provider.dart';
|
import 'package:adguard_home_manager/providers/dns_provider.dart';
|
||||||
import 'package:adguard_home_manager/functions/snackbar.dart';
|
import 'package:adguard_home_manager/functions/snackbar.dart';
|
||||||
|
@ -99,6 +100,7 @@ class _DnsServerSettingsScreenState extends State<DnsServerSettingsScreen> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final dnsProvider = Provider.of<DnsProvider>(context);
|
final dnsProvider = Provider.of<DnsProvider>(context);
|
||||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||||
|
final width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
void saveData() async {
|
void saveData() async {
|
||||||
ProcessModal processModal = ProcessModal(context: context);
|
ProcessModal processModal = ProcessModal(context: context);
|
||||||
|
@ -153,6 +155,7 @@ class _DnsServerSettingsScreenState extends State<DnsServerSettingsScreen> {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(AppLocalizations.of(context)!.dnsServerSettings),
|
title: Text(AppLocalizations.of(context)!.dnsServerSettings),
|
||||||
|
surfaceTintColor: isDesktop(width) ? Colors.transparent : null,
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: isDataValid == true
|
onPressed: isDataValid == true
|
||||||
|
|
|
@ -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/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/providers/dns_provider.dart';
|
||||||
import 'package:adguard_home_manager/classes/process_modal.dart';
|
import 'package:adguard_home_manager/classes/process_modal.dart';
|
||||||
import 'package:adguard_home_manager/functions/snackbar.dart';
|
import 'package:adguard_home_manager/functions/snackbar.dart';
|
||||||
|
@ -90,6 +91,8 @@ class _PrivateReverseDnsServersScreenState extends State<PrivateReverseDnsServer
|
||||||
final dnsProvider = Provider.of<DnsProvider>(context);
|
final dnsProvider = Provider.of<DnsProvider>(context);
|
||||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||||
|
|
||||||
|
final width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
void saveData() async {
|
void saveData() async {
|
||||||
ProcessModal processModal = ProcessModal(context: context);
|
ProcessModal processModal = ProcessModal(context: context);
|
||||||
processModal.open(AppLocalizations.of(context)!.savingConfig);
|
processModal.open(AppLocalizations.of(context)!.savingConfig);
|
||||||
|
@ -134,6 +137,7 @@ class _PrivateReverseDnsServersScreenState extends State<PrivateReverseDnsServer
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(AppLocalizations.of(context)!.privateReverseDnsServers),
|
title: Text(AppLocalizations.of(context)!.privateReverseDnsServers),
|
||||||
|
surfaceTintColor: isDesktop(width) ? Colors.transparent : null,
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: validValues == true
|
onPressed: validValues == true
|
||||||
|
|
|
@ -11,6 +11,7 @@ import 'package:adguard_home_manager/screens/settings/dns/comment_modal.dart';
|
||||||
import 'package:adguard_home_manager/widgets/custom_radio_list_tile.dart';
|
import 'package:adguard_home_manager/widgets/custom_radio_list_tile.dart';
|
||||||
|
|
||||||
import 'package:adguard_home_manager/classes/process_modal.dart';
|
import 'package:adguard_home_manager/classes/process_modal.dart';
|
||||||
|
import 'package:adguard_home_manager/functions/desktop_mode.dart';
|
||||||
import 'package:adguard_home_manager/providers/dns_provider.dart';
|
import 'package:adguard_home_manager/providers/dns_provider.dart';
|
||||||
import 'package:adguard_home_manager/functions/snackbar.dart';
|
import 'package:adguard_home_manager/functions/snackbar.dart';
|
||||||
import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
||||||
|
@ -90,6 +91,7 @@ class _UpstreamDnsScreenState extends State<UpstreamDnsScreen> {
|
||||||
else {
|
else {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
|
useRootNavigator: true,
|
||||||
builder: (context) => CommentModal(
|
builder: (context) => CommentModal(
|
||||||
onConfirm: (value) {
|
onConfirm: (value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
@ -123,6 +125,7 @@ class _UpstreamDnsScreenState extends State<UpstreamDnsScreen> {
|
||||||
else {
|
else {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
|
useRootNavigator: true,
|
||||||
builder: (context) => CommentModal(
|
builder: (context) => CommentModal(
|
||||||
comment: item['comment'],
|
comment: item['comment'],
|
||||||
onConfirm: (value) {
|
onConfirm: (value) {
|
||||||
|
@ -174,6 +177,7 @@ class _UpstreamDnsScreenState extends State<UpstreamDnsScreen> {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(AppLocalizations.of(context)!.upstreamDns),
|
title: Text(AppLocalizations.of(context)!.upstreamDns),
|
||||||
|
surfaceTintColor: isDesktop(width) ? Colors.transparent : null,
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: validValues == true
|
onPressed: validValues == true
|
||||||
|
|
|
@ -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/screens/settings/dns_rewrites/dns_rewrite_modal.dart';
|
||||||
|
|
||||||
import 'package:adguard_home_manager/providers/app_config_provider.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/functions/snackbar.dart';
|
||||||
import 'package:adguard_home_manager/constants/enums.dart';
|
import 'package:adguard_home_manager/constants/enums.dart';
|
||||||
import 'package:adguard_home_manager/providers/rewrite_rules_provider.dart';
|
import 'package:adguard_home_manager/providers/rewrite_rules_provider.dart';
|
||||||
|
@ -129,193 +130,195 @@ class _DnsRewritesScreenState extends State<DnsRewritesScreen> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(AppLocalizations.of(context)!.dnsRewrites),
|
title: Text(AppLocalizations.of(context)!.dnsRewrites),
|
||||||
|
surfaceTintColor: isDesktop(width) ? Colors.transparent : null,
|
||||||
centerTitle: false,
|
centerTitle: false,
|
||||||
),
|
),
|
||||||
body: Stack(
|
body: Stack(
|
||||||
children: [
|
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(
|
AnimatedPositioned(
|
||||||
duration: const Duration(milliseconds: 100),
|
duration: const Duration(milliseconds: 100),
|
||||||
curve: Curves.easeInOut,
|
curve: Curves.easeInOut,
|
||||||
|
@ -344,6 +347,7 @@ class _DnsRewritesScreenState extends State<DnsRewritesScreen> {
|
||||||
else {
|
else {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
|
useRootNavigator: true,
|
||||||
builder: (context) => DnsRewriteModal(
|
builder: (context) => DnsRewriteModal(
|
||||||
onConfirm: addDnsRewrite,
|
onConfirm: addDnsRewrite,
|
||||||
dialog: false,
|
dialog: false,
|
||||||
|
|
|
@ -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/screens/settings/encryption/error_message.dart';
|
||||||
|
|
||||||
import 'package:adguard_home_manager/classes/process_modal.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/base64.dart';
|
||||||
import 'package:adguard_home_manager/functions/snackbar.dart';
|
import 'package:adguard_home_manager/functions/snackbar.dart';
|
||||||
import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
||||||
|
@ -272,379 +273,10 @@ class _EncryptionSettingsWidgetState extends State<EncryptionSettingsWidget> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(AppLocalizations.of(context)!.encryptionSettings),
|
title: Text(AppLocalizations.of(context)!.encryptionSettings),
|
||||||
|
surfaceTintColor: isDesktop(width) ? Colors.transparent : null,
|
||||||
centerTitle: false,
|
centerTitle: false,
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
|
@ -668,7 +300,377 @@ class _EncryptionSettingsWidgetState extends State<EncryptionSettingsWidget> {
|
||||||
const SizedBox(width: 10),
|
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();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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/widgets/section_label.dart';
|
||||||
|
|
||||||
import 'package:adguard_home_manager/functions/check_app_updates.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/snackbar.dart';
|
||||||
import 'package:adguard_home_manager/functions/open_url.dart';
|
import 'package:adguard_home_manager/functions/open_url.dart';
|
||||||
import 'package:adguard_home_manager/functions/app_update_download_link.dart';
|
import 'package:adguard_home_manager/functions/app_update_download_link.dart';
|
||||||
|
@ -34,6 +35,8 @@ class _GeneralSettingsState extends State<GeneralSettings> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||||
|
|
||||||
|
final width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
Future updateSettings({
|
Future updateSettings({
|
||||||
required bool newStatus,
|
required bool newStatus,
|
||||||
required Future Function(bool) function
|
required Future Function(bool) function
|
||||||
|
@ -113,6 +116,7 @@ class _GeneralSettingsState extends State<GeneralSettings> {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(AppLocalizations.of(context)!.generalSettings),
|
title: Text(AppLocalizations.of(context)!.generalSettings),
|
||||||
|
surfaceTintColor: isDesktop(width) ? Colors.transparent : null,
|
||||||
),
|
),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
children: [
|
children: [
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// ignore_for_file: use_build_context_synchronously
|
// ignore_for_file: use_build_context_synchronously
|
||||||
|
|
||||||
|
import 'package:adguard_home_manager/functions/desktop_mode.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
@ -91,6 +92,8 @@ class _ReorderableTopItemsHomeState extends State<ReorderableTopItemsHome> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||||
|
|
||||||
|
final width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
Widget tile(HomeTopItems title) {
|
Widget tile(HomeTopItems title) {
|
||||||
switch (title) {
|
switch (title) {
|
||||||
case HomeTopItems.queriedDomains:
|
case HomeTopItems.queriedDomains:
|
||||||
|
@ -177,6 +180,7 @@ class _ReorderableTopItemsHomeState extends State<ReorderableTopItemsHome> {
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(AppLocalizations.of(context)!.topItemsOrder),
|
title: Text(AppLocalizations.of(context)!.topItemsOrder),
|
||||||
|
surfaceTintColor: isDesktop(width) ? Colors.transparent : null,
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: !listEquals(appConfigProvider.homeTopItemsOrder, persistHomeTopItemsList)
|
onPressed: !listEquals(appConfigProvider.homeTopItemsOrder, persistHomeTopItemsList)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// ignore_for_file: use_build_context_synchronously
|
// ignore_for_file: use_build_context_synchronously
|
||||||
|
|
||||||
|
import 'package:adguard_home_manager/functions/desktop_mode.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
@ -70,6 +71,8 @@ class _SafeSearchSettingsScreenState extends State<SafeSearchSettingsScreen> {
|
||||||
final statusProvider = Provider.of<StatusProvider>(context);
|
final statusProvider = Provider.of<StatusProvider>(context);
|
||||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||||
|
|
||||||
|
final width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
void saveConfig() async {
|
void saveConfig() async {
|
||||||
ProcessModal processModal = ProcessModal(context: context);
|
ProcessModal processModal = ProcessModal(context: context);
|
||||||
processModal.open(AppLocalizations.of(context)!.savingSettings);
|
processModal.open(AppLocalizations.of(context)!.savingSettings);
|
||||||
|
@ -104,167 +107,10 @@ class _SafeSearchSettingsScreenState extends State<SafeSearchSettingsScreen> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(AppLocalizations.of(context)!.safeSearchSettings),
|
title: Text(AppLocalizations.of(context)!.safeSearchSettings),
|
||||||
|
surfaceTintColor: isDesktop(width) ? Colors.transparent : null,
|
||||||
centerTitle: false,
|
centerTitle: false,
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
|
@ -277,7 +123,165 @@ class _SafeSearchSettingsScreenState extends State<SafeSearchSettingsScreen> {
|
||||||
const SizedBox(width: 8)
|
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();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:adguard_home_manager/constants/enums.dart';
|
import 'package:adguard_home_manager/constants/enums.dart';
|
||||||
|
import 'package:adguard_home_manager/functions/desktop_mode.dart';
|
||||||
import 'package:animations/animations.dart';
|
import 'package:animations/animations.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
@ -66,121 +67,123 @@ class _ServerInformationWidgetState extends State<ServerInformationWidget> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Widget generateBody() {
|
final width = MediaQuery.of(context).size.width;
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(AppLocalizations.of(context)!.serverInformation),
|
title: Text(AppLocalizations.of(context)!.serverInformation),
|
||||||
|
surfaceTintColor: isDesktop(width) ? Colors.transparent : null,
|
||||||
centerTitle: false,
|
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();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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/section_label.dart';
|
||||||
import 'package:adguard_home_manager/widgets/custom_list_tile.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/constants/strings.dart';
|
||||||
import 'package:adguard_home_manager/functions/open_url.dart';
|
import 'package:adguard_home_manager/functions/open_url.dart';
|
||||||
import 'package:adguard_home_manager/functions/compare_versions.dart';
|
import 'package:adguard_home_manager/functions/compare_versions.dart';
|
||||||
|
@ -36,35 +37,46 @@ class Settings extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final width = MediaQuery.of(context).size.width;
|
return LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
if (width > 900) {
|
if (constraints.maxWidth > 900) {
|
||||||
return SplitView.material(
|
return SplitView.material(
|
||||||
hideDivider: true,
|
hideDivider: true,
|
||||||
flexWidth: const FlexWidth(mainViewFlexWidth: 1, secondaryViewFlexWidth: 2),
|
flexWidth: const FlexWidth(mainViewFlexWidth: 1, secondaryViewFlexWidth: 2),
|
||||||
placeholder: Center(
|
placeholder: Center(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(24),
|
padding: const EdgeInsets.all(24),
|
||||||
child: Text(
|
child: Text(
|
||||||
AppLocalizations.of(context)!.selectOptionLeftColumn,
|
AppLocalizations.of(context)!.selectOptionLeftColumn,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant
|
color: Theme.of(context).colorScheme.onSurfaceVariant
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
child: const SettingsWidget(
|
||||||
),
|
twoColumns: true,
|
||||||
child: const SettingsWidget(),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return const SettingsWidget();
|
return const SettingsWidget(
|
||||||
}
|
twoColumns: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
class SettingsWidget extends StatelessWidget {
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -74,7 +86,7 @@ class SettingsWidget extends StatelessWidget {
|
||||||
|
|
||||||
final width = MediaQuery.of(context).size.width;
|
final width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
if (width <= 900 && appConfigProvider.selectedSettingsScreen != null) {
|
if (!twoColumns && appConfigProvider.selectedSettingsScreen != null) {
|
||||||
appConfigProvider.setSelectedSettingsScreen(screen: null);
|
appConfigProvider.setSelectedSettingsScreen(screen: null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,7 +98,7 @@ class SettingsWidget extends StatelessWidget {
|
||||||
required Widget screenToNavigate,
|
required Widget screenToNavigate,
|
||||||
required int thisItem
|
required int thisItem
|
||||||
}) {
|
}) {
|
||||||
if (width > 900) {
|
if (twoColumns) {
|
||||||
return CustomSettingsTile(
|
return CustomSettingsTile(
|
||||||
title: title,
|
title: title,
|
||||||
subtitle: subtitle,
|
subtitle: subtitle,
|
||||||
|
@ -125,6 +137,7 @@ class SettingsWidget extends StatelessWidget {
|
||||||
floating: true,
|
floating: true,
|
||||||
centerTitle: false,
|
centerTitle: false,
|
||||||
forceElevated: innerBoxIsScrolled,
|
forceElevated: innerBoxIsScrolled,
|
||||||
|
surfaceTintColor: isDesktop(width) ? Colors.transparent : null,
|
||||||
title: Text(AppLocalizations.of(context)!.settings),
|
title: Text(AppLocalizations.of(context)!.settings),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -176,7 +189,9 @@ class SettingsWidget extends StatelessWidget {
|
||||||
title: AppLocalizations.of(context)!.dnsSettings,
|
title: AppLocalizations.of(context)!.dnsSettings,
|
||||||
subtitle: AppLocalizations.of(context)!.dnsSettingsDescription,
|
subtitle: AppLocalizations.of(context)!.dnsSettingsDescription,
|
||||||
thisItem: 3,
|
thisItem: 3,
|
||||||
screenToNavigate: const DnsSettings(),
|
screenToNavigate: DnsSettings(
|
||||||
|
splitView: twoColumns,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
settingsTile(
|
settingsTile(
|
||||||
icon: Icons.security_rounded,
|
icon: Icons.security_rounded,
|
||||||
|
|
|
@ -3,7 +3,6 @@ import 'package:provider/provider.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.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/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/servers_provider.dart';
|
||||||
import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
||||||
import 'package:adguard_home_manager/models/app_screen.dart';
|
import 'package:adguard_home_manager/models/app_screen.dart';
|
||||||
|
@ -15,7 +14,6 @@ class BottomNavBar extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final serversProvider = Provider.of<ServersProvider>(context);
|
final serversProvider = Provider.of<ServersProvider>(context);
|
||||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||||
final logsProvider = Provider.of<LogsProvider>(context);
|
|
||||||
|
|
||||||
List<AppScreen> screens = serversProvider.selectedServer != null && serversProvider.apiClient != null
|
List<AppScreen> screens = serversProvider.selectedServer != null && serversProvider.apiClient != null
|
||||||
? screensServerConnected
|
? screensServerConnected
|
||||||
|
@ -84,21 +82,6 @@ class BottomNavBar extends StatelessWidget {
|
||||||
),
|
),
|
||||||
label: translatedName(screen.name)
|
label: translatedName(screen.name)
|
||||||
)).toList(),
|
)).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);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
249
lib/widgets/layout.dart
Normal file
249
lib/widgets/layout.dart
Normal file
|
@ -0,0 +1,249 @@
|
||||||
|
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"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.1.0"
|
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:
|
flutter_html:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -310,6 +318,14 @@ packages:
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
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:
|
html:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -382,6 +398,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.2"
|
version: "1.0.2"
|
||||||
|
logging:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: logging
|
||||||
|
sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.0"
|
||||||
markdown:
|
markdown:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -74,6 +74,8 @@ dependencies:
|
||||||
flutter_dotenv: ^5.0.2
|
flutter_dotenv: ^5.0.2
|
||||||
flutter_reorderable_list: ^1.3.1
|
flutter_reorderable_list: ^1.3.1
|
||||||
pie_chart: ^5.3.2
|
pie_chart: ^5.3.2
|
||||||
|
go_router: ^12.0.1
|
||||||
|
flutter_hooks: ^0.20.3
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
Loading…
Add table
Reference in a new issue