mirror of
https://github.com/JGeek00/adguard-home-manager.git
synced 2025-06-09 08:07:45 +00:00
Merge branch 'desktop-ui'
This commit is contained in:
commit
b4344ffb96
154 changed files with 11280 additions and 4128 deletions
25
.metadata
25
.metadata
|
@ -4,7 +4,7 @@
|
||||||
# This file should be version controlled.
|
# This file should be version controlled.
|
||||||
|
|
||||||
version:
|
version:
|
||||||
revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
|
revision: f72efea43c3013323d1b95cff571f3c1caa37583
|
||||||
channel: stable
|
channel: stable
|
||||||
|
|
||||||
project_type: app
|
project_type: app
|
||||||
|
@ -13,26 +13,11 @@ project_type: app
|
||||||
migration:
|
migration:
|
||||||
platforms:
|
platforms:
|
||||||
- platform: root
|
- platform: root
|
||||||
create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
|
create_revision: f72efea43c3013323d1b95cff571f3c1caa37583
|
||||||
base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
|
base_revision: f72efea43c3013323d1b95cff571f3c1caa37583
|
||||||
- platform: android
|
|
||||||
create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
|
|
||||||
base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
|
|
||||||
- platform: ios
|
|
||||||
create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
|
|
||||||
base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
|
|
||||||
- platform: linux
|
- platform: linux
|
||||||
create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
|
create_revision: f72efea43c3013323d1b95cff571f3c1caa37583
|
||||||
base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
|
base_revision: f72efea43c3013323d1b95cff571f3c1caa37583
|
||||||
- platform: macos
|
|
||||||
create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
|
|
||||||
base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
|
|
||||||
- platform: web
|
|
||||||
create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
|
|
||||||
base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
|
|
||||||
- platform: windows
|
|
||||||
create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
|
|
||||||
base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
|
|
||||||
|
|
||||||
# User provided section
|
# User provided section
|
||||||
|
|
||||||
|
|
BIN
assets/icon/icon-circle.ico
Normal file
BIN
assets/icon/icon-circle.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
BIN
assets/icon/icon-circle.png
Normal file
BIN
assets/icon/icon-circle.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 42 KiB |
BIN
assets/icon/icon-macos.png
Executable file
BIN
assets/icon/icon-macos.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 425 KiB |
1
debian/compile_deb.txt
vendored
Normal file
1
debian/compile_deb.txt
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
https://pub.dev/packages/flutter_to_debian
|
14
debian/debian.yaml
vendored
Normal file
14
debian/debian.yaml
vendored
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
flutter_app:
|
||||||
|
command: adguard_home_manager
|
||||||
|
arch: x64
|
||||||
|
parent: /usr/local/lib
|
||||||
|
|
||||||
|
control:
|
||||||
|
Package: AdGuardHomeManager
|
||||||
|
Version: 2.0.0
|
||||||
|
Architecture: amd64
|
||||||
|
Essential: no
|
||||||
|
Priority: optional
|
||||||
|
Depends:
|
||||||
|
Maintainer: JGeek00
|
||||||
|
Description: AdGuard Home control app
|
8
debian/gui/adguard-home-manager.desktop
vendored
Normal file
8
debian/gui/adguard-home-manager.desktop
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
[Desktop Entry]
|
||||||
|
Name=AdGuard Home Manager
|
||||||
|
Comment=Manage your AdGuard Home server
|
||||||
|
Exec=adguard-home-manager
|
||||||
|
Icon=${SNAP}/meta/gui/adguard-home-manager.png
|
||||||
|
Terminal=false
|
||||||
|
Type=Application
|
||||||
|
Categories=Utilities;
|
BIN
debian/gui/adguard-home-manager.png
vendored
Normal file
BIN
debian/gui/adguard-home-manager.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 42 KiB |
|
@ -365,7 +365,7 @@
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.adguardHomeManager;
|
PRODUCT_BUNDLE_IDENTIFIER = com.jgeek00.adguard_home_manager;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
|
@ -494,7 +494,7 @@
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.adguardHomeManager;
|
PRODUCT_BUNDLE_IDENTIFIER = com.jgeek00.adguard_home_manager;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
@ -517,7 +517,7 @@
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.adguardHomeManager;
|
PRODUCT_BUNDLE_IDENTIFIER = com.jgeek00.adguard_home_manager;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// ignore_for_file: use_build_context_synchronously, depend_on_referenced_packages
|
// ignore_for_file: use_build_context_synchronously, depend_on_referenced_packages
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:animations/animations.dart';
|
import 'package:animations/animations.dart';
|
||||||
|
@ -10,7 +11,9 @@ import 'package:store_checker/store_checker.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
import 'package:adguard_home_manager/widgets/bottom_nav_bar.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/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/providers/app_config_provider.dart';
|
||||||
import 'package:adguard_home_manager/models/github_release.dart';
|
import 'package:adguard_home_manager/models/github_release.dart';
|
||||||
|
@ -53,11 +56,23 @@ class _BaseState extends State<Base> with WidgetsBindingObserver {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<GitHubRelease?> checkInstallationSource() async {
|
Future<GitHubRelease?> checkInstallationSource() async {
|
||||||
Source installationSource = await StoreChecker.getSource;
|
final result = await checkAppUpdatesGitHub();
|
||||||
if (installationSource != Source.IS_INSTALLED_FROM_PLAY_STORE) {
|
if (result['result'] == 'success') {
|
||||||
final result = await checkAppUpdatesGitHub();
|
final update = updateExists(widget.appConfigProvider.getAppInfo!.version, result['body'].tagName);
|
||||||
if (result['result'] == 'success') {
|
if (update == true) {
|
||||||
if (updateExists(widget.appConfigProvider.getAppInfo!.version, result['body'].tagName)) {
|
if (Platform.isAndroid) {
|
||||||
|
Source installationSource = await StoreChecker.getSource;
|
||||||
|
if (installationSource == Source.IS_INSTALLED_FROM_PLAY_STORE) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return result['body'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (Platform.isIOS) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
return result['body'];
|
return result['body'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -107,38 +122,51 @@ class _BaseState extends State<Base> with WidgetsBindingObserver {
|
||||||
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 width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
List<AppScreen> screens = serversProvider.selectedServer != null
|
List<AppScreen> screens = serversProvider.selectedServer != null
|
||||||
? screensServerConnected
|
? screensServerConnected
|
||||||
: screensSelectServer;
|
: screensSelectServer;
|
||||||
|
|
||||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
return CustomMenuBar(
|
||||||
value: SystemUiOverlayStyle(
|
child: AnnotatedRegion<SystemUiOverlayStyle>(
|
||||||
statusBarColor: Colors.transparent,
|
value: SystemUiOverlayStyle(
|
||||||
statusBarBrightness: Theme.of(context).brightness == Brightness.light
|
statusBarColor: Colors.transparent,
|
||||||
? Brightness.light
|
statusBarBrightness: Theme.of(context).brightness == Brightness.light
|
||||||
: Brightness.dark,
|
? Brightness.light
|
||||||
statusBarIconBrightness: Theme.of(context).brightness == Brightness.light
|
: Brightness.dark,
|
||||||
? Brightness.dark
|
statusBarIconBrightness: Theme.of(context).brightness == Brightness.light
|
||||||
: Brightness.light,
|
? Brightness.dark
|
||||||
systemNavigationBarColor: Theme.of(context).scaffoldBackgroundColor,
|
: Brightness.light,
|
||||||
systemNavigationBarIconBrightness: Theme.of(context).brightness == Brightness.light
|
systemNavigationBarColor: Theme.of(context).scaffoldBackgroundColor,
|
||||||
? Brightness.dark
|
systemNavigationBarIconBrightness: Theme.of(context).brightness == Brightness.light
|
||||||
: Brightness.light,
|
? Brightness.dark
|
||||||
),
|
: Brightness.light,
|
||||||
child: Scaffold(
|
|
||||||
body: PageTransitionSwitcher(
|
|
||||||
duration: const Duration(milliseconds: 200),
|
|
||||||
transitionBuilder: (
|
|
||||||
(child, primaryAnimation, secondaryAnimation) => FadeThroughTransition(
|
|
||||||
animation: primaryAnimation,
|
|
||||||
secondaryAnimation: secondaryAnimation,
|
|
||||||
child: child,
|
|
||||||
)
|
|
||||||
),
|
|
||||||
child: screens[appConfigProvider.selectedScreen].body,
|
|
||||||
),
|
),
|
||||||
bottomNavigationBar: const BottomNavBar(),
|
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,17 +1,30 @@
|
||||||
import 'package:flutter_web_browser/flutter_web_browser.dart';
|
import 'dart:io';
|
||||||
|
|
||||||
void openUrl(String url) {
|
import 'package:flutter_web_browser/flutter_web_browser.dart';
|
||||||
FlutterWebBrowser.openWebPage(
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
url: url,
|
|
||||||
customTabsOptions: const CustomTabsOptions(
|
void openUrl(String url) async {
|
||||||
instantAppsEnabled: true,
|
if (Platform.isAndroid || Platform.isIOS) {
|
||||||
showTitle: true,
|
FlutterWebBrowser.openWebPage(
|
||||||
urlBarHidingEnabled: false,
|
url: url,
|
||||||
),
|
customTabsOptions: const CustomTabsOptions(
|
||||||
safariVCOptions: const SafariViewControllerOptions(
|
instantAppsEnabled: true,
|
||||||
barCollapsingEnabled: true,
|
showTitle: true,
|
||||||
dismissButtonStyle: SafariViewControllerDismissButtonStyle.close,
|
urlBarHidingEnabled: false,
|
||||||
modalPresentationCapturesStatusBarAppearance: true,
|
),
|
||||||
)
|
safariVCOptions: const SafariViewControllerOptions(
|
||||||
);
|
barCollapsingEnabled: true,
|
||||||
}
|
dismissButtonStyle: SafariViewControllerDismissButtonStyle.close,
|
||||||
|
modalPresentationCapturesStatusBarAppearance: true,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
final uri = Uri.parse(url);
|
||||||
|
if (await canLaunchUrl(uri)) {
|
||||||
|
await launchUrl(uri);
|
||||||
|
} else {
|
||||||
|
throw 'Could not launch $url';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -45,10 +45,10 @@
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
"serverStatus": "Server status",
|
"serverStatus": "Server status",
|
||||||
"connectionNotUpdated": "Connection not updated",
|
"connectionNotUpdated": "Connection not updated",
|
||||||
"ruleFilteringWidget": "Rule\nfiltering",
|
"ruleFilteringWidget": "Rule filtering",
|
||||||
"safeBrowsingWidget": "Safe\nbrowsing",
|
"safeBrowsingWidget": "Safe browsing",
|
||||||
"parentalFilteringWidget": "Parental\nfiltering",
|
"parentalFilteringWidget": "Parental filtering",
|
||||||
"safeSearchWidget": "Safe\nsearch",
|
"safeSearchWidget": "Safe search",
|
||||||
"ruleFiltering": "Rule filtering",
|
"ruleFiltering": "Rule filtering",
|
||||||
"safeBrowsing": "Safe browsing",
|
"safeBrowsing": "Safe browsing",
|
||||||
"parentalFiltering": "Parental filtering",
|
"parentalFiltering": "Parental filtering",
|
||||||
|
@ -606,5 +606,11 @@
|
||||||
"remainingTime": "Remaining time",
|
"remainingTime": "Remaining time",
|
||||||
"safeSearchSettings": "Safe search settings",
|
"safeSearchSettings": "Safe search settings",
|
||||||
"loadingSafeSearchSettings": "Loading safe search settings...",
|
"loadingSafeSearchSettings": "Loading safe search settings...",
|
||||||
"safeSearchSettingsNotLoaded": "Error when loading safe search settings."
|
"safeSearchSettingsNotLoaded": "Error when loading safe search settings.",
|
||||||
|
"loadingLogsSettings": "Loading logs settings...",
|
||||||
|
"selectOptionLeftColumn": "Select an option of the left column",
|
||||||
|
"selectClientLeftColumn": "Select a client of the left column",
|
||||||
|
"disableList": "Disable list",
|
||||||
|
"enableList": "Enable list",
|
||||||
|
"screens": "Screens"
|
||||||
}
|
}
|
|
@ -45,10 +45,10 @@
|
||||||
"save": "Guardar",
|
"save": "Guardar",
|
||||||
"connectionNotUpdated": "Conexión no actualizada",
|
"connectionNotUpdated": "Conexión no actualizada",
|
||||||
"serverStatus": "Estado del servidor",
|
"serverStatus": "Estado del servidor",
|
||||||
"ruleFilteringWidget": "Bloqueo por\nfiltros",
|
"ruleFilteringWidget": "Bloqueo por filtros",
|
||||||
"safeBrowsingWidget": "Navegación\nsegura",
|
"safeBrowsingWidget": "Navegación segura",
|
||||||
"parentalFilteringWidget": "Control\nparental",
|
"parentalFilteringWidget": "Control parental",
|
||||||
"safeSearchWidget": "Búsqueda\nsegura",
|
"safeSearchWidget": "Búsqueda segura",
|
||||||
"ruleFiltering": "Bloqueo por filtros",
|
"ruleFiltering": "Bloqueo por filtros",
|
||||||
"safeBrowsing": "Navegación segura",
|
"safeBrowsing": "Navegación segura",
|
||||||
"parentalFiltering": "Control parental",
|
"parentalFiltering": "Control parental",
|
||||||
|
@ -606,5 +606,11 @@
|
||||||
"remainingTime": "Tiempo restante",
|
"remainingTime": "Tiempo restante",
|
||||||
"safeSearchSettings": "Configuración de búsqueda segura",
|
"safeSearchSettings": "Configuración de búsqueda segura",
|
||||||
"loadingSafeSearchSettings": "Cargando configuración de búsqueda segura...",
|
"loadingSafeSearchSettings": "Cargando configuración de búsqueda segura...",
|
||||||
"safeSearchSettingsNotLoaded": "Error al cargar la configuración de búsqueda segura."
|
"safeSearchSettingsNotLoaded": "Error al cargar la configuración de búsqueda segura.",
|
||||||
|
"loadingLogsSettings": "Cargando configuración de registros...",
|
||||||
|
"selectOptionLeftColumn": "Selecciona una opción de la columna de la izquierda",
|
||||||
|
"selectClientLeftColumn": "Selecciona un cliente de la columna de la izquierda",
|
||||||
|
"disableList": "Deshabilitar lista",
|
||||||
|
"enableList": "Habilitar lista",
|
||||||
|
"screens": "Pantallas"
|
||||||
}
|
}
|
|
@ -7,25 +7,33 @@ import 'package:provider/provider.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';
|
||||||
|
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||||
|
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/base.dart';
|
||||||
|
|
||||||
import 'package:adguard_home_manager/classes/http_override.dart';
|
import 'package:adguard_home_manager/classes/http_override.dart';
|
||||||
import 'package:adguard_home_manager/services/database.dart';
|
import 'package:adguard_home_manager/services/db/database.dart';
|
||||||
import 'package:adguard_home_manager/constants/colors.dart';
|
import 'package:adguard_home_manager/constants/colors.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/servers_provider.dart';
|
|
||||||
import 'package:adguard_home_manager/config/theme.dart';
|
import 'package:adguard_home_manager/config/theme.dart';
|
||||||
|
import 'package:adguard_home_manager/providers/servers_provider.dart';
|
||||||
|
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
SystemChrome.setPreferredOrientations(
|
|
||||||
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]
|
if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) {
|
||||||
);
|
setWindowMinSize(const Size(500, 500));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Platform.isWindows || Platform.isLinux) {
|
||||||
|
sqfliteFfiInit();
|
||||||
|
databaseFactory = databaseFactoryFfi;
|
||||||
|
}
|
||||||
|
|
||||||
AppConfigProvider appConfigProvider = AppConfigProvider();
|
AppConfigProvider appConfigProvider = AppConfigProvider();
|
||||||
ServersProvider serversProvider = ServersProvider();
|
ServersProvider serversProvider = ServersProvider();
|
||||||
|
|
|
@ -4,6 +4,7 @@ import 'package:flutter/scheduler.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
import 'package:sqflite/sqlite_api.dart';
|
import 'package:sqflite/sqlite_api.dart';
|
||||||
|
|
||||||
|
import 'package:adguard_home_manager/services/db/queries.dart';
|
||||||
import 'package:adguard_home_manager/functions/conversions.dart';
|
import 'package:adguard_home_manager/functions/conversions.dart';
|
||||||
import 'package:adguard_home_manager/models/app_log.dart';
|
import 'package:adguard_home_manager/models/app_log.dart';
|
||||||
|
|
||||||
|
@ -16,6 +17,8 @@ class AppConfigProvider with ChangeNotifier {
|
||||||
|
|
||||||
int _selectedScreen = 0;
|
int _selectedScreen = 0;
|
||||||
|
|
||||||
|
int? _selectedSettingsScreen;
|
||||||
|
|
||||||
bool _showingSnackbar = false;
|
bool _showingSnackbar = false;
|
||||||
|
|
||||||
int _selectedTheme = 0;
|
int _selectedTheme = 0;
|
||||||
|
@ -118,6 +121,10 @@ class AppConfigProvider with ChangeNotifier {
|
||||||
return _doNotRememberVersion;
|
return _doNotRememberVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int? get selectedSettingsScreen {
|
||||||
|
return _selectedSettingsScreen;
|
||||||
|
}
|
||||||
|
|
||||||
void setDbInstance(Database db) {
|
void setDbInstance(Database db) {
|
||||||
_dbInstance = db;
|
_dbInstance = db;
|
||||||
}
|
}
|
||||||
|
@ -159,8 +166,19 @@ class AppConfigProvider with ChangeNotifier {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setSelectedSettingsScreen({required int? screen, bool? notify}) {
|
||||||
|
_selectedSettingsScreen = screen;
|
||||||
|
if (notify == true) {
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<bool> setOverrideSslCheck(bool status) async {
|
Future<bool> setOverrideSslCheck(bool status) async {
|
||||||
final updated = await _updateOverrideSslCheck(status == true ? 1 : 0);
|
final updated = await updateConfigQuery(
|
||||||
|
db: _dbInstance!,
|
||||||
|
column: 'overrideSslCheck',
|
||||||
|
value: status == true ? 1 : 0
|
||||||
|
);
|
||||||
if (updated == true) {
|
if (updated == true) {
|
||||||
_overrideSslCheck = status == true ? 1 : 0;
|
_overrideSslCheck = status == true ? 1 : 0;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
@ -172,7 +190,11 @@ class AppConfigProvider with ChangeNotifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> setHideZeroValues(bool status) async {
|
Future<bool> setHideZeroValues(bool status) async {
|
||||||
final updated = await _updateSetHideZeroValues(status == true ? 1 : 0);
|
final updated = await updateConfigQuery(
|
||||||
|
db: _dbInstance!,
|
||||||
|
column: 'overrideSslCheck',
|
||||||
|
value: status == true ? 1 : 0
|
||||||
|
);
|
||||||
if (updated == true) {
|
if (updated == true) {
|
||||||
_hideZeroValues = status == true ? 1 : 0;
|
_hideZeroValues = status == true ? 1 : 0;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
@ -184,7 +206,11 @@ class AppConfigProvider with ChangeNotifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> setShowNameTimeLogs(bool status) async {
|
Future<bool> setShowNameTimeLogs(bool status) async {
|
||||||
final updated = await _updateShowNameTimeLogsDb(status == true ? 1 : 0);
|
final updated = await updateConfigQuery(
|
||||||
|
db: _dbInstance!,
|
||||||
|
column: 'showNameTimeLogs',
|
||||||
|
value: status == true ? 1 : 0
|
||||||
|
);
|
||||||
if (updated == true) {
|
if (updated == true) {
|
||||||
_showNameTimeLogs = status == true ? 1 : 0;
|
_showNameTimeLogs = status == true ? 1 : 0;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
@ -196,7 +222,11 @@ class AppConfigProvider with ChangeNotifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> setSelectedTheme(int value) async {
|
Future<bool> setSelectedTheme(int value) async {
|
||||||
final updated = await _updateThemeDb(value);
|
final updated = await updateConfigQuery(
|
||||||
|
db: _dbInstance!,
|
||||||
|
column: 'theme',
|
||||||
|
value: value
|
||||||
|
);
|
||||||
if (updated == true) {
|
if (updated == true) {
|
||||||
_selectedTheme = value;
|
_selectedTheme = value;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
@ -208,7 +238,11 @@ class AppConfigProvider with ChangeNotifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> setUseDynamicColor(bool value) async {
|
Future<bool> setUseDynamicColor(bool value) async {
|
||||||
final updated = await _updateDynamicColorDb(value == true ? 1 : 0);
|
final updated = await updateConfigQuery(
|
||||||
|
db: _dbInstance!,
|
||||||
|
column: 'useDynamicColor',
|
||||||
|
value: value == true ? 1 : 0
|
||||||
|
);
|
||||||
if (updated == true) {
|
if (updated == true) {
|
||||||
_useDynamicColor = value;
|
_useDynamicColor = value;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
@ -220,7 +254,11 @@ class AppConfigProvider with ChangeNotifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> setUseThemeColorForStatus(bool value) async {
|
Future<bool> setUseThemeColorForStatus(bool value) async {
|
||||||
final updated = await _updateUseThemeColorForStatusDb(value == true ? 1 : 0);
|
final updated = await updateConfigQuery(
|
||||||
|
db: _dbInstance!,
|
||||||
|
column: 'useThemeColorForStatus',
|
||||||
|
value: value == true ? 1 : 0
|
||||||
|
);
|
||||||
if (updated == true) {
|
if (updated == true) {
|
||||||
_useThemeColorForStatus = value;
|
_useThemeColorForStatus = value;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
@ -232,7 +270,11 @@ class AppConfigProvider with ChangeNotifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> setStaticColor(int value) async {
|
Future<bool> setStaticColor(int value) async {
|
||||||
final updated = await _updateStaticColorDb(value);
|
final updated = await updateConfigQuery(
|
||||||
|
db: _dbInstance!,
|
||||||
|
column: 'staticColor',
|
||||||
|
value: value
|
||||||
|
);
|
||||||
if (updated == true) {
|
if (updated == true) {
|
||||||
_staticColor = value;
|
_staticColor = value;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
@ -244,109 +286,12 @@ class AppConfigProvider with ChangeNotifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> setDoNotRememberVersion(String value) async {
|
Future<bool> setDoNotRememberVersion(String value) async {
|
||||||
try {
|
final updated = await updateConfigQuery(
|
||||||
return await _dbInstance!.transaction((txn) async {
|
db: _dbInstance!,
|
||||||
await txn.rawUpdate(
|
column: 'doNotRememberVersion',
|
||||||
'UPDATE appConfig SET doNotRememberVersion = "$value"',
|
value: value
|
||||||
);
|
);
|
||||||
_doNotRememberVersion = value;
|
return updated;
|
||||||
notifyListeners();
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> _updateThemeDb(int value) async {
|
|
||||||
try {
|
|
||||||
return await _dbInstance!.transaction((txn) async {
|
|
||||||
await txn.rawUpdate(
|
|
||||||
'UPDATE appConfig SET theme = $value',
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> _updateDynamicColorDb(int value) async {
|
|
||||||
try {
|
|
||||||
return await _dbInstance!.transaction((txn) async {
|
|
||||||
await txn.rawUpdate(
|
|
||||||
'UPDATE appConfig SET useDynamicColor = $value',
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> _updateUseThemeColorForStatusDb(int value) async {
|
|
||||||
try {
|
|
||||||
return await _dbInstance!.transaction((txn) async {
|
|
||||||
await txn.rawUpdate(
|
|
||||||
'UPDATE appConfig SET useThemeColorForStatus = $value',
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> _updateStaticColorDb(int value) async {
|
|
||||||
try {
|
|
||||||
return await _dbInstance!.transaction((txn) async {
|
|
||||||
await txn.rawUpdate(
|
|
||||||
'UPDATE appConfig SET staticColor = $value',
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> _updateOverrideSslCheck(int value) async {
|
|
||||||
try {
|
|
||||||
return await _dbInstance!.transaction((txn) async {
|
|
||||||
await txn.rawUpdate(
|
|
||||||
'UPDATE appConfig SET overrideSslCheck = $value',
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> _updateSetHideZeroValues(int value) async {
|
|
||||||
try {
|
|
||||||
return await _dbInstance!.transaction((txn) async {
|
|
||||||
await txn.rawUpdate(
|
|
||||||
'UPDATE appConfig SET hideZeroValues = $value',
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> _updateShowNameTimeLogsDb(int value) async {
|
|
||||||
try {
|
|
||||||
return await _dbInstance!.transaction((txn) async {
|
|
||||||
await txn.rawUpdate(
|
|
||||||
'UPDATE appConfig SET showNameTimeLogs = $value',
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void saveFromDb(Database dbInstance, Map<String, dynamic> dbData) {
|
void saveFromDb(Database dbInstance, Map<String, dynamic> dbData) {
|
||||||
|
|
|
@ -7,14 +7,15 @@ import 'package:adguard_home_manager/models/dns_info.dart';
|
||||||
import 'package:adguard_home_manager/models/rewrite_rules.dart';
|
import 'package:adguard_home_manager/models/rewrite_rules.dart';
|
||||||
import 'package:adguard_home_manager/models/filtering_status.dart';
|
import 'package:adguard_home_manager/models/filtering_status.dart';
|
||||||
import 'package:adguard_home_manager/models/clients_allowed_blocked.dart';
|
import 'package:adguard_home_manager/models/clients_allowed_blocked.dart';
|
||||||
import 'package:adguard_home_manager/models/update_available.dart';
|
|
||||||
import 'package:adguard_home_manager/models/blocked_services.dart';
|
import 'package:adguard_home_manager/models/blocked_services.dart';
|
||||||
import 'package:adguard_home_manager/models/clients.dart';
|
import 'package:adguard_home_manager/models/clients.dart';
|
||||||
import 'package:adguard_home_manager/models/server_status.dart';
|
import 'package:adguard_home_manager/models/server_status.dart';
|
||||||
import 'package:adguard_home_manager/models/server.dart';
|
import 'package:adguard_home_manager/models/server.dart';
|
||||||
|
import 'package:adguard_home_manager/models/update_available.dart';
|
||||||
import 'package:adguard_home_manager/services/http_requests.dart';
|
import 'package:adguard_home_manager/services/http_requests.dart';
|
||||||
import 'package:adguard_home_manager/functions/time_server_disabled.dart';
|
import 'package:adguard_home_manager/functions/time_server_disabled.dart';
|
||||||
import 'package:adguard_home_manager/functions/conversions.dart';
|
import 'package:adguard_home_manager/functions/conversions.dart';
|
||||||
|
import 'package:adguard_home_manager/services/db/queries.dart';
|
||||||
import 'package:adguard_home_manager/functions/compare_versions.dart';
|
import 'package:adguard_home_manager/functions/compare_versions.dart';
|
||||||
import 'package:adguard_home_manager/constants/enums.dart';
|
import 'package:adguard_home_manager/constants/enums.dart';
|
||||||
|
|
||||||
|
@ -33,6 +34,9 @@ class ServersProvider with ChangeNotifier {
|
||||||
loadStatus: LoadStatus.loading,
|
loadStatus: LoadStatus.loading,
|
||||||
data: null
|
data: null
|
||||||
);
|
);
|
||||||
|
String? _searchTermClients;
|
||||||
|
List<AutoClient> _filteredActiveClients = [];
|
||||||
|
List<Client> _filteredAddedClients = [];
|
||||||
|
|
||||||
final Filtering _filtering = Filtering(
|
final Filtering _filtering = Filtering(
|
||||||
loadStatus: LoadStatus.loading,
|
loadStatus: LoadStatus.loading,
|
||||||
|
@ -86,6 +90,18 @@ class ServersProvider with ChangeNotifier {
|
||||||
return _clients;
|
return _clients;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String? get searchTermClients {
|
||||||
|
return _searchTermClients;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<AutoClient> get filteredActiveClients {
|
||||||
|
return _filteredActiveClients;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Client> get filteredAddedClients {
|
||||||
|
return _filteredAddedClients;
|
||||||
|
}
|
||||||
|
|
||||||
FilteringStatus? get filteringStatus {
|
FilteringStatus? get filteringStatus {
|
||||||
return _filteringStatus;
|
return _filteringStatus;
|
||||||
}
|
}
|
||||||
|
@ -147,6 +163,43 @@ class ServersProvider with ChangeNotifier {
|
||||||
|
|
||||||
void setClientsData(ClientsData data) {
|
void setClientsData(ClientsData data) {
|
||||||
_clients.data = data;
|
_clients.data = data;
|
||||||
|
if (_searchTermClients != null && _searchTermClients != '') {
|
||||||
|
_filteredActiveClients = _clients.data!.autoClientsData.where(
|
||||||
|
(client) => client.ip.contains(_searchTermClients!.toLowerCase()) || (client.name != null ? client.name!.contains(_searchTermClients!.toLowerCase()) : false)
|
||||||
|
).toList();
|
||||||
|
_filteredAddedClients = _clients.data!.clients.where(
|
||||||
|
(client) {
|
||||||
|
isContained(String value) => value.contains(value.toLowerCase());
|
||||||
|
return client.ids.any(isContained);
|
||||||
|
}
|
||||||
|
).toList();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
_filteredActiveClients = data.autoClientsData;
|
||||||
|
_filteredAddedClients = data.clients;
|
||||||
|
}
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setSearchTermClients(String? value) {
|
||||||
|
_searchTermClients = value;
|
||||||
|
if (value != null && value != '') {
|
||||||
|
if (_clients.data != null) {
|
||||||
|
_filteredActiveClients = _clients.data!.autoClientsData.where(
|
||||||
|
(client) => client.ip.contains(value.toLowerCase()) || (client.name != null ? client.name!.contains(value.toLowerCase()) : false)
|
||||||
|
).toList();
|
||||||
|
_filteredAddedClients = _clients.data!.clients.where(
|
||||||
|
(client) {
|
||||||
|
isContained(String value) => value.contains(value.toLowerCase());
|
||||||
|
return client.ids.any(isContained);
|
||||||
|
}
|
||||||
|
).toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (_clients.data != null) _filteredActiveClients = _clients.data!.autoClientsData;
|
||||||
|
if (_clients.data != null) _filteredAddedClients = _clients.data!.clients;
|
||||||
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -249,7 +302,7 @@ class ServersProvider with ChangeNotifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> createServer(Server server) async {
|
Future<dynamic> createServer(Server server) async {
|
||||||
final saved = await saveServerIntoDb(server);
|
final saved = await saveServerQuery(_dbInstance!, server);
|
||||||
if (saved == null) {
|
if (saved == null) {
|
||||||
if (server.defaultServer == true) {
|
if (server.defaultServer == true) {
|
||||||
final defaultServer = await setDefaultServer(server);
|
final defaultServer = await setDefaultServer(server);
|
||||||
|
@ -274,7 +327,7 @@ class ServersProvider with ChangeNotifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> setDefaultServer(Server server) async {
|
Future<dynamic> setDefaultServer(Server server) async {
|
||||||
final updated = await setDefaultServerDb(server.id);
|
final updated = await setDefaultServerQuery(_dbInstance!, server.id);
|
||||||
if (updated == null) {
|
if (updated == null) {
|
||||||
List<Server> newServers = _serversList.map((s) {
|
List<Server> newServers = _serversList.map((s) {
|
||||||
if (s.id == server.id) {
|
if (s.id == server.id) {
|
||||||
|
@ -296,7 +349,7 @@ class ServersProvider with ChangeNotifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> editServer(Server server) async {
|
Future<dynamic> editServer(Server server) async {
|
||||||
final result = await editServerDb(server);
|
final result = await editServerQuery(_dbInstance!, server);
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
List<Server> newServers = _serversList.map((s) {
|
List<Server> newServers = _serversList.map((s) {
|
||||||
if (s.id == server.id) {
|
if (s.id == server.id) {
|
||||||
|
@ -316,7 +369,7 @@ class ServersProvider with ChangeNotifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> removeServer(Server server) async {
|
Future<bool> removeServer(Server server) async {
|
||||||
final result = await removeFromDb(server.id);
|
final result = await removeServerQuery(_dbInstance!, server.id);
|
||||||
if (result == true) {
|
if (result == true) {
|
||||||
_selectedServer = null;
|
_selectedServer = null;
|
||||||
List<Server> newServers = _serversList.where((s) => s.id != server.id).toList();
|
List<Server> newServers = _serversList.where((s) => s.id != server.id).toList();
|
||||||
|
@ -472,63 +525,6 @@ class ServersProvider with ChangeNotifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> saveServerIntoDb(Server server) async {
|
|
||||||
try {
|
|
||||||
return await _dbInstance!.transaction((txn) async {
|
|
||||||
await txn.rawInsert(
|
|
||||||
'INSERT INTO servers (id, name, connectionMethod, domain, path, port, user, password, defaultServer, authToken, runningOnHa) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
|
||||||
[server.id, server.name, server.connectionMethod, server.domain, server.path, server.port, server.user, server.password, server.defaultServer, server.authToken, convertFromBoolToInt(server.runningOnHa)]
|
|
||||||
);
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<dynamic> editServerDb(Server server) async {
|
|
||||||
try {
|
|
||||||
return await _dbInstance!.transaction((txn) async {
|
|
||||||
await txn.rawUpdate(
|
|
||||||
'UPDATE servers SET name = ?, connectionMethod = ?, domain = ?, path = ?, port = ?, user = ?, password = ?, authToken = ?, runningOnHa = ? WHERE id = "${server.id}"',
|
|
||||||
[server.name, server.connectionMethod, server.domain, server.path, server.port, server.user, server.password, server.authToken, server.runningOnHa]
|
|
||||||
);
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> removeFromDb(String id) async {
|
|
||||||
try {
|
|
||||||
return await _dbInstance!.transaction((txn) async {
|
|
||||||
await txn.rawDelete(
|
|
||||||
'DELETE FROM servers WHERE id = "$id"',
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<dynamic> setDefaultServerDb(String id) async {
|
|
||||||
try {
|
|
||||||
return await _dbInstance!.transaction((txn) async {
|
|
||||||
await txn.rawUpdate(
|
|
||||||
'UPDATE servers SET defaultServer = 0 WHERE defaultServer = 1',
|
|
||||||
);
|
|
||||||
await txn.rawUpdate(
|
|
||||||
'UPDATE servers SET defaultServer = 1 WHERE id = "$id"',
|
|
||||||
);
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void checkServerUpdatesAvailable(Server server) async {
|
void checkServerUpdatesAvailable(Server server) async {
|
||||||
setUpdateAvailableLoadStatus(LoadStatus.loading, true);
|
setUpdateAvailableLoadStatus(LoadStatus.loading, true);
|
||||||
final result = await checkServerUpdates(server: server);
|
final result = await checkServerUpdates(server: server);
|
||||||
|
|
100
lib/screens/clients/active_client_tile.dart
Normal file
100
lib/screens/clients/active_client_tile.dart
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:adguard_home_manager/widgets/custom_list_tile.dart';
|
||||||
|
|
||||||
|
import 'package:adguard_home_manager/models/clients.dart';
|
||||||
|
|
||||||
|
class ActiveClientTile extends StatelessWidget {
|
||||||
|
final AutoClient client;
|
||||||
|
final void Function(AutoClient) onTap;
|
||||||
|
final bool splitView;
|
||||||
|
final AutoClient? selectedClient;
|
||||||
|
|
||||||
|
const ActiveClientTile({
|
||||||
|
Key? key,
|
||||||
|
required this.client,
|
||||||
|
required this.onTap,
|
||||||
|
required this.splitView,
|
||||||
|
this.selectedClient
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (splitView == true) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
|
child: Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
borderRadius: BorderRadius.circular(28),
|
||||||
|
child: InkWell(
|
||||||
|
borderRadius: BorderRadius.circular(28),
|
||||||
|
onTap: () => onTap(client),
|
||||||
|
child: Container(
|
||||||
|
width: double.maxFinite,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(28),
|
||||||
|
color: client == selectedClient
|
||||||
|
? Theme.of(context).colorScheme.primaryContainer
|
||||||
|
: null
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
client.name != ''
|
||||||
|
? client.name!
|
||||||
|
: client.ip,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (client.name != '') Text(client.ip)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
client.source,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.onSurface
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return CustomListTile(
|
||||||
|
title: client.name != ''
|
||||||
|
? client.name!
|
||||||
|
: client.ip,
|
||||||
|
subtitle: client.name != ''
|
||||||
|
? client.ip
|
||||||
|
: null,
|
||||||
|
trailing: Text(
|
||||||
|
client.source,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.onSurface
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTap: () => onTap(client),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
250
lib/screens/clients/added_client_tile.dart
Normal file
250
lib/screens/clients/added_client_tile.dart
Normal file
|
@ -0,0 +1,250 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'package:adguard_home_manager/widgets/custom_list_tile.dart';
|
||||||
|
|
||||||
|
import 'package:adguard_home_manager/functions/compare_versions.dart';
|
||||||
|
import 'package:adguard_home_manager/models/clients.dart';
|
||||||
|
import 'package:adguard_home_manager/providers/servers_provider.dart';
|
||||||
|
import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
||||||
|
|
||||||
|
class AddedClientTile extends StatefulWidget {
|
||||||
|
final Client client;
|
||||||
|
final void Function(Client) onTap;
|
||||||
|
final void Function(Client) onLongPress;
|
||||||
|
final void Function(Client) onEdit;
|
||||||
|
final Client? selectedClient;
|
||||||
|
final bool? splitView;
|
||||||
|
|
||||||
|
const AddedClientTile({
|
||||||
|
Key? key,
|
||||||
|
required this.client,
|
||||||
|
required this.onTap,
|
||||||
|
required this.onLongPress,
|
||||||
|
required this.onEdit,
|
||||||
|
this.selectedClient,
|
||||||
|
required this.splitView
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<AddedClientTile> createState() => _AddedClientTileState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AddedClientTileState extends State<AddedClientTile> {
|
||||||
|
bool hover = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final serversProvider = Provider.of<ServersProvider>(context);
|
||||||
|
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||||
|
|
||||||
|
if (widget.splitView == true) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
|
child: Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
borderRadius: BorderRadius.circular(28),
|
||||||
|
child: InkWell(
|
||||||
|
borderRadius: BorderRadius.circular(28),
|
||||||
|
onTap: () => widget.onTap(widget.client),
|
||||||
|
onHover: (v) => setState(() => hover = v),
|
||||||
|
child: Container(
|
||||||
|
width: double.maxFinite,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(28),
|
||||||
|
color: widget.client == widget.selectedClient
|
||||||
|
? Theme.of(context).colorScheme.primaryContainer
|
||||||
|
: null
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
widget.client.ids.toString().replaceAll(RegExp(r'^\[|\]$'), ''),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.filter_list_rounded,
|
||||||
|
size: 19,
|
||||||
|
color: widget.client.filteringEnabled == true
|
||||||
|
? appConfigProvider.useThemeColorForStatus == true
|
||||||
|
? Theme.of(context).colorScheme.primary
|
||||||
|
: Colors.green
|
||||||
|
: appConfigProvider.useThemeColorForStatus == true
|
||||||
|
? Colors.grey
|
||||||
|
: Colors.red,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Icon(
|
||||||
|
Icons.vpn_lock_rounded,
|
||||||
|
size: 18,
|
||||||
|
color: widget.client.safebrowsingEnabled == true
|
||||||
|
? appConfigProvider.useThemeColorForStatus == true
|
||||||
|
? Theme.of(context).colorScheme.primary
|
||||||
|
: Colors.green
|
||||||
|
: appConfigProvider.useThemeColorForStatus == true
|
||||||
|
? Colors.grey
|
||||||
|
: Colors.red,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Icon(
|
||||||
|
Icons.block,
|
||||||
|
size: 18,
|
||||||
|
color: widget.client.parentalEnabled == true
|
||||||
|
? appConfigProvider.useThemeColorForStatus == true
|
||||||
|
? Theme.of(context).colorScheme.primary
|
||||||
|
: Colors.green
|
||||||
|
: appConfigProvider.useThemeColorForStatus == true
|
||||||
|
? Colors.grey
|
||||||
|
: Colors.red,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Icon(
|
||||||
|
Icons.search_rounded,
|
||||||
|
size: 19,
|
||||||
|
color: serverVersionIsAhead(
|
||||||
|
currentVersion: serversProvider.serverStatus.data!.serverVersion,
|
||||||
|
referenceVersion: 'v0.107.28',
|
||||||
|
referenceVersionBeta: 'v0.108.0-b.33'
|
||||||
|
) == true
|
||||||
|
? widget.client.safeSearch != null && widget.client.safeSearch!.enabled == true
|
||||||
|
? appConfigProvider.useThemeColorForStatus == true
|
||||||
|
? Theme.of(context).colorScheme.primary
|
||||||
|
: Colors.green
|
||||||
|
: appConfigProvider.useThemeColorForStatus == true
|
||||||
|
? Colors.grey
|
||||||
|
: Colors.red
|
||||||
|
: widget.client.safesearchEnabled == true
|
||||||
|
? appConfigProvider.useThemeColorForStatus == true
|
||||||
|
? Theme.of(context).colorScheme.primary
|
||||||
|
: Colors.green
|
||||||
|
: appConfigProvider.useThemeColorForStatus == true
|
||||||
|
? Colors.grey
|
||||||
|
: Colors.red,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (hover == true) IconButton(
|
||||||
|
onPressed: () => widget.onEdit(widget.client),
|
||||||
|
icon: const Icon(Icons.edit_rounded)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return CustomListTile(
|
||||||
|
onLongPress: () => widget.onLongPress(widget.client),
|
||||||
|
onTap: () => widget.onTap(widget.client),
|
||||||
|
onHover: (v) => setState(() => hover = v),
|
||||||
|
title: widget.client.name,
|
||||||
|
subtitleWidget: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
widget.client.ids.toString().replaceAll(RegExp(r'^\[|\]$'), ''),
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).listTileTheme.textColor
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.filter_list_rounded,
|
||||||
|
size: 19,
|
||||||
|
color: widget.client.filteringEnabled == true
|
||||||
|
? appConfigProvider.useThemeColorForStatus == true
|
||||||
|
? Theme.of(context).colorScheme.primary
|
||||||
|
: Colors.green
|
||||||
|
: appConfigProvider.useThemeColorForStatus == true
|
||||||
|
? Colors.grey
|
||||||
|
: Colors.red,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Icon(
|
||||||
|
Icons.vpn_lock_rounded,
|
||||||
|
size: 18,
|
||||||
|
color: widget.client.safebrowsingEnabled == true
|
||||||
|
? appConfigProvider.useThemeColorForStatus == true
|
||||||
|
? Theme.of(context).colorScheme.primary
|
||||||
|
: Colors.green
|
||||||
|
: appConfigProvider.useThemeColorForStatus == true
|
||||||
|
? Colors.grey
|
||||||
|
: Colors.red,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Icon(
|
||||||
|
Icons.block,
|
||||||
|
size: 18,
|
||||||
|
color: widget.client.parentalEnabled == true
|
||||||
|
? appConfigProvider.useThemeColorForStatus == true
|
||||||
|
? Theme.of(context).colorScheme.primary
|
||||||
|
: Colors.green
|
||||||
|
: appConfigProvider.useThemeColorForStatus == true
|
||||||
|
? Colors.grey
|
||||||
|
: Colors.red,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Icon(
|
||||||
|
Icons.search_rounded,
|
||||||
|
size: 19,
|
||||||
|
color: serverVersionIsAhead(
|
||||||
|
currentVersion: serversProvider.serverStatus.data!.serverVersion,
|
||||||
|
referenceVersion: 'v0.107.28',
|
||||||
|
referenceVersionBeta: 'v0.108.0-b.33'
|
||||||
|
) == true
|
||||||
|
? widget.client.safeSearch != null && widget.client.safeSearch!.enabled == true
|
||||||
|
? appConfigProvider.useThemeColorForStatus == true
|
||||||
|
? Theme.of(context).colorScheme.primary
|
||||||
|
: Colors.green
|
||||||
|
: appConfigProvider.useThemeColorForStatus == true
|
||||||
|
? Colors.grey
|
||||||
|
: Colors.red
|
||||||
|
: widget.client.safesearchEnabled == true
|
||||||
|
? appConfigProvider.useThemeColorForStatus == true
|
||||||
|
? Theme.of(context).colorScheme.primary
|
||||||
|
: Colors.green
|
||||||
|
: appConfigProvider.useThemeColorForStatus == true
|
||||||
|
? Colors.grey
|
||||||
|
: Colors.red,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
trailing: hover == true
|
||||||
|
? IconButton(
|
||||||
|
onPressed: () => widget.onEdit(widget.client),
|
||||||
|
icon: const Icon(Icons.edit_rounded)
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +1,16 @@
|
||||||
// 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';
|
||||||
|
import 'package:flutter_split_view/flutter_split_view.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';
|
||||||
|
|
||||||
import 'package:adguard_home_manager/screens/clients/client_screen.dart';
|
import 'package:adguard_home_manager/screens/clients/client_screen.dart';
|
||||||
|
import 'package:adguard_home_manager/screens/clients/added_client_tile.dart';
|
||||||
import 'package:adguard_home_manager/screens/clients/remove_client_modal.dart';
|
import 'package:adguard_home_manager/screens/clients/remove_client_modal.dart';
|
||||||
import 'package:adguard_home_manager/screens/clients/fab.dart';
|
import 'package:adguard_home_manager/screens/clients/fab.dart';
|
||||||
import 'package:adguard_home_manager/screens/clients/options_modal.dart';
|
import 'package:adguard_home_manager/screens/clients/options_modal.dart';
|
||||||
|
@ -27,13 +31,19 @@ class AddedList extends StatefulWidget {
|
||||||
final LoadStatus loadStatus;
|
final LoadStatus loadStatus;
|
||||||
final List<Client> data;
|
final List<Client> data;
|
||||||
final Future Function() fetchClients;
|
final Future Function() fetchClients;
|
||||||
|
final void Function(Client) onClientSelected;
|
||||||
|
final Client? selectedClient;
|
||||||
|
final bool splitView;
|
||||||
|
|
||||||
const AddedList({
|
const AddedList({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.scrollController,
|
required this.scrollController,
|
||||||
required this.loadStatus,
|
required this.loadStatus,
|
||||||
required this.data,
|
required this.data,
|
||||||
required this.fetchClients
|
required this.fetchClients,
|
||||||
|
required this.onClientSelected,
|
||||||
|
this.selectedClient,
|
||||||
|
required this.splitView
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -69,6 +79,8 @@ class _AddedListState extends State<AddedList> {
|
||||||
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 width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
void confirmEditClient(Client client) async {
|
void confirmEditClient(Client client) async {
|
||||||
ProcessModal processModal = ProcessModal(context: context);
|
ProcessModal processModal = ProcessModal(context: context);
|
||||||
processModal.open(AppLocalizations.of(context)!.addingClient);
|
processModal.open(AppLocalizations.of(context)!.addingClient);
|
||||||
|
@ -130,6 +142,10 @@ class _AddedListState extends State<AddedList> {
|
||||||
clientsData.clients = clientsData.clients.where((c) => c.name != client.name).toList();
|
clientsData.clients = clientsData.clients.where((c) => c.name != client.name).toList();
|
||||||
serversProvider.setClientsData(clientsData);
|
serversProvider.setClientsData(clientsData);
|
||||||
|
|
||||||
|
if (widget.splitView == true) {
|
||||||
|
SplitView.of(context).popUntil(0);
|
||||||
|
}
|
||||||
|
|
||||||
showSnacbkar(
|
showSnacbkar(
|
||||||
context: context,
|
context: context,
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
|
@ -150,15 +166,31 @@ class _AddedListState extends State<AddedList> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void openClientModal(Client client) {
|
void openClientModal(Client client) {
|
||||||
Navigator.push(context, MaterialPageRoute(
|
if (width > 900 || !(Platform.isAndroid | Platform.isIOS)) {
|
||||||
fullscreenDialog: true,
|
showDialog(
|
||||||
builder: (BuildContext context) => ClientScreen(
|
barrierDismissible: false,
|
||||||
onConfirm: confirmEditClient,
|
context: context,
|
||||||
serverVersion: serversProvider.serverStatus.data!.serverVersion,
|
builder: (BuildContext context) => ClientScreen(
|
||||||
onDelete: deleteClient,
|
onConfirm: confirmEditClient,
|
||||||
client: client,
|
serverVersion: serversProvider.serverStatus.data!.serverVersion,
|
||||||
)
|
onDelete: deleteClient,
|
||||||
));
|
client: client,
|
||||||
|
dialog: true,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Navigator.push(context, MaterialPageRoute(
|
||||||
|
fullscreenDialog: true,
|
||||||
|
builder: (BuildContext context) => ClientScreen(
|
||||||
|
onConfirm: confirmEditClient,
|
||||||
|
serverVersion: serversProvider.serverStatus.data!.serverVersion,
|
||||||
|
onDelete: deleteClient,
|
||||||
|
client: client,
|
||||||
|
dialog: false,
|
||||||
|
)
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void openDeleteModal(Client client) {
|
void openDeleteModal(Client client) {
|
||||||
|
@ -181,9 +213,12 @@ class _AddedListState extends State<AddedList> {
|
||||||
}
|
}
|
||||||
|
|
||||||
return CustomTabContentList(
|
return CustomTabContentList(
|
||||||
|
noSliver: !(Platform.isAndroid || Platform.isIOS),
|
||||||
|
listPadding: widget.splitView == true
|
||||||
|
? const EdgeInsets.only(top: 8)
|
||||||
|
: null,
|
||||||
loadingGenerator: () => SizedBox(
|
loadingGenerator: () => SizedBox(
|
||||||
width: double.maxFinite,
|
width: double.maxFinite,
|
||||||
height: MediaQuery.of(context).size.height-171,
|
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
@ -202,109 +237,28 @@ class _AddedListState extends State<AddedList> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
itemsCount: widget.data.length,
|
itemsCount: widget.data.length,
|
||||||
contentWidget: (index) => ListTile(
|
contentWidget: (index) => AddedClientTile(
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
selectedClient: widget.selectedClient,
|
||||||
isThreeLine: true,
|
client: widget.data[index],
|
||||||
onLongPress: () => openOptionsModal(widget.data[index]),
|
onTap: widget.onClientSelected,
|
||||||
onTap: () => openClientModal(widget.data[index]),
|
onLongPress: openOptionsModal,
|
||||||
title: Padding(
|
onEdit: openClientModal,
|
||||||
padding: const EdgeInsets.only(bottom: 5),
|
splitView: widget.splitView,
|
||||||
child: Text(
|
),
|
||||||
widget.data[index].name,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.normal,
|
|
||||||
color: Theme.of(context).colorScheme.onSurface
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
subtitle: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
widget.data[index].ids.toString().replaceAll(RegExp(r'^\[|\]$'), ''),
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context).listTileTheme.textColor
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 7),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
Icons.filter_list_rounded,
|
|
||||||
size: 19,
|
|
||||||
color: widget.data[index].filteringEnabled == true
|
|
||||||
? appConfigProvider.useThemeColorForStatus == true
|
|
||||||
? Theme.of(context).colorScheme.primary
|
|
||||||
: Colors.green
|
|
||||||
: appConfigProvider.useThemeColorForStatus == true
|
|
||||||
? Colors.grey
|
|
||||||
: Colors.red,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
Icon(
|
|
||||||
Icons.vpn_lock_rounded,
|
|
||||||
size: 18,
|
|
||||||
color: widget.data[index].safebrowsingEnabled == true
|
|
||||||
? appConfigProvider.useThemeColorForStatus == true
|
|
||||||
? Theme.of(context).colorScheme.primary
|
|
||||||
: Colors.green
|
|
||||||
: appConfigProvider.useThemeColorForStatus == true
|
|
||||||
? Colors.grey
|
|
||||||
: Colors.red,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
Icon(
|
|
||||||
Icons.block,
|
|
||||||
size: 18,
|
|
||||||
color: widget.data[index].parentalEnabled == true
|
|
||||||
? appConfigProvider.useThemeColorForStatus == true
|
|
||||||
? Theme.of(context).colorScheme.primary
|
|
||||||
: Colors.green
|
|
||||||
: appConfigProvider.useThemeColorForStatus == true
|
|
||||||
? Colors.grey
|
|
||||||
: Colors.red,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
Icon(
|
|
||||||
Icons.search_rounded,
|
|
||||||
size: 19,
|
|
||||||
color: serverVersionIsAhead(
|
|
||||||
currentVersion: serversProvider.serverStatus.data!.serverVersion,
|
|
||||||
referenceVersion: 'v0.107.28',
|
|
||||||
referenceVersionBeta: 'v0.108.0-b.33'
|
|
||||||
) == true
|
|
||||||
? widget.data[index].safeSearch != null && widget.data[index].safeSearch!.enabled == true
|
|
||||||
? appConfigProvider.useThemeColorForStatus == true
|
|
||||||
? Theme.of(context).colorScheme.primary
|
|
||||||
: Colors.green
|
|
||||||
: appConfigProvider.useThemeColorForStatus == true
|
|
||||||
? Colors.grey
|
|
||||||
: Colors.red
|
|
||||||
: widget.data[index].safesearchEnabled == true
|
|
||||||
? appConfigProvider.useThemeColorForStatus == true
|
|
||||||
? Theme.of(context).colorScheme.primary
|
|
||||||
: Colors.green
|
|
||||||
: appConfigProvider.useThemeColorForStatus == true
|
|
||||||
? Colors.grey
|
|
||||||
: Colors.red,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
noData: SizedBox(
|
noData: SizedBox(
|
||||||
width: double.maxFinite,
|
width: double.maxFinite,
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Padding(
|
||||||
AppLocalizations.of(context)!.noClientsList,
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
textAlign: TextAlign.center,
|
child: Text(
|
||||||
style: TextStyle(
|
AppLocalizations.of(context)!.noClientsList,
|
||||||
fontSize: 24,
|
textAlign: TextAlign.center,
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
style: TextStyle(
|
||||||
|
fontSize: 24,
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 30),
|
const SizedBox(height: 30),
|
||||||
|
@ -318,7 +272,6 @@ class _AddedListState extends State<AddedList> {
|
||||||
),
|
),
|
||||||
errorGenerator: () => SizedBox(
|
errorGenerator: () => SizedBox(
|
||||||
width: double.maxFinite,
|
width: double.maxFinite,
|
||||||
height: MediaQuery.of(context).size.height-171,
|
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
|
|
@ -19,6 +19,7 @@ class ClientScreen extends StatefulWidget {
|
||||||
final String serverVersion;
|
final String serverVersion;
|
||||||
final void Function(Client) onConfirm;
|
final void Function(Client) onConfirm;
|
||||||
final void Function(Client)? onDelete;
|
final void Function(Client)? onDelete;
|
||||||
|
final bool dialog;
|
||||||
|
|
||||||
const ClientScreen({
|
const ClientScreen({
|
||||||
Key? key,
|
Key? key,
|
||||||
|
@ -26,6 +27,7 @@ class ClientScreen extends StatefulWidget {
|
||||||
required this.serverVersion,
|
required this.serverVersion,
|
||||||
required this.onConfirm,
|
required this.onConfirm,
|
||||||
this.onDelete,
|
this.onDelete,
|
||||||
|
required this.dialog
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -300,51 +302,13 @@ class _ClientScreenState extends State<ClientScreen> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Scaffold(
|
Widget content(bool withPaddingTop) {
|
||||||
appBar: AppBar(
|
return ListView(
|
||||||
leading: IconButton(
|
padding: const EdgeInsets.only(top: 0),
|
||||||
onPressed: () => Navigator.pop(context),
|
|
||||||
icon: const Icon(Icons.close)
|
|
||||||
),
|
|
||||||
title: Text(
|
|
||||||
widget.client != null
|
|
||||||
? AppLocalizations.of(context)!.client
|
|
||||||
: AppLocalizations.of(context)!.addClient
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
if (widget.client == null || (widget.client != null && editMode == true)) IconButton(
|
|
||||||
onPressed: validValues == true
|
|
||||||
? () {
|
|
||||||
createClient();
|
|
||||||
Navigator.pop(context);
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
icon: Icon(
|
|
||||||
widget.client != null && editMode == true
|
|
||||||
? Icons.save_rounded
|
|
||||||
: Icons.check_rounded
|
|
||||||
),
|
|
||||||
tooltip: widget.client != null && editMode == true
|
|
||||||
? AppLocalizations.of(context)!.save
|
|
||||||
: AppLocalizations.of(context)!.confirm,
|
|
||||||
),
|
|
||||||
if (widget.client != null && editMode == false) IconButton(
|
|
||||||
onPressed: () => setState(() => editMode = true),
|
|
||||||
icon: const Icon(Icons.edit_rounded),
|
|
||||||
tooltip: AppLocalizations.of(context)!.edit,
|
|
||||||
),
|
|
||||||
if (widget.client != null) IconButton(
|
|
||||||
onPressed: openDeleteClientScreen,
|
|
||||||
icon: const Icon(Icons.delete_rounded),
|
|
||||||
tooltip: AppLocalizations.of(context)!.delete,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
body: ListView(
|
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(height: 24),
|
if (withPaddingTop == true) const SizedBox(height: 24),
|
||||||
|
if (withPaddingTop == false) const SizedBox(height: 6),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
|
@ -693,10 +657,7 @@ class _ClientScreenState extends State<ClientScreen> {
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
Expanded(
|
||||||
width: editMode == true
|
|
||||||
? MediaQuery.of(context).size.width - 108
|
|
||||||
: MediaQuery.of(context).size.width - 40,
|
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
enabled: editMode,
|
enabled: editMode,
|
||||||
controller: controller['controller'],
|
controller: controller['controller'],
|
||||||
|
@ -751,7 +712,125 @@ class _ClientScreenState extends State<ClientScreen> {
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20)
|
const SizedBox(height: 20)
|
||||||
],
|
],
|
||||||
),
|
);
|
||||||
);
|
}
|
||||||
|
|
||||||
|
if (widget.dialog == true) {
|
||||||
|
return Dialog(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
maxWidth: 500
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
icon: const Icon(Icons.clear_rounded)
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
widget.client != null
|
||||||
|
? AppLocalizations.of(context)!.client
|
||||||
|
: AppLocalizations.of(context)!.addClient,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 22
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
if (widget.client == null || (widget.client != null && editMode == true)) IconButton(
|
||||||
|
onPressed: validValues == true
|
||||||
|
? () {
|
||||||
|
createClient();
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
icon: Icon(
|
||||||
|
widget.client != null && editMode == true
|
||||||
|
? Icons.save_rounded
|
||||||
|
: Icons.check_rounded
|
||||||
|
),
|
||||||
|
tooltip: widget.client != null && editMode == true
|
||||||
|
? AppLocalizations.of(context)!.save
|
||||||
|
: AppLocalizations.of(context)!.confirm,
|
||||||
|
),
|
||||||
|
if (widget.client != null && editMode == false) IconButton(
|
||||||
|
onPressed: () => setState(() => editMode = true),
|
||||||
|
icon: const Icon(Icons.edit_rounded),
|
||||||
|
tooltip: AppLocalizations.of(context)!.edit,
|
||||||
|
),
|
||||||
|
if (widget.client != null) IconButton(
|
||||||
|
onPressed: openDeleteClientScreen,
|
||||||
|
icon: const Icon(Icons.delete_rounded),
|
||||||
|
tooltip: AppLocalizations.of(context)!.delete,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Flexible(
|
||||||
|
child: content(false)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
leading: IconButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
icon: const Icon(Icons.close)
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
widget.client != null
|
||||||
|
? AppLocalizations.of(context)!.client
|
||||||
|
: AppLocalizations.of(context)!.addClient
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
if (widget.client == null || (widget.client != null && editMode == true)) IconButton(
|
||||||
|
onPressed: validValues == true
|
||||||
|
? () {
|
||||||
|
createClient();
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
icon: Icon(
|
||||||
|
widget.client != null && editMode == true
|
||||||
|
? Icons.save_rounded
|
||||||
|
: Icons.check_rounded
|
||||||
|
),
|
||||||
|
tooltip: widget.client != null && editMode == true
|
||||||
|
? AppLocalizations.of(context)!.save
|
||||||
|
: AppLocalizations.of(context)!.confirm,
|
||||||
|
),
|
||||||
|
if (widget.client != null && editMode == false) IconButton(
|
||||||
|
onPressed: () => setState(() => editMode = true),
|
||||||
|
icon: const Icon(Icons.edit_rounded),
|
||||||
|
tooltip: AppLocalizations.of(context)!.edit,
|
||||||
|
),
|
||||||
|
if (widget.client != null) IconButton(
|
||||||
|
onPressed: openDeleteClientScreen,
|
||||||
|
icon: const Icon(Icons.delete_rounded),
|
||||||
|
tooltip: AppLocalizations.of(context)!.delete,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: content(true)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,9 +1,14 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_split_view/flutter_split_view.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';
|
||||||
|
|
||||||
import 'package:adguard_home_manager/screens/clients/clients_list.dart';
|
import 'package:adguard_home_manager/screens/clients/clients_list.dart';
|
||||||
import 'package:adguard_home_manager/screens/clients/search_clients.dart';
|
import 'package:adguard_home_manager/screens/clients/search_clients.dart';
|
||||||
|
import 'package:adguard_home_manager/screens/clients/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/screens/clients/added_list.dart';
|
||||||
|
|
||||||
import 'package:adguard_home_manager/models/app_log.dart';
|
import 'package:adguard_home_manager/models/app_log.dart';
|
||||||
|
@ -56,6 +61,9 @@ class _ClientsWidgetState extends State<ClientsWidget> with TickerProviderStateM
|
||||||
late TabController tabController;
|
late TabController tabController;
|
||||||
final ScrollController scrollController = ScrollController();
|
final ScrollController scrollController = ScrollController();
|
||||||
|
|
||||||
|
bool searchMode = false;
|
||||||
|
final TextEditingController searchController = TextEditingController();
|
||||||
|
|
||||||
Future fetchClients() async {
|
Future fetchClients() async {
|
||||||
widget.setLoadingStatus(LoadStatus.loading, false);
|
widget.setLoadingStatus(LoadStatus.loading, false);
|
||||||
final result = await getClients(widget.server);
|
final result = await getClients(widget.server);
|
||||||
|
@ -90,83 +98,207 @@ class _ClientsWidgetState extends State<ClientsWidget> with TickerProviderStateM
|
||||||
@override
|
@override
|
||||||
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 width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
return DefaultTabController(
|
PreferredSizeWidget tabBar() {
|
||||||
length: 2,
|
return TabBar(
|
||||||
child: NestedScrollView(
|
controller: tabController,
|
||||||
controller: scrollController,
|
unselectedLabelColor: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
headerSliverBuilder: ((context, innerBoxIsScrolled) {
|
tabs: [
|
||||||
return [
|
Tab(
|
||||||
SliverOverlapAbsorber(
|
child: Row(
|
||||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
mainAxisSize: MainAxisSize.min,
|
||||||
sliver: SliverAppBar(
|
children: [
|
||||||
title: Text(AppLocalizations.of(context)!.clients),
|
const Icon(Icons.devices),
|
||||||
pinned: true,
|
const SizedBox(width: 8),
|
||||||
floating: true,
|
Text(AppLocalizations.of(context)!.activeClients)
|
||||||
centerTitle: false,
|
],
|
||||||
forceElevated: innerBoxIsScrolled,
|
),
|
||||||
actions: [
|
|
||||||
if (serversProvider.clients.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(
|
|
||||||
controller: tabController,
|
|
||||||
unselectedLabelColor: Theme.of(context).colorScheme.onSurfaceVariant,
|
|
||||||
tabs: [
|
|
||||||
Tab(
|
|
||||||
icon: const Icon(Icons.devices),
|
|
||||||
text: AppLocalizations.of(context)!.activeClients,
|
|
||||||
),
|
|
||||||
Tab(
|
|
||||||
icon: const Icon(Icons.add_rounded),
|
|
||||||
text: AppLocalizations.of(context)!.added,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
),
|
|
||||||
)
|
|
||||||
];
|
|
||||||
}),
|
|
||||||
body: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Theme.of(context).scaffoldBackgroundColor,
|
|
||||||
border: Border(
|
|
||||||
top: BorderSide(
|
|
||||||
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.1)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
child: TabBarView(
|
Tab(
|
||||||
controller: tabController,
|
child: Row(
|
||||||
children: [
|
mainAxisSize: MainAxisSize.min,
|
||||||
ClientsList(
|
children: [
|
||||||
scrollController: scrollController,
|
const Icon(Icons.add_rounded),
|
||||||
loadStatus: serversProvider.clients.loadStatus,
|
const SizedBox(width: 8),
|
||||||
data: serversProvider.clients.loadStatus == LoadStatus.loaded
|
Text(AppLocalizations.of(context)!.added)
|
||||||
? serversProvider.clients.data!.autoClientsData : [],
|
],
|
||||||
fetchClients: fetchClients,
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget tabBarView(bool sliver) {
|
||||||
|
return TabBarView(
|
||||||
|
controller: tabController,
|
||||||
|
children: [
|
||||||
|
ClientsList(
|
||||||
|
scrollController: scrollController,
|
||||||
|
loadStatus: serversProvider.clients.loadStatus,
|
||||||
|
data: serversProvider.clients.loadStatus == LoadStatus.loaded
|
||||||
|
? serversProvider.filteredActiveClients : [],
|
||||||
|
fetchClients: fetchClients,
|
||||||
|
onClientSelected: (client) => Navigator.push(context, MaterialPageRoute(
|
||||||
|
builder: (context) => LogsListClient(
|
||||||
|
ip: client.ip,
|
||||||
|
serversProvider: serversProvider,
|
||||||
|
appConfigProvider: appConfigProvider
|
||||||
|
)
|
||||||
|
)),
|
||||||
|
splitView: false,
|
||||||
|
sliver: sliver,
|
||||||
|
),
|
||||||
|
AddedList(
|
||||||
|
scrollController: scrollController,
|
||||||
|
loadStatus: serversProvider.clients.loadStatus,
|
||||||
|
data: serversProvider.clients.loadStatus == LoadStatus.loaded
|
||||||
|
? serversProvider.filteredAddedClients : [],
|
||||||
|
fetchClients: fetchClients,
|
||||||
|
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
|
||||||
),
|
),
|
||||||
AddedList(
|
),
|
||||||
scrollController: scrollController,
|
),
|
||||||
loadStatus: serversProvider.clients.loadStatus,
|
|
||||||
data: serversProvider.clients.loadStatus == LoadStatus.loaded
|
|
||||||
? serversProvider.clients.data!.clients : [],
|
|
||||||
fetchClients: fetchClients,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
)
|
child: ClientsDesktopView(
|
||||||
);
|
serversProvider: serversProvider,
|
||||||
|
appConfigProvider: appConfigProvider,
|
||||||
|
fetchClients: fetchClients,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (!(Platform.isAndroid || Platform.isIOS)) {
|
||||||
|
return DefaultTabController(
|
||||||
|
length: 2,
|
||||||
|
child: Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(AppLocalizations.of(context)!.clients),
|
||||||
|
centerTitle: false,
|
||||||
|
actions: [
|
||||||
|
if (serversProvider.clients.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 = "";
|
||||||
|
serversProvider.setSearchTermClients(null);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.arrow_back_rounded)
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: TextField(
|
||||||
|
controller: searchController,
|
||||||
|
onChanged: (value) => serversProvider.setSearchTermClients(value),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
suffixIcon: IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
searchController.text = "";
|
||||||
|
serversProvider.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
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: Text(AppLocalizations.of(context)!.clients),
|
||||||
|
pinned: true,
|
||||||
|
floating: true,
|
||||||
|
centerTitle: false,
|
||||||
|
forceElevated: innerBoxIsScrolled,
|
||||||
|
actions: [
|
||||||
|
if (serversProvider.clients.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)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
248
lib/screens/clients/clients_desktop_view.dart
Normal file
248
lib/screens/clients/clients_desktop_view.dart
Normal file
|
@ -0,0 +1,248 @@
|
||||||
|
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/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/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;
|
||||||
|
final Future Function() fetchClients;
|
||||||
|
|
||||||
|
const ClientsDesktopView({
|
||||||
|
Key? key,
|
||||||
|
required this.serversProvider,
|
||||||
|
required this.appConfigProvider,
|
||||||
|
required this.fetchClients
|
||||||
|
}) : 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 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,
|
||||||
|
loadStatus: serversProvider.clients.loadStatus,
|
||||||
|
data: serversProvider.clients.loadStatus == LoadStatus.loaded
|
||||||
|
? serversProvider.filteredActiveClients : [],
|
||||||
|
fetchClients: widget.fetchClients,
|
||||||
|
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,
|
||||||
|
loadStatus: serversProvider.clients.loadStatus,
|
||||||
|
data: serversProvider.clients.loadStatus == LoadStatus.loaded
|
||||||
|
? serversProvider.filteredAddedClients : [],
|
||||||
|
fetchClients: widget.fetchClients,
|
||||||
|
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 = "";
|
||||||
|
serversProvider.setSearchTermClients(null);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.arrow_back_rounded)
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: TextField(
|
||||||
|
controller: searchController,
|
||||||
|
onChanged: (value) => serversProvider.setSearchTermClients(value),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
suffixIcon: IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
searchController.text = "";
|
||||||
|
serversProvider.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 (serversProvider.clients.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 (serversProvider.clients.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,36 +1,44 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.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/widgets/custom_list_tile.dart';
|
import 'package:adguard_home_manager/screens/clients/active_client_tile.dart';
|
||||||
|
|
||||||
import 'package:adguard_home_manager/widgets/tab_content_list.dart';
|
import 'package:adguard_home_manager/widgets/tab_content_list.dart';
|
||||||
|
|
||||||
import 'package:adguard_home_manager/models/clients.dart';
|
import 'package:adguard_home_manager/models/clients.dart';
|
||||||
import 'package:adguard_home_manager/models/applied_filters.dart';
|
|
||||||
import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
|
||||||
import 'package:adguard_home_manager/constants/enums.dart';
|
import 'package:adguard_home_manager/constants/enums.dart';
|
||||||
import 'package:adguard_home_manager/providers/logs_provider.dart';
|
|
||||||
|
|
||||||
class ClientsList extends StatelessWidget {
|
class ClientsList extends StatelessWidget {
|
||||||
final ScrollController scrollController;
|
final ScrollController scrollController;
|
||||||
final LoadStatus loadStatus;
|
final LoadStatus loadStatus;
|
||||||
final List<AutoClient> data;
|
final List<AutoClient> data;
|
||||||
final Future Function() fetchClients;
|
final Future Function() fetchClients;
|
||||||
|
final void Function(AutoClient) onClientSelected;
|
||||||
|
final AutoClient? selectedClient;
|
||||||
|
final bool splitView;
|
||||||
|
final bool sliver;
|
||||||
|
|
||||||
const ClientsList({
|
const ClientsList({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.scrollController,
|
required this.scrollController,
|
||||||
required this.loadStatus,
|
required this.loadStatus,
|
||||||
required this.data,
|
required this.data,
|
||||||
required this.fetchClients
|
required this.fetchClients,
|
||||||
|
required this.onClientSelected,
|
||||||
|
this.selectedClient,
|
||||||
|
required this.splitView,
|
||||||
|
required this.sliver
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
|
||||||
final logsProvider = Provider.of<LogsProvider>(context);
|
|
||||||
|
|
||||||
return CustomTabContentList(
|
return CustomTabContentList(
|
||||||
|
listPadding: splitView == true
|
||||||
|
? const EdgeInsets.only(top: 8)
|
||||||
|
: 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,
|
||||||
|
@ -52,32 +60,12 @@ class ClientsList extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
itemsCount: data.length,
|
itemsCount: data.length,
|
||||||
contentWidget: (index) => CustomListTile(
|
contentWidget: (index) => ActiveClientTile(
|
||||||
title: data[index].name != ''
|
client: data[index],
|
||||||
? data[index].name!
|
onTap: onClientSelected,
|
||||||
: data[index].ip,
|
splitView: splitView,
|
||||||
subtitle: data[index].name != ''
|
selectedClient: selectedClient,
|
||||||
? data[index].ip
|
),
|
||||||
: null,
|
|
||||||
trailing: Text(
|
|
||||||
data[index].source,
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context).colorScheme.onSurface
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
logsProvider.setSearchText(null);
|
|
||||||
logsProvider.setSelectedClients([data[index].ip]);
|
|
||||||
logsProvider.setAppliedFilters(
|
|
||||||
AppliedFiters(
|
|
||||||
selectedResultStatus: 'all',
|
|
||||||
searchText: null,
|
|
||||||
clients: [data[index].ip]
|
|
||||||
)
|
|
||||||
);
|
|
||||||
appConfigProvider.setSelectedScreen(2);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
noData: SizedBox(
|
noData: SizedBox(
|
||||||
width: double.maxFinite,
|
width: double.maxFinite,
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
// 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: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';
|
||||||
|
@ -23,6 +25,8 @@ class ClientsFab extends StatelessWidget {
|
||||||
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 width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
void confirmAddClient(Client client) async {
|
void confirmAddClient(Client client) async {
|
||||||
ProcessModal processModal = ProcessModal(context: context);
|
ProcessModal processModal = ProcessModal(context: context);
|
||||||
processModal.open(AppLocalizations.of(context)!.addingClient);
|
processModal.open(AppLocalizations.of(context)!.addingClient);
|
||||||
|
@ -65,13 +69,27 @@ class ClientsFab extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
void openAddClient() {
|
void openAddClient() {
|
||||||
Navigator.push(context, MaterialPageRoute(
|
if (width > 900 || !(Platform.isAndroid | Platform.isIOS)) {
|
||||||
fullscreenDialog: true,
|
showDialog(
|
||||||
builder: (BuildContext context) => ClientScreen(
|
barrierDismissible: false,
|
||||||
onConfirm: confirmAddClient,
|
context: context,
|
||||||
serverVersion: serversProvider.serverStatus.data!.serverVersion,
|
builder: (BuildContext context) => ClientScreen(
|
||||||
)
|
onConfirm: confirmAddClient,
|
||||||
));
|
serverVersion: serversProvider.serverStatus.data!.serverVersion,
|
||||||
|
dialog: true,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Navigator.push(context, MaterialPageRoute(
|
||||||
|
fullscreenDialog: true,
|
||||||
|
builder: (BuildContext context) => ClientScreen(
|
||||||
|
onConfirm: confirmAddClient,
|
||||||
|
serverVersion: serversProvider.serverStatus.data!.serverVersion,
|
||||||
|
dialog: false,
|
||||||
|
)
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return FloatingActionButton(
|
return FloatingActionButton(
|
||||||
|
|
229
lib/screens/clients/logs_list_client.dart
Normal file
229
lib/screens/clients/logs_list_client.dart
Normal file
|
@ -0,0 +1,229 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
import 'package:adguard_home_manager/screens/logs/log_tile.dart';
|
||||||
|
import 'package:adguard_home_manager/screens/logs/log_details_screen.dart';
|
||||||
|
|
||||||
|
import 'package:adguard_home_manager/models/logs.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/services/http_requests.dart';
|
||||||
|
|
||||||
|
class LogsListClient extends StatefulWidget {
|
||||||
|
final String ip;
|
||||||
|
final String? name;
|
||||||
|
final ServersProvider serversProvider;
|
||||||
|
final AppConfigProvider appConfigProvider;
|
||||||
|
|
||||||
|
const LogsListClient({
|
||||||
|
Key? key,
|
||||||
|
required this.ip,
|
||||||
|
this.name,
|
||||||
|
required this.serversProvider,
|
||||||
|
required this.appConfigProvider
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<LogsListClient> createState() => _LogsListClientState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LogsListClientState extends State<LogsListClient> {
|
||||||
|
late ScrollController scrollController;
|
||||||
|
|
||||||
|
bool isLoadingMore = false;
|
||||||
|
|
||||||
|
int logsQuantity = 100;
|
||||||
|
int offset = 0;
|
||||||
|
|
||||||
|
int loadStatus = 0;
|
||||||
|
LogsData? logsData;
|
||||||
|
|
||||||
|
String previousIp = "";
|
||||||
|
|
||||||
|
bool showDivider = true;
|
||||||
|
|
||||||
|
Future fetchLogs({
|
||||||
|
int? inOffset,
|
||||||
|
bool? loadingMore,
|
||||||
|
String? responseStatus,
|
||||||
|
String? searchText,
|
||||||
|
}) async {
|
||||||
|
int offst = inOffset ?? offset;
|
||||||
|
|
||||||
|
if (loadingMore != null && loadingMore == true) {
|
||||||
|
setState(() => isLoadingMore = true);
|
||||||
|
}
|
||||||
|
|
||||||
|
final result = await getLogs(
|
||||||
|
server: widget.serversProvider.selectedServer!,
|
||||||
|
count: logsQuantity,
|
||||||
|
offset: offst,
|
||||||
|
search: '"${widget.ip}"'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (loadingMore != null && loadingMore == true) {
|
||||||
|
setState(() => isLoadingMore = false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
if (result['result'] == 'success') {
|
||||||
|
setState(() => offset = inOffset != null ? inOffset+logsQuantity : offset+logsQuantity);
|
||||||
|
if (loadingMore != null && loadingMore == true && logsData != null) {
|
||||||
|
LogsData newLogsData = result['data'];
|
||||||
|
newLogsData.data = [...logsData!.data, ...result['data'].data];
|
||||||
|
setState(() => logsData = newLogsData);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
LogsData newLogsData = result['data'];
|
||||||
|
setState(() => logsData = newLogsData);
|
||||||
|
}
|
||||||
|
setState(() => loadStatus = 1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setState(() => loadStatus = 2);
|
||||||
|
widget.appConfigProvider.addLog(result['log']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void scrollListener() {
|
||||||
|
if (scrollController.position.extentAfter < 500 && isLoadingMore == false) {
|
||||||
|
fetchLogs(loadingMore: true);
|
||||||
|
}
|
||||||
|
if (scrollController.position.pixels > 0) {
|
||||||
|
setState(() => showDivider = false);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setState(() => showDivider = true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
scrollController = ScrollController()..addListener(scrollListener);
|
||||||
|
fetchLogs(inOffset: 0);
|
||||||
|
setState(() => previousIp = widget.ip);
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (widget.ip != previousIp) {
|
||||||
|
setState(() => loadStatus = 0);
|
||||||
|
if (scrollController.hasClients) scrollController.animateTo(0, duration: const Duration(milliseconds: 1), curve: Curves.ease);
|
||||||
|
fetchLogs(inOffset: 0);
|
||||||
|
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:
|
||||||
|
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) => showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => LogDetailsScreen(
|
||||||
|
log: log,
|
||||||
|
dialog: true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
return SizedBox(
|
||||||
|
width: double.maxFinite,
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Icon(
|
||||||
|
Icons.error,
|
||||||
|
color: Colors.red,
|
||||||
|
size: 50,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 30),
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context)!.logsNotLoaded,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 22,
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(widget.name != null && widget.name != '' ? widget.name! : widget.ip),
|
||||||
|
centerTitle: true,
|
||||||
|
actions: [
|
||||||
|
if (!(Platform.isAndroid || Platform.isIOS)) ...[
|
||||||
|
IconButton(
|
||||||
|
onPressed: fetchLogs,
|
||||||
|
icon: const Icon(Icons.refresh_rounded),
|
||||||
|
tooltip: AppLocalizations.of(context)!.refresh,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8)
|
||||||
|
]
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: status(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -64,109 +64,114 @@ class _SafeSearchModalState extends State<SafeSearchModal> {
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
content: Wrap(
|
content: ConstrainedBox(
|
||||||
children: [
|
constraints: const BoxConstraints(
|
||||||
Padding(
|
maxWidth: 400
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
),
|
||||||
child: Material(
|
child: Wrap(
|
||||||
color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
|
children: [
|
||||||
borderRadius: BorderRadius.circular(28),
|
Padding(
|
||||||
child: InkWell(
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
onTap: widget.disabled == true
|
child: Material(
|
||||||
? null
|
color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
|
||||||
: () => setState(() => generalEnabled = !generalEnabled),
|
|
||||||
borderRadius: BorderRadius.circular(28),
|
borderRadius: BorderRadius.circular(28),
|
||||||
child: Padding(
|
child: InkWell(
|
||||||
padding: const EdgeInsets.symmetric(
|
onTap: widget.disabled == true
|
||||||
horizontal: 20,
|
? null
|
||||||
vertical: 5
|
: () => setState(() => generalEnabled = !generalEnabled),
|
||||||
),
|
borderRadius: BorderRadius.circular(28),
|
||||||
child: Row(
|
child: Padding(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
padding: const EdgeInsets.symmetric(
|
||||||
children: [
|
horizontal: 20,
|
||||||
Text(
|
vertical: 5
|
||||||
AppLocalizations.of(context)!.enable,
|
),
|
||||||
style: TextStyle(
|
child: Row(
|
||||||
fontSize: 16,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
color: widget.disabled == true
|
children: [
|
||||||
? Colors.grey
|
Text(
|
||||||
: Theme.of(context).colorScheme.onSurface
|
AppLocalizations.of(context)!.enable,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
color: widget.disabled == true
|
||||||
|
? Colors.grey
|
||||||
|
: Theme.of(context).colorScheme.onSurface
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
Switch(
|
||||||
Switch(
|
value: generalEnabled,
|
||||||
value: generalEnabled,
|
onChanged: widget.disabled == true
|
||||||
onChanged: widget.disabled == true
|
? null
|
||||||
? null
|
: (value) => setState(() => generalEnabled = value),
|
||||||
: (value) => setState(() => generalEnabled = value),
|
)
|
||||||
)
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 4, width: double.maxFinite),
|
||||||
const SizedBox(height: 4, width: double.maxFinite),
|
CustomCheckboxListTile(
|
||||||
CustomCheckboxListTile(
|
value: bingEnabled,
|
||||||
value: bingEnabled,
|
onChanged: (value) => setState(() => bingEnabled = value),
|
||||||
onChanged: (value) => setState(() => bingEnabled = value),
|
title: "Bing",
|
||||||
title: "Bing",
|
disabled: widget.disabled || !generalEnabled,
|
||||||
disabled: widget.disabled || !generalEnabled,
|
padding: const EdgeInsets.symmetric(
|
||||||
padding: const EdgeInsets.symmetric(
|
horizontal: 36,
|
||||||
horizontal: 36,
|
vertical: 4
|
||||||
vertical: 4
|
),
|
||||||
),
|
),
|
||||||
),
|
CustomCheckboxListTile(
|
||||||
CustomCheckboxListTile(
|
value: duckduckgoEnabled,
|
||||||
value: duckduckgoEnabled,
|
onChanged: (value) => setState(() => duckduckgoEnabled = value),
|
||||||
onChanged: (value) => setState(() => duckduckgoEnabled = value),
|
title: "DuckDuckGo",
|
||||||
title: "DuckDuckGo",
|
disabled: widget.disabled || !generalEnabled,
|
||||||
disabled: widget.disabled || !generalEnabled,
|
padding: const EdgeInsets.symmetric(
|
||||||
padding: const EdgeInsets.symmetric(
|
horizontal: 36,
|
||||||
horizontal: 36,
|
vertical: 4
|
||||||
vertical: 4
|
),
|
||||||
),
|
),
|
||||||
),
|
CustomCheckboxListTile(
|
||||||
CustomCheckboxListTile(
|
value: googleEnabled,
|
||||||
value: googleEnabled,
|
onChanged: (value) => setState(() => googleEnabled = value),
|
||||||
onChanged: (value) => setState(() => googleEnabled = value),
|
title: "Google",
|
||||||
title: "Google",
|
disabled: widget.disabled || !generalEnabled,
|
||||||
disabled: widget.disabled || !generalEnabled,
|
padding: const EdgeInsets.symmetric(
|
||||||
padding: const EdgeInsets.symmetric(
|
horizontal: 36,
|
||||||
horizontal: 36,
|
vertical: 4
|
||||||
vertical: 4
|
),
|
||||||
),
|
),
|
||||||
),
|
CustomCheckboxListTile(
|
||||||
CustomCheckboxListTile(
|
value: pixabayEnabled,
|
||||||
value: pixabayEnabled,
|
onChanged: (value) => setState(() => pixabayEnabled = value),
|
||||||
onChanged: (value) => setState(() => pixabayEnabled = value),
|
title: "Pixabay",
|
||||||
title: "Pixabay",
|
disabled: widget.disabled || !generalEnabled,
|
||||||
disabled: widget.disabled || !generalEnabled,
|
padding: const EdgeInsets.symmetric(
|
||||||
padding: const EdgeInsets.symmetric(
|
horizontal: 36,
|
||||||
horizontal: 36,
|
vertical: 4
|
||||||
vertical: 4
|
),
|
||||||
),
|
),
|
||||||
),
|
CustomCheckboxListTile(
|
||||||
CustomCheckboxListTile(
|
value: yandexEnabled,
|
||||||
value: yandexEnabled,
|
onChanged: (value) => setState(() => yandexEnabled = value),
|
||||||
onChanged: (value) => setState(() => yandexEnabled = value),
|
title: "Yandex",
|
||||||
title: "Yandex",
|
disabled: widget.disabled || !generalEnabled,
|
||||||
disabled: widget.disabled || !generalEnabled,
|
padding: const EdgeInsets.symmetric(
|
||||||
padding: const EdgeInsets.symmetric(
|
horizontal: 36,
|
||||||
horizontal: 36,
|
vertical: 4
|
||||||
vertical: 4
|
),
|
||||||
),
|
),
|
||||||
),
|
CustomCheckboxListTile(
|
||||||
CustomCheckboxListTile(
|
value: youtubeEnabled,
|
||||||
value: youtubeEnabled,
|
onChanged: (value) => setState(() => youtubeEnabled = value),
|
||||||
onChanged: (value) => setState(() => youtubeEnabled = value),
|
title: "YouTube",
|
||||||
title: "YouTube",
|
disabled: widget.disabled || !generalEnabled,
|
||||||
disabled: widget.disabled || !generalEnabled,
|
padding: const EdgeInsets.symmetric(
|
||||||
padding: const EdgeInsets.symmetric(
|
horizontal: 36,
|
||||||
horizontal: 36,
|
vertical: 4
|
||||||
vertical: 4
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
// 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:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
@ -99,6 +101,8 @@ class _SearchClientsWidgetState extends State<SearchClientsWidget> {
|
||||||
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 width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
void deleteClient(Client client) async {
|
void deleteClient(Client client) async {
|
||||||
ProcessModal processModal = ProcessModal(context: context);
|
ProcessModal processModal = ProcessModal(context: context);
|
||||||
processModal.open(AppLocalizations.of(context)!.removingClient);
|
processModal.open(AppLocalizations.of(context)!.removingClient);
|
||||||
|
@ -183,15 +187,31 @@ class _SearchClientsWidgetState extends State<SearchClientsWidget> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void openClientModal(Client client) {
|
void openClientModal(Client client) {
|
||||||
Navigator.push(context, MaterialPageRoute(
|
if (width > 900 || !(Platform.isAndroid | Platform.isIOS)) {
|
||||||
fullscreenDialog: true,
|
showDialog(
|
||||||
builder: (BuildContext context) => ClientScreen(
|
barrierDismissible: false,
|
||||||
onConfirm: confirmEditClient,
|
context: context,
|
||||||
onDelete: deleteClient,
|
builder: (BuildContext context) => ClientScreen(
|
||||||
client: client,
|
onConfirm: confirmEditClient,
|
||||||
serverVersion: serversProvider.serverStatus.data!.serverVersion,
|
serverVersion: serversProvider.serverStatus.data!.serverVersion,
|
||||||
)
|
onDelete: deleteClient,
|
||||||
));
|
client: client,
|
||||||
|
dialog: true,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Navigator.push(context, MaterialPageRoute(
|
||||||
|
fullscreenDialog: true,
|
||||||
|
builder: (BuildContext context) => ClientScreen(
|
||||||
|
onConfirm: confirmEditClient,
|
||||||
|
serverVersion: serversProvider.serverStatus.data!.serverVersion,
|
||||||
|
onDelete: deleteClient,
|
||||||
|
client: client,
|
||||||
|
dialog: false,
|
||||||
|
)
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void openDeleteModal(Client client) {
|
void openDeleteModal(Client client) {
|
||||||
|
|
|
@ -66,6 +66,7 @@ class _ConnectState extends State<Connect> {
|
||||||
controllers: expandableControllerList,
|
controllers: expandableControllerList,
|
||||||
onChange: expandOrContract,
|
onChange: expandOrContract,
|
||||||
scrollController: scrollController,
|
scrollController: scrollController,
|
||||||
|
breakingWidth: 700,
|
||||||
),
|
),
|
||||||
AnimatedPositioned(
|
AnimatedPositioned(
|
||||||
duration: const Duration(milliseconds: 100),
|
duration: const Duration(milliseconds: 100),
|
||||||
|
|
|
@ -6,12 +6,27 @@ class FabConnect extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
void openAddServerModal() async {
|
void openAddServerModal() async {
|
||||||
await Future.delayed(const Duration(seconds: 0), (() => {
|
await Future.delayed(const Duration(seconds: 0), (() => {
|
||||||
Navigator.push(context, MaterialPageRoute(
|
if (width > 700) {
|
||||||
fullscreenDialog: true,
|
showDialog(
|
||||||
builder: (BuildContext context) => const AddServerModal()
|
context: context,
|
||||||
))
|
barrierDismissible: false,
|
||||||
|
builder: (context) => const AddServerModal(
|
||||||
|
window: true,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Navigator.push(context, MaterialPageRoute(
|
||||||
|
fullscreenDialog: true,
|
||||||
|
builder: (BuildContext context) => const AddServerModal(
|
||||||
|
window: false,
|
||||||
|
)
|
||||||
|
))
|
||||||
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
// 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: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';
|
||||||
|
@ -15,12 +17,14 @@ import 'package:adguard_home_manager/constants/enums.dart';
|
||||||
import 'package:adguard_home_manager/models/filtering.dart';
|
import 'package:adguard_home_manager/models/filtering.dart';
|
||||||
import 'package:adguard_home_manager/providers/servers_provider.dart';
|
import 'package:adguard_home_manager/providers/servers_provider.dart';
|
||||||
|
|
||||||
class FiltersFab extends StatelessWidget {
|
class AddFiltersButton extends StatelessWidget {
|
||||||
final String type;
|
final String type;
|
||||||
|
final Widget Function(void Function()) widget;
|
||||||
|
|
||||||
const FiltersFab({
|
const AddFiltersButton({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.type,
|
required this.type,
|
||||||
|
required this.widget
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -28,6 +32,8 @@ class FiltersFab extends StatelessWidget {
|
||||||
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 width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
void confirmAddRule(String rule) async {
|
void confirmAddRule(String rule) async {
|
||||||
ProcessModal processModal = ProcessModal(context: context);
|
ProcessModal processModal = ProcessModal(context: context);
|
||||||
processModal.open(AppLocalizations.of(context)!.addingRule);
|
processModal.open(AppLocalizations.of(context)!.addingRule);
|
||||||
|
@ -64,14 +70,27 @@ class FiltersFab extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
void openAddCustomRule() {
|
void openAddCustomRule() {
|
||||||
Navigator.of(context).push(
|
if (width > 700 || !(Platform.isAndroid || Platform.isIOS)) {
|
||||||
MaterialPageRoute(
|
showDialog(
|
||||||
fullscreenDialog: true,
|
context: context,
|
||||||
builder: (context) => AddCustomRule(
|
builder: (context) => AddCustomRule(
|
||||||
onConfirm: confirmAddRule
|
onConfirm: confirmAddRule,
|
||||||
|
dialog: true,
|
||||||
),
|
),
|
||||||
)
|
barrierDismissible: false
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Navigator.of(context).push(
|
||||||
|
MaterialPageRoute(
|
||||||
|
fullscreenDialog: true,
|
||||||
|
builder: (context) => AddCustomRule(
|
||||||
|
onConfirm: confirmAddRule,
|
||||||
|
dialog: false,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void confirmAddList({required String name, required String url, required String type}) async {
|
void confirmAddList({required String name, required String url, required String type}) async {
|
||||||
|
@ -154,22 +173,34 @@ class FiltersFab extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
void openAddWhitelistBlacklist() {
|
void openAddWhitelistBlacklist() {
|
||||||
showModalBottomSheet(
|
if (width > 700 || !(Platform.isAndroid || Platform.isIOS)) {
|
||||||
context: context,
|
showDialog(
|
||||||
builder: (ctx) => AddListModal(
|
context: context,
|
||||||
type: type,
|
builder: (ctx) => AddListModal(
|
||||||
onConfirm: confirmAddList,
|
type: type,
|
||||||
),
|
onConfirm: confirmAddList,
|
||||||
isScrollControlled: true,
|
dialog: true,
|
||||||
backgroundColor: Colors.transparent
|
),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder: (ctx) => AddListModal(
|
||||||
|
type: type,
|
||||||
|
onConfirm: confirmAddList,
|
||||||
|
dialog: false,
|
||||||
|
),
|
||||||
|
isScrollControlled: true,
|
||||||
|
backgroundColor: Colors.transparent
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return FloatingActionButton(
|
return widget(
|
||||||
onPressed: type == 'blacklist' || type == 'whitelist'
|
type == 'blacklist' || type == 'whitelist'
|
||||||
? () => openAddWhitelistBlacklist()
|
? () => openAddWhitelistBlacklist()
|
||||||
: () => openAddCustomRule(),
|
: () => openAddCustomRule(),
|
||||||
child: const Icon(Icons.add),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,17 +1,17 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
import 'package:flutter_web_browser/flutter_web_browser.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/providers/app_config_provider.dart';
|
|
||||||
|
|
||||||
class AddCustomRule extends StatefulWidget {
|
class AddCustomRule extends StatefulWidget {
|
||||||
final void Function(String) onConfirm;
|
final void Function(String) onConfirm;
|
||||||
|
final bool dialog;
|
||||||
|
|
||||||
const AddCustomRule({
|
const AddCustomRule({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.onConfirm
|
required this.onConfirm,
|
||||||
|
required this.dialog
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -72,293 +72,338 @@ class _AddCustomRuleState extends State<AddCustomRule> {
|
||||||
|
|
||||||
return rule;
|
return rule;
|
||||||
}
|
}
|
||||||
|
|
||||||
void openDocsPage() {
|
|
||||||
FlutterWebBrowser.openWebPage(
|
|
||||||
url: Urls.customRuleDocs,
|
|
||||||
customTabsOptions: const CustomTabsOptions(
|
|
||||||
instantAppsEnabled: true,
|
|
||||||
showTitle: true,
|
|
||||||
urlBarHidingEnabled: false,
|
|
||||||
),
|
|
||||||
safariVCOptions: const SafariViewControllerOptions(
|
|
||||||
barCollapsingEnabled: true,
|
|
||||||
dismissButtonStyle: SafariViewControllerDismissButtonStyle.close,
|
|
||||||
modalPresentationCapturesStatusBarAppearance: true,
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
List<Widget> content() {
|
||||||
title: Text(AppLocalizations.of(context)!.addCustomRule),
|
return [
|
||||||
actions: [
|
const SizedBox(height: 24),
|
||||||
IconButton(
|
Row(
|
||||||
onPressed: checkValidValues() == true
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
? () {
|
children: [
|
||||||
Navigator.pop(context);
|
Container(
|
||||||
widget.onConfirm(buildRule());
|
padding: const EdgeInsets.symmetric(
|
||||||
}
|
horizontal: 10,
|
||||||
: null,
|
vertical: 5
|
||||||
icon: const Icon(Icons.check)
|
),
|
||||||
),
|
decoration: BoxDecoration(
|
||||||
const SizedBox(width: 10)
|
color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
|
||||||
],
|
borderRadius: BorderRadius.circular(30),
|
||||||
),
|
border: Border.all(
|
||||||
body: ListView(
|
color: Theme.of(context).colorScheme.primary
|
||||||
children: [
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 10,
|
|
||||||
vertical: 5
|
|
||||||
),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
|
|
||||||
borderRadius: BorderRadius.circular(30),
|
|
||||||
border: Border.all(
|
|
||||||
color: Theme.of(context).colorScheme.primary
|
|
||||||
)
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
buildRule(),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
fontWeight: FontWeight.w500
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
],
|
child: Text(
|
||||||
),
|
buildRule(),
|
||||||
const SizedBox(height: 30),
|
textAlign: TextAlign.center,
|
||||||
Padding(
|
style: TextStyle(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
color: Theme.of(context).colorScheme.primary,
|
||||||
child: TextFormField(
|
fontWeight: FontWeight.w500
|
||||||
controller: domainController,
|
|
||||||
onChanged: (value) => setState(() => {}),
|
|
||||||
decoration: InputDecoration(
|
|
||||||
prefixIcon: const Icon(Icons.link_rounded),
|
|
||||||
border: const OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(
|
|
||||||
Radius.circular(10)
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
errorText: domainError,
|
)
|
||||||
labelText: AppLocalizations.of(context)!.domain,
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Container(height: 30),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
child: TextFormField(
|
||||||
|
controller: domainController,
|
||||||
|
onChanged: (value) => setState(() => {}),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
prefixIcon: const Icon(Icons.link_rounded),
|
||||||
|
border: const OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(10)
|
||||||
|
)
|
||||||
),
|
),
|
||||||
|
errorText: domainError,
|
||||||
|
labelText: AppLocalizations.of(context)!.domain,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 30),
|
),
|
||||||
Padding(
|
Container(height: 30),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
Padding(
|
||||||
child: SegmentedButton(
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
segments: [
|
child: SegmentedButton(
|
||||||
ButtonSegment(
|
segments: [
|
||||||
value: BlockingPresets.block,
|
ButtonSegment(
|
||||||
label: Text(AppLocalizations.of(context)!.block)
|
value: BlockingPresets.block,
|
||||||
),
|
label: Text(AppLocalizations.of(context)!.block)
|
||||||
ButtonSegment(
|
),
|
||||||
value: BlockingPresets.unblock,
|
ButtonSegment(
|
||||||
label: Text(AppLocalizations.of(context)!.unblock)
|
value: BlockingPresets.unblock,
|
||||||
),
|
label: Text(AppLocalizations.of(context)!.unblock)
|
||||||
ButtonSegment(
|
),
|
||||||
value: BlockingPresets.custom,
|
ButtonSegment(
|
||||||
label: Text(AppLocalizations.of(context)!.custom)
|
value: BlockingPresets.custom,
|
||||||
),
|
label: Text(AppLocalizations.of(context)!.custom)
|
||||||
],
|
),
|
||||||
selected: <BlockingPresets>{preset},
|
],
|
||||||
onSelectionChanged: (value) => setState(() => preset = value.first),
|
selected: <BlockingPresets>{preset},
|
||||||
),
|
onSelectionChanged: (value) => setState(() => preset = value.first),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
),
|
||||||
Material(
|
Container(height: 20),
|
||||||
color: Colors.transparent,
|
Material(
|
||||||
child: InkWell(
|
color: Colors.transparent,
|
||||||
onTap: () => setState(() => addImportant = !addImportant),
|
child: InkWell(
|
||||||
child: Padding(
|
onTap: () => setState(() => addImportant = !addImportant),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 28),
|
child: Padding(
|
||||||
child: Row(
|
padding: const EdgeInsets.symmetric(horizontal: 28),
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
child: Row(
|
||||||
children: [
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
Padding(
|
children: [
|
||||||
padding: const EdgeInsets.only(left: 10),
|
Padding(
|
||||||
child: Text(
|
padding: const EdgeInsets.only(left: 10),
|
||||||
AppLocalizations.of(context)!.addImportant,
|
child: Text(
|
||||||
style: TextStyle(
|
AppLocalizations.of(context)!.addImportant,
|
||||||
fontSize: 16,
|
style: TextStyle(
|
||||||
color: Theme.of(context).colorScheme.onSurface
|
fontSize: 16,
|
||||||
),
|
color: Theme.of(context).colorScheme.onSurface
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Switch(
|
),
|
||||||
value: addImportant,
|
Switch(
|
||||||
onChanged: (value) => setState(() => addImportant = value),
|
value: addImportant,
|
||||||
)
|
onChanged: (value) => setState(() => addImportant = value),
|
||||||
],
|
)
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
),
|
||||||
Padding(
|
Container(height: 20),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
Padding(
|
||||||
child: Card(
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
child: Padding(
|
child: Card(
|
||||||
padding: const EdgeInsets.all(20),
|
child: Padding(
|
||||||
child: Column(
|
padding: const EdgeInsets.all(20),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.info,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface
|
||||||
|
),
|
||||||
|
const SizedBox(width: 20),
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context)!.examples,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
SizedBox(
|
||||||
|
width: double.maxFinite,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"||example.org^",
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Theme.of(context).colorScheme.primary
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 5),
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context)!.example1,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Theme.of(context).colorScheme.primary
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
Text(
|
||||||
|
"@@||example.org^",
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Theme.of(context).colorScheme.primary
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 5),
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context)!.example2,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Theme.of(context).colorScheme.primary
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
Text(
|
||||||
|
"! Here goes a comment",
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Theme.of(context).colorScheme.primary
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"# Also a comment",
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Theme.of(context).colorScheme.primary
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 5),
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context)!.example3,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Theme.of(context).colorScheme.primary
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
Text(
|
||||||
|
"/REGEX/",
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Theme.of(context).colorScheme.primary
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 5),
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context)!.example4,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Theme.of(context).colorScheme.primary
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(height: 20),
|
||||||
|
Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () => openUrl(Urls.customRuleDocs),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 10),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 10),
|
||||||
|
child: Text(
|
||||||
|
AppLocalizations.of(context)!.moreInformation,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 15),
|
||||||
|
child: Icon(
|
||||||
|
Icons.open_in_new,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(height: 20)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (widget.dialog == true) {
|
||||||
|
return Dialog(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
maxWidth: 500
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
IconButton(
|
||||||
Icons.info,
|
onPressed: () => Navigator.pop(context),
|
||||||
color: Theme.of(context).colorScheme.onSurface
|
icon: const Icon(Icons.clear_rounded),
|
||||||
|
tooltip: AppLocalizations.of(context)!.close,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 20),
|
const SizedBox(width: 8),
|
||||||
Text(
|
Text(
|
||||||
AppLocalizations.of(context)!.examples,
|
AppLocalizations.of(context)!.addCustomRule,
|
||||||
style: TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 18,
|
fontSize: 22
|
||||||
color: Theme.of(context).colorScheme.onSurface
|
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
IconButton(
|
||||||
SizedBox(
|
onPressed: checkValidValues() == true
|
||||||
width: double.maxFinite,
|
? () {
|
||||||
child: Column(
|
Navigator.pop(context);
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
widget.onConfirm(buildRule());
|
||||||
children: [
|
}
|
||||||
Text(
|
: null,
|
||||||
"||example.org^",
|
icon: const Icon(Icons.check)
|
||||||
textAlign: TextAlign.left,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
color: Theme.of(context).colorScheme.primary
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 5),
|
|
||||||
Text(
|
|
||||||
AppLocalizations.of(context)!.example1,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
color: Theme.of(context).colorScheme.primary
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
Text(
|
|
||||||
"@@||example.org^",
|
|
||||||
textAlign: TextAlign.left,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
color: Theme.of(context).colorScheme.primary
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 5),
|
|
||||||
Text(
|
|
||||||
AppLocalizations.of(context)!.example2,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
color: Theme.of(context).colorScheme.primary
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
Text(
|
|
||||||
"! Here goes a comment",
|
|
||||||
textAlign: TextAlign.left,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
color: Theme.of(context).colorScheme.primary
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
"# Also a comment",
|
|
||||||
textAlign: TextAlign.left,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
color: Theme.of(context).colorScheme.primary
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 5),
|
|
||||||
Text(
|
|
||||||
AppLocalizations.of(context)!.example3,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
color: Theme.of(context).colorScheme.primary
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
Text(
|
|
||||||
"/REGEX/",
|
|
||||||
textAlign: TextAlign.left,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
color: Theme.of(context).colorScheme.primary
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 5),
|
|
||||||
Text(
|
|
||||||
AppLocalizations.of(context)!.example4,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
color: Theme.of(context).colorScheme.primary
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
Flexible(
|
||||||
),
|
child: SingleChildScrollView(
|
||||||
const SizedBox(height: 20),
|
child: Wrap(
|
||||||
Material(
|
alignment: WrapAlignment.center,
|
||||||
color: Colors.transparent,
|
children: content(),
|
||||||
child: InkWell(
|
),
|
||||||
onTap: openDocsPage,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 10),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 10),
|
|
||||||
child: Text(
|
|
||||||
AppLocalizations.of(context)!.moreInformation,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
color: Theme.of(context).colorScheme.onSurface
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(right: 15),
|
|
||||||
child: Icon(
|
|
||||||
Icons.open_in_new,
|
|
||||||
color: Theme.of(context).colorScheme.onSurface
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20)
|
),
|
||||||
],
|
);
|
||||||
),
|
}
|
||||||
);
|
else {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(AppLocalizations.of(context)!.addCustomRule),
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: checkValidValues() == true
|
||||||
|
? () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
widget.onConfirm(buildRule());
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
icon: const Icon(Icons.check)
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: ListView(
|
||||||
|
children: content(),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -10,6 +10,7 @@ class AddListModal extends StatefulWidget {
|
||||||
final Filter? list;
|
final Filter? list;
|
||||||
final void Function({required String name, required String url, required String type})? onConfirm;
|
final void Function({required String name, required String url, required String type})? onConfirm;
|
||||||
final void Function({required Filter list, required String type})? onEdit;
|
final void Function({required Filter list, required String type})? onEdit;
|
||||||
|
final bool dialog;
|
||||||
|
|
||||||
const AddListModal({
|
const AddListModal({
|
||||||
Key? key,
|
Key? key,
|
||||||
|
@ -17,6 +18,7 @@ class AddListModal extends StatefulWidget {
|
||||||
this.list,
|
this.list,
|
||||||
this.onConfirm,
|
this.onConfirm,
|
||||||
this.onEdit,
|
this.onEdit,
|
||||||
|
required this.dialog
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -68,51 +70,49 @@ class _AddListModalState extends State<AddListModal> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Padding(
|
Widget content() {
|
||||||
padding: MediaQuery.of(context).viewInsets,
|
return Column(
|
||||||
child: Container(
|
mainAxisSize: MainAxisSize.min,
|
||||||
height: Platform.isIOS ? 386 : 370,
|
children: [
|
||||||
decoration: BoxDecoration(
|
Flexible(
|
||||||
borderRadius: const BorderRadius.only(
|
child: SingleChildScrollView(
|
||||||
topLeft: Radius.circular(28),
|
child: Wrap(
|
||||||
topRight: Radius.circular(28)
|
|
||||||
),
|
|
||||||
color: Theme.of(context).dialogBackgroundColor
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: ListView(
|
|
||||||
physics: (Platform.isIOS ? 426 : 410) < MediaQuery.of(context).size.height
|
|
||||||
? const NeverScrollableScrollPhysics()
|
|
||||||
: null,
|
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Row(
|
||||||
padding: const EdgeInsets.only(top: 24),
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
child: Icon(
|
children: [
|
||||||
widget.type == 'whitelist'
|
Column(
|
||||||
? Icons.verified_user_rounded
|
children: [
|
||||||
: Icons.gpp_bad_rounded,
|
Padding(
|
||||||
size: 24,
|
padding: const EdgeInsets.only(top: 24),
|
||||||
color: Theme.of(context).listTileTheme.iconColor
|
child: Icon(
|
||||||
),
|
widget.type == 'whitelist'
|
||||||
|
? Icons.verified_user_rounded
|
||||||
|
: Icons.gpp_bad_rounded,
|
||||||
|
size: 24,
|
||||||
|
color: Theme.of(context).listTileTheme.iconColor
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
widget.list != null
|
||||||
|
? widget.type == 'whitelist'
|
||||||
|
? AppLocalizations.of(context)!.editWhitelist
|
||||||
|
: AppLocalizations.of(context)!.editBlacklist
|
||||||
|
: widget.type == 'whitelist'
|
||||||
|
? AppLocalizations.of(context)!.addWhitelist
|
||||||
|
: AppLocalizations.of(context)!.addBlacklist,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 24,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
|
||||||
Text(
|
|
||||||
widget.list != null
|
|
||||||
? widget.type == 'whitelist'
|
|
||||||
? AppLocalizations.of(context)!.editWhitelist
|
|
||||||
: AppLocalizations.of(context)!.editBlacklist
|
|
||||||
: widget.type == 'whitelist'
|
|
||||||
? AppLocalizations.of(context)!.addWhitelist
|
|
||||||
: AppLocalizations.of(context)!.addBlacklist,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 24,
|
|
||||||
color: Theme.of(context).colorScheme.onSurface
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
|
@ -129,7 +129,7 @@ class _AddListModalState extends State<AddListModal> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 30),
|
Container(height: 30),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
|
@ -151,54 +151,80 @@ class _AddListModalState extends State<AddListModal> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
),
|
||||||
padding: const EdgeInsets.all(24),
|
Padding(
|
||||||
child: Row(
|
padding: const EdgeInsets.all(24),
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
child: Row(
|
||||||
children: [
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
TextButton(
|
children: [
|
||||||
onPressed: () => Navigator.pop(context),
|
TextButton(
|
||||||
child: Text(AppLocalizations.of(context)!.cancel)
|
onPressed: () => Navigator.pop(context),
|
||||||
),
|
child: Text(AppLocalizations.of(context)!.cancel)
|
||||||
const SizedBox(width: 20),
|
),
|
||||||
TextButton(
|
const SizedBox(width: 20),
|
||||||
onPressed: () {
|
TextButton(
|
||||||
Navigator.pop(context);
|
onPressed: () {
|
||||||
if (widget.list != null) {
|
Navigator.pop(context);
|
||||||
final Filter newList = Filter(
|
if (widget.list != null) {
|
||||||
url: urlController.text,
|
final Filter newList = Filter(
|
||||||
name: nameController.text,
|
url: urlController.text,
|
||||||
lastUpdated: widget.list!.lastUpdated,
|
name: nameController.text,
|
||||||
id: widget.list!.id,
|
lastUpdated: widget.list!.lastUpdated,
|
||||||
rulesCount: widget.list!.rulesCount,
|
id: widget.list!.id,
|
||||||
enabled: widget.list!.enabled
|
rulesCount: widget.list!.rulesCount,
|
||||||
);
|
enabled: widget.list!.enabled
|
||||||
widget.onEdit!(
|
);
|
||||||
list: newList,
|
widget.onEdit!(
|
||||||
type: widget.type
|
list: newList,
|
||||||
);
|
type: widget.type
|
||||||
}
|
);
|
||||||
else {
|
}
|
||||||
widget.onConfirm!(
|
else {
|
||||||
name: nameController.text,
|
widget.onConfirm!(
|
||||||
url: urlController.text,
|
name: nameController.text,
|
||||||
type: widget.type
|
url: urlController.text,
|
||||||
);
|
type: widget.type
|
||||||
}
|
);
|
||||||
},
|
}
|
||||||
child: Text(
|
},
|
||||||
widget.list != null
|
child: Text(
|
||||||
? AppLocalizations.of(context)!.save
|
widget.list != null
|
||||||
: AppLocalizations.of(context)!.confirm
|
? AppLocalizations.of(context)!.save
|
||||||
)
|
: AppLocalizations.of(context)!.confirm
|
||||||
),
|
)
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
if (Platform.isIOS) const SizedBox(height: 16)
|
),
|
||||||
],
|
if (Platform.isIOS) const SizedBox(height: 16)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (widget.dialog == true) {
|
||||||
|
return Dialog(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
maxWidth: 400
|
||||||
|
),
|
||||||
|
child: content()
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
}
|
||||||
|
else {
|
||||||
|
return Padding(
|
||||||
|
padding: MediaQuery.of(context).viewInsets,
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: const BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(28),
|
||||||
|
topRight: Radius.circular(28)
|
||||||
|
),
|
||||||
|
color: Theme.of(context).dialogBackgroundColor
|
||||||
|
),
|
||||||
|
child: content()
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -12,7 +12,12 @@ 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';
|
||||||
|
|
||||||
class BlockedServicesScreen extends StatelessWidget {
|
class BlockedServicesScreen extends StatelessWidget {
|
||||||
const BlockedServicesScreen({Key? key}) : super(key: key);
|
final bool dialog;
|
||||||
|
|
||||||
|
const BlockedServicesScreen({
|
||||||
|
Key? key,
|
||||||
|
required this.dialog
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -21,7 +26,8 @@ class BlockedServicesScreen extends StatelessWidget {
|
||||||
|
|
||||||
return BlockedServicesScreenWidget(
|
return BlockedServicesScreenWidget(
|
||||||
serversProvider: serversProvider,
|
serversProvider: serversProvider,
|
||||||
appConfigProvider: appConfigProvider
|
appConfigProvider: appConfigProvider,
|
||||||
|
dialog: dialog,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,11 +35,13 @@ class BlockedServicesScreen extends StatelessWidget {
|
||||||
class BlockedServicesScreenWidget extends StatefulWidget {
|
class BlockedServicesScreenWidget extends StatefulWidget {
|
||||||
final ServersProvider serversProvider;
|
final ServersProvider serversProvider;
|
||||||
final AppConfigProvider appConfigProvider;
|
final AppConfigProvider appConfigProvider;
|
||||||
|
final bool dialog;
|
||||||
|
|
||||||
const BlockedServicesScreenWidget({
|
const BlockedServicesScreenWidget({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.serversProvider,
|
required this.serversProvider,
|
||||||
required this.appConfigProvider,
|
required this.appConfigProvider,
|
||||||
|
required this.dialog
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -209,24 +217,74 @@ class _BlockedServicesScreenStateWidget extends State<BlockedServicesScreenWidge
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Scaffold(
|
if (widget.dialog == true) {
|
||||||
appBar: AppBar(
|
return Dialog(
|
||||||
title: Text(AppLocalizations.of(context)!.blockedServices),
|
child: ConstrainedBox(
|
||||||
actions: [
|
constraints: const BoxConstraints(
|
||||||
IconButton(
|
maxWidth: 400
|
||||||
onPressed: updateBlockedServices,
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.save_rounded
|
|
||||||
),
|
|
||||||
tooltip: AppLocalizations.of(context)!.save,
|
|
||||||
),
|
),
|
||||||
const SizedBox(width: 10)
|
child: Column(
|
||||||
],
|
children: [
|
||||||
),
|
Padding(
|
||||||
body: RefreshIndicator(
|
padding: const EdgeInsets.all(16),
|
||||||
onRefresh: loadBlockedServices,
|
child: Row(
|
||||||
child: body()
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
),
|
children: [
|
||||||
);
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
icon: const Icon(Icons.clear_rounded),
|
||||||
|
tooltip: AppLocalizations.of(context)!.close,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context)!.blockedServices,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 22
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed: updateBlockedServices,
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.save_rounded
|
||||||
|
),
|
||||||
|
tooltip: AppLocalizations.of(context)!.save,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: body()
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(AppLocalizations.of(context)!.blockedServices),
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: updateBlockedServices,
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.save_rounded
|
||||||
|
),
|
||||||
|
tooltip: AppLocalizations.of(context)!.save,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: RefreshIndicator(
|
||||||
|
onRefresh: loadBlockedServices,
|
||||||
|
child: body()
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -10,7 +10,12 @@ import 'package:adguard_home_manager/services/http_requests.dart';
|
||||||
import 'package:adguard_home_manager/providers/servers_provider.dart';
|
import 'package:adguard_home_manager/providers/servers_provider.dart';
|
||||||
|
|
||||||
class CheckHostModal extends StatefulWidget {
|
class CheckHostModal extends StatefulWidget {
|
||||||
const CheckHostModal({Key? key}) : super(key: key);
|
final bool dialog;
|
||||||
|
|
||||||
|
const CheckHostModal({
|
||||||
|
Key? key,
|
||||||
|
required this.dialog
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<CheckHostModal> createState() => _CheckHostModalState();
|
State<CheckHostModal> createState() => _CheckHostModalState();
|
||||||
|
@ -117,126 +122,141 @@ class _CheckHostModalState extends State<CheckHostModal> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Padding(
|
Widget content() {
|
||||||
padding: MediaQuery.of(context).viewInsets,
|
return Column(
|
||||||
child: Container(
|
mainAxisSize: MainAxisSize.min,
|
||||||
height: 330,
|
children: [
|
||||||
width: double.maxFinite,
|
Flexible(
|
||||||
decoration: BoxDecoration(
|
child: SingleChildScrollView(
|
||||||
borderRadius: const BorderRadius.only(
|
child: Wrap(
|
||||||
topLeft: Radius.circular(28),
|
children: [
|
||||||
topRight: Radius.circular(28),
|
Row(
|
||||||
),
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
color: Theme.of(context).dialogBackgroundColor
|
children: [
|
||||||
),
|
Column(
|
||||||
child: Center(
|
children: [
|
||||||
child: Column(
|
Padding(
|
||||||
children: [
|
padding: const EdgeInsets.only(top: 24),
|
||||||
Expanded(
|
child: Icon(
|
||||||
child: ListView(
|
Icons.shield_rounded,
|
||||||
physics: 350 < MediaQuery.of(context).size.height
|
size: 24,
|
||||||
? const NeverScrollableScrollPhysics()
|
color: Theme.of(context).listTileTheme.iconColor
|
||||||
: null,
|
),
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 24),
|
|
||||||
child: Icon(
|
|
||||||
Icons.shield_rounded,
|
|
||||||
size: 24,
|
|
||||||
color: Theme.of(context).listTileTheme.iconColor
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Text(
|
|
||||||
AppLocalizations.of(context)!.checkHostFiltered,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 24,
|
|
||||||
color: Theme.of(context).colorScheme.onSurface
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
|
||||||
child: TextFormField(
|
|
||||||
controller: domainController,
|
|
||||||
onChanged: validateDomain,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
prefixIcon: const Icon(Icons.link_rounded),
|
|
||||||
border: const OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(
|
|
||||||
Radius.circular(10)
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
errorText: domainError,
|
const SizedBox(height: 16),
|
||||||
labelText: AppLocalizations.of(context)!.domain,
|
Text(
|
||||||
|
AppLocalizations.of(context)!.checkHostFiltered,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 24,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
child: TextFormField(
|
||||||
|
controller: domainController,
|
||||||
|
onChanged: validateDomain,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
prefixIcon: const Icon(Icons.link_rounded),
|
||||||
|
border: const OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(10)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
errorText: domainError,
|
||||||
|
labelText: AppLocalizations.of(context)!.domain,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (resultWidget != null) Padding(
|
||||||
|
padding: const EdgeInsets.all(24),
|
||||||
|
child: resultWidget,
|
||||||
|
),
|
||||||
|
if (resultWidget == null) Padding(
|
||||||
|
padding: const EdgeInsets.all(24),
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
AppLocalizations.of(context)!.insertDomain,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (resultWidget != null) Padding(
|
),
|
||||||
padding: const EdgeInsets.only(
|
],
|
||||||
top: 20,
|
),
|
||||||
left: 20,
|
),
|
||||||
right: 20
|
),
|
||||||
),
|
Column(
|
||||||
child: resultWidget,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
bottom: 24,
|
||||||
|
right: 24
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
child: Text(AppLocalizations.of(context)!.close),
|
||||||
),
|
),
|
||||||
if (resultWidget == null) Padding(
|
const SizedBox(width: 20),
|
||||||
padding: const EdgeInsets.only(
|
TextButton(
|
||||||
top: 20,
|
onPressed: domainController.text != '' && domainError == null
|
||||||
left: 20,
|
? () => checkHost()
|
||||||
right: 20
|
: null,
|
||||||
),
|
child: Text(
|
||||||
child: Center(
|
AppLocalizations.of(context)!.check,
|
||||||
child: Text(
|
style: TextStyle(
|
||||||
AppLocalizations.of(context)!.insertDomain,
|
color: domainController.text != '' && domainError == null
|
||||||
style: const TextStyle(
|
? Theme.of(context).colorScheme.primary
|
||||||
fontSize: 16,
|
: Colors.grey
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
|
||||||
Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(
|
|
||||||
bottom: 24,
|
|
||||||
right: 24
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.pop(context),
|
|
||||||
child: Text(AppLocalizations.of(context)!.close),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 20),
|
|
||||||
TextButton(
|
|
||||||
onPressed: domainController.text != '' && domainError == null
|
|
||||||
? () => checkHost()
|
|
||||||
: null,
|
|
||||||
child: Text(
|
|
||||||
AppLocalizations.of(context)!.check,
|
|
||||||
style: TextStyle(
|
|
||||||
color: domainController.text != '' && domainError == null
|
|
||||||
? Theme.of(context).colorScheme.primary
|
|
||||||
: Colors.grey
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (widget.dialog == true) {
|
||||||
|
return Dialog(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
maxWidth: 400
|
||||||
),
|
),
|
||||||
|
child: content()
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
}
|
||||||
|
else {
|
||||||
|
return Padding(
|
||||||
|
padding: MediaQuery.of(context).viewInsets,
|
||||||
|
child: Container(
|
||||||
|
width: double.maxFinite,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: const BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(28),
|
||||||
|
topRight: Radius.circular(28),
|
||||||
|
),
|
||||||
|
color: Theme.of(context).dialogBackgroundColor
|
||||||
|
),
|
||||||
|
child: content()
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -2,33 +2,27 @@
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.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/filters/fab.dart';
|
import 'package:adguard_home_manager/screens/filters/add_button.dart';
|
||||||
import 'package:adguard_home_manager/screens/filters/remove_custom_rule_modal.dart';
|
|
||||||
import 'package:adguard_home_manager/widgets/tab_content_list.dart';
|
import 'package:adguard_home_manager/widgets/tab_content_list.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/models/filtering.dart';
|
|
||||||
import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
|
||||||
import 'package:adguard_home_manager/services/http_requests.dart';
|
|
||||||
import 'package:adguard_home_manager/providers/servers_provider.dart';
|
|
||||||
import 'package:adguard_home_manager/classes/process_modal.dart';
|
|
||||||
|
|
||||||
class CustomRulesList extends StatefulWidget {
|
class CustomRulesList extends StatefulWidget {
|
||||||
final LoadStatus loadStatus;
|
final LoadStatus loadStatus;
|
||||||
final ScrollController scrollController;
|
final ScrollController scrollController;
|
||||||
final List<String> data;
|
final List<String> data;
|
||||||
final Future<void> Function() fetchData;
|
final Future<void> Function() fetchData;
|
||||||
|
final void Function(String) onRemoveCustomRule;
|
||||||
|
|
||||||
const CustomRulesList({
|
const CustomRulesList({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.loadStatus,
|
required this.loadStatus,
|
||||||
required this.scrollController,
|
required this.scrollController,
|
||||||
required this.data,
|
required this.data,
|
||||||
required this.fetchData
|
required this.fetchData,
|
||||||
|
required this.onRemoveCustomRule
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -61,52 +55,6 @@ class _CustomRulesListState extends State<CustomRulesList> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final serversProvider = Provider.of<ServersProvider>(context);
|
|
||||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
|
||||||
|
|
||||||
void removeCustomRule(String rule) async {
|
|
||||||
ProcessModal processModal = ProcessModal(context: context);
|
|
||||||
processModal.open(AppLocalizations.of(context)!.deletingRule);
|
|
||||||
|
|
||||||
final List<String> newRules = serversProvider.filtering.data!.userRules.where((r) => r != rule).toList();
|
|
||||||
|
|
||||||
final result = await setCustomRules(server: serversProvider.selectedServer!, rules: newRules);
|
|
||||||
|
|
||||||
processModal.close();
|
|
||||||
|
|
||||||
if (result['result'] == 'success') {
|
|
||||||
FilteringData filteringData = serversProvider.filtering.data!;
|
|
||||||
filteringData.userRules = newRules;
|
|
||||||
serversProvider.setFilteringData(filteringData);
|
|
||||||
|
|
||||||
showSnacbkar(
|
|
||||||
context: context,
|
|
||||||
appConfigProvider: appConfigProvider,
|
|
||||||
label: AppLocalizations.of(context)!.ruleRemovedSuccessfully,
|
|
||||||
color: Colors.green
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
appConfigProvider.addLog(result['log']);
|
|
||||||
|
|
||||||
showSnacbkar(
|
|
||||||
context: context,
|
|
||||||
appConfigProvider: appConfigProvider,
|
|
||||||
label: AppLocalizations.of(context)!.ruleNotRemoved,
|
|
||||||
color: Colors.red
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void openRemoveCustomRuleModal(String rule) {
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => RemoveCustomRule(
|
|
||||||
onConfirm: () => removeCustomRule(rule),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool checkIfComment(String value) {
|
bool checkIfComment(String value) {
|
||||||
final regex = RegExp(r'^(!|#).*$');
|
final regex = RegExp(r'^(!|#).*$');
|
||||||
if (regex.hasMatch(value)) {
|
if (regex.hasMatch(value)) {
|
||||||
|
@ -184,7 +132,7 @@ class _CustomRulesListState extends State<CustomRulesList> {
|
||||||
),
|
),
|
||||||
subtitle: generateSubtitle(widget.data[index]),
|
subtitle: generateSubtitle(widget.data[index]),
|
||||||
trailing: IconButton(
|
trailing: IconButton(
|
||||||
onPressed: () => openRemoveCustomRuleModal(widget.data[index]),
|
onPressed: () => widget.onRemoveCustomRule(widget.data[index]),
|
||||||
icon: const Icon(Icons.delete)
|
icon: const Icon(Icons.delete)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -239,8 +187,12 @@ class _CustomRulesListState extends State<CustomRulesList> {
|
||||||
),
|
),
|
||||||
loadStatus: widget.loadStatus,
|
loadStatus: widget.loadStatus,
|
||||||
onRefresh: widget.fetchData,
|
onRefresh: widget.fetchData,
|
||||||
fab: const FiltersFab(
|
fab: AddFiltersButton(
|
||||||
type: 'custom_rule',
|
type: 'custom_rule',
|
||||||
|
widget: (fn) => FloatingActionButton(
|
||||||
|
onPressed: fn,
|
||||||
|
child: const Icon(Icons.add),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
fabVisible: isVisible,
|
fabVisible: isVisible,
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,60 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class FilterListTile extends StatelessWidget {
|
|
||||||
final IconData icon;
|
|
||||||
final String title;
|
|
||||||
final String subtitle;
|
|
||||||
final Color? color;
|
|
||||||
final bool? bold;
|
|
||||||
|
|
||||||
const FilterListTile({
|
|
||||||
Key? key,
|
|
||||||
required this.icon,
|
|
||||||
required this.title,
|
|
||||||
required this.subtitle,
|
|
||||||
this.color,
|
|
||||||
this.bold,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
icon,
|
|
||||||
size: 24,
|
|
||||||
color: Theme.of(context).listTileTheme.iconColor,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 16),
|
|
||||||
Flexible(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
title,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
color: Theme.of(context).colorScheme.onSurface
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 3),
|
|
||||||
Text(
|
|
||||||
subtitle,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
color: color ?? Theme.of(context).listTileTheme.textColor,
|
|
||||||
fontWeight: bold == true ? FontWeight.bold : FontWeight.w400
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +1,23 @@
|
||||||
// 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: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';
|
||||||
|
|
||||||
import 'package:adguard_home_manager/screens/filters/filters_list.dart';
|
|
||||||
import 'package:adguard_home_manager/screens/filters/check_host_modal.dart';
|
import 'package:adguard_home_manager/screens/filters/check_host_modal.dart';
|
||||||
import 'package:adguard_home_manager/screens/filters/custom_rules_list.dart';
|
import 'package:adguard_home_manager/screens/filters/filters_tabs_view.dart';
|
||||||
|
import 'package:adguard_home_manager/screens/filters/filters_triple_column.dart';
|
||||||
|
import 'package:adguard_home_manager/screens/filters/list_details_screen.dart';
|
||||||
|
import 'package:adguard_home_manager/screens/filters/remove_custom_rule_modal.dart';
|
||||||
import 'package:adguard_home_manager/screens/filters/blocked_services_screen.dart';
|
import 'package:adguard_home_manager/screens/filters/blocked_services_screen.dart';
|
||||||
import 'package:adguard_home_manager/screens/filters/update_interval_lists_modal.dart';
|
import 'package:adguard_home_manager/screens/filters/update_interval_lists_modal.dart';
|
||||||
|
|
||||||
import 'package:adguard_home_manager/functions/snackbar.dart';
|
import 'package:adguard_home_manager/functions/snackbar.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/app_config_provider.dart';
|
import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
||||||
|
import 'package:adguard_home_manager/models/filtering.dart';
|
||||||
import 'package:adguard_home_manager/constants/enums.dart';
|
import 'package:adguard_home_manager/constants/enums.dart';
|
||||||
import 'package:adguard_home_manager/services/http_requests.dart';
|
import 'package:adguard_home_manager/services/http_requests.dart';
|
||||||
import 'package:adguard_home_manager/models/clients.dart';
|
import 'package:adguard_home_manager/models/clients.dart';
|
||||||
|
@ -47,10 +52,7 @@ class FiltersWidget extends StatefulWidget {
|
||||||
State<FiltersWidget> createState() => _FiltersWidgetState();
|
State<FiltersWidget> createState() => _FiltersWidgetState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FiltersWidgetState extends State<FiltersWidget> with TickerProviderStateMixin {
|
class _FiltersWidgetState extends State<FiltersWidget> {
|
||||||
late TabController tabController;
|
|
||||||
final ScrollController scrollController = ScrollController();
|
|
||||||
|
|
||||||
Future fetchFilters() async {
|
Future fetchFilters() async {
|
||||||
widget.serversProvider.setFilteringLoadStatus(LoadStatus.loading, false);
|
widget.serversProvider.setFilteringLoadStatus(LoadStatus.loading, false);
|
||||||
|
|
||||||
|
@ -68,20 +70,14 @@ class _FiltersWidgetState extends State<FiltersWidget> with TickerProviderStateM
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<AutoClient> generateClientsList(List<AutoClient> clients, List<String> ips) {
|
||||||
|
return clients.where((client) => ips.contains(client.ip)).toList();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
fetchFilters();
|
fetchFilters();
|
||||||
super.initState();
|
super.initState();
|
||||||
tabController = TabController(
|
|
||||||
initialIndex: 0,
|
|
||||||
length: 3,
|
|
||||||
vsync: this,
|
|
||||||
);
|
|
||||||
tabController.addListener(() => widget.appConfigProvider.setSelectedFiltersTab(tabController.index));
|
|
||||||
}
|
|
||||||
|
|
||||||
List<AutoClient> generateClientsList(List<AutoClient> clients, List<String> ips) {
|
|
||||||
return clients.where((client) => ips.contains(client.ip)).toList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -89,6 +85,8 @@ class _FiltersWidgetState extends State<FiltersWidget> with TickerProviderStateM
|
||||||
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 width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
void fetchUpdateLists() async {
|
void fetchUpdateLists() async {
|
||||||
ProcessModal processModal = ProcessModal(context: context);
|
ProcessModal processModal = ProcessModal(context: context);
|
||||||
processModal.open(AppLocalizations.of(context)!.updatingLists);
|
processModal.open(AppLocalizations.of(context)!.updatingLists);
|
||||||
|
@ -139,12 +137,24 @@ class _FiltersWidgetState extends State<FiltersWidget> with TickerProviderStateM
|
||||||
|
|
||||||
void showCheckHostModal() {
|
void showCheckHostModal() {
|
||||||
Future.delayed(const Duration(seconds: 0), () {
|
Future.delayed(const Duration(seconds: 0), () {
|
||||||
showModalBottomSheet(
|
if (width > 700 || !(Platform.isAndroid || Platform.isIOS)) {
|
||||||
context: context,
|
showDialog(
|
||||||
builder: (context) => const CheckHostModal(),
|
context: context,
|
||||||
backgroundColor: Colors.transparent,
|
builder: (context) => const CheckHostModal(
|
||||||
isScrollControlled: true,
|
dialog: true,
|
||||||
);
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => const CheckHostModal(
|
||||||
|
dialog: false,
|
||||||
|
),
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
isScrollControlled: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,169 +226,222 @@ class _FiltersWidgetState extends State<FiltersWidget> with TickerProviderStateM
|
||||||
|
|
||||||
void openBlockedServicesModal() {
|
void openBlockedServicesModal() {
|
||||||
Future.delayed(const Duration(seconds: 0), () {
|
Future.delayed(const Duration(seconds: 0), () {
|
||||||
Navigator.of(context).push(
|
if (width > 700 || !(Platform.isAndroid || Platform.isIOS)) {
|
||||||
MaterialPageRoute(
|
showDialog(
|
||||||
builder: (context) => const BlockedServicesScreen(),
|
context: context,
|
||||||
)
|
builder: (context) => const BlockedServicesScreen(
|
||||||
);
|
dialog: true,
|
||||||
|
),
|
||||||
|
barrierDismissible: false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Navigator.of(context).push(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => const BlockedServicesScreen(
|
||||||
|
dialog: false,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return DefaultTabController(
|
void removeCustomRule(String rule) async {
|
||||||
length: 3,
|
ProcessModal processModal = ProcessModal(context: context);
|
||||||
child: NestedScrollView(
|
processModal.open(AppLocalizations.of(context)!.deletingRule);
|
||||||
controller: scrollController,
|
|
||||||
headerSliverBuilder: ((context, innerBoxIsScrolled) {
|
final List<String> newRules = serversProvider.filtering.data!.userRules.where((r) => r != rule).toList();
|
||||||
return [
|
|
||||||
SliverOverlapAbsorber(
|
final result = await setCustomRules(server: serversProvider.selectedServer!, rules: newRules);
|
||||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
|
||||||
sliver: SliverAppBar(
|
processModal.close();
|
||||||
title: Text(AppLocalizations.of(context)!.filters),
|
|
||||||
pinned: true,
|
if (result['result'] == 'success') {
|
||||||
floating: true,
|
FilteringData filteringData = serversProvider.filtering.data!;
|
||||||
forceElevated: innerBoxIsScrolled,
|
filteringData.userRules = newRules;
|
||||||
centerTitle: false,
|
serversProvider.setFilteringData(filteringData);
|
||||||
actions: serversProvider.filtering.loadStatus == LoadStatus.loaded ? [
|
|
||||||
IconButton(
|
showSnacbkar(
|
||||||
onPressed: enableDisableFiltering,
|
context: context,
|
||||||
tooltip: serversProvider.filtering.data!.enabled == true
|
appConfigProvider: appConfigProvider,
|
||||||
? AppLocalizations.of(context)!.disableFiltering
|
label: AppLocalizations.of(context)!.ruleRemovedSuccessfully,
|
||||||
: AppLocalizations.of(context)!.enableFiltering,
|
color: Colors.green
|
||||||
icon: Stack(
|
);
|
||||||
children: [
|
}
|
||||||
const Icon(Icons.power_settings_new_rounded),
|
else {
|
||||||
Positioned(
|
appConfigProvider.addLog(result['log']);
|
||||||
bottom: 0,
|
|
||||||
right: 0,
|
showSnacbkar(
|
||||||
child: Stack(
|
context: context,
|
||||||
children: [
|
appConfigProvider: appConfigProvider,
|
||||||
Container(
|
label: AppLocalizations.of(context)!.ruleNotRemoved,
|
||||||
decoration: BoxDecoration(
|
color: Colors.red
|
||||||
borderRadius: BorderRadius.circular(30),
|
);
|
||||||
color: Colors.white
|
}
|
||||||
),
|
}
|
||||||
child: Icon(
|
|
||||||
serversProvider.filtering.data!.enabled == true
|
void openRemoveCustomRuleModal(String rule) {
|
||||||
? Icons.check_circle_rounded
|
showDialog(
|
||||||
: Icons.cancel,
|
context: context,
|
||||||
size: 12,
|
builder: (context) => RemoveCustomRule(
|
||||||
color: serversProvider.filtering.data!.enabled == true
|
onConfirm: () => removeCustomRule(rule),
|
||||||
? appConfigProvider.useThemeColorForStatus == true
|
)
|
||||||
? Theme.of(context).colorScheme.primary
|
);
|
||||||
: Colors.green
|
}
|
||||||
: appConfigProvider.useThemeColorForStatus == true
|
|
||||||
? Colors.grey
|
void openListDetails(Filter filter, String type) {
|
||||||
: Colors.red
|
if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) {
|
||||||
),
|
showDialog(
|
||||||
),
|
context: context,
|
||||||
],
|
builder: (context) => ListDetailsScreen(
|
||||||
),
|
list: filter,
|
||||||
)
|
type: type,
|
||||||
],
|
dialog: true,
|
||||||
)
|
),
|
||||||
),
|
barrierDismissible: false
|
||||||
IconButton(
|
);
|
||||||
onPressed: () {
|
}
|
||||||
showModalBottomSheet(
|
else {
|
||||||
context: context,
|
Navigator.of(context).push(
|
||||||
builder: (context) => UpdateIntervalListsModal(
|
MaterialPageRoute(
|
||||||
interval: serversProvider.filtering.data!.interval,
|
builder: (context) => ListDetailsScreen(
|
||||||
onChange: setUpdateFrequency
|
list: filter,
|
||||||
|
type: type,
|
||||||
|
dialog: false,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> actions() {
|
||||||
|
if (serversProvider.filtering.loadStatus == LoadStatus.loaded) {
|
||||||
|
return [
|
||||||
|
IconButton(
|
||||||
|
onPressed: enableDisableFiltering,
|
||||||
|
tooltip: serversProvider.filtering.data!.enabled == true
|
||||||
|
? AppLocalizations.of(context)!.disableFiltering
|
||||||
|
: AppLocalizations.of(context)!.enableFiltering,
|
||||||
|
icon: Stack(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.power_settings_new_rounded),
|
||||||
|
Positioned(
|
||||||
|
bottom: 0,
|
||||||
|
right: 0,
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(30),
|
||||||
|
color: Colors.white
|
||||||
),
|
),
|
||||||
backgroundColor: Colors.transparent,
|
child: Icon(
|
||||||
isScrollControlled: true
|
serversProvider.filtering.data!.enabled == true
|
||||||
);
|
? Icons.check_circle_rounded
|
||||||
},
|
: Icons.cancel,
|
||||||
icon: const Icon(Icons.update_rounded)
|
size: 12,
|
||||||
|
color: serversProvider.filtering.data!.enabled == true
|
||||||
|
? appConfigProvider.useThemeColorForStatus == true
|
||||||
|
? Theme.of(context).colorScheme.primary
|
||||||
|
: Colors.green
|
||||||
|
: appConfigProvider.useThemeColorForStatus == true
|
||||||
|
? Colors.grey
|
||||||
|
: Colors.red
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
PopupMenuButton(
|
)
|
||||||
itemBuilder: (context) => [
|
],
|
||||||
PopupMenuItem(
|
)
|
||||||
onTap: fetchUpdateLists,
|
),
|
||||||
child: Row(
|
IconButton(
|
||||||
children: [
|
onPressed: () {
|
||||||
const Icon(Icons.sync_rounded),
|
if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) {
|
||||||
const SizedBox(width: 10),
|
showDialog(
|
||||||
Text(AppLocalizations.of(context)!.updateLists)
|
context: context,
|
||||||
],
|
builder: (context) => UpdateIntervalListsModal(
|
||||||
)
|
interval: serversProvider.filtering.data!.interval,
|
||||||
),
|
onChange: setUpdateFrequency,
|
||||||
PopupMenuItem(
|
dialog: true,
|
||||||
onTap: openBlockedServicesModal,
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
const Icon(Icons.block),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
Text(AppLocalizations.of(context)!.blockedServices)
|
|
||||||
],
|
|
||||||
)
|
|
||||||
),
|
|
||||||
PopupMenuItem(
|
|
||||||
onTap: showCheckHostModal,
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
const Icon(Icons.shield_rounded),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
Text(AppLocalizations.of(context)!.checkHostFiltered)
|
|
||||||
],
|
|
||||||
)
|
|
||||||
),
|
|
||||||
]
|
|
||||||
),
|
),
|
||||||
const SizedBox(width: 5),
|
);
|
||||||
] : [],
|
}
|
||||||
bottom: TabBar(
|
else {
|
||||||
controller: tabController,
|
showModalBottomSheet(
|
||||||
isScrollable: false,
|
context: context,
|
||||||
unselectedLabelColor: Theme.of(context).colorScheme.onSurfaceVariant,
|
builder: (context) => UpdateIntervalListsModal(
|
||||||
tabs: [
|
interval: serversProvider.filtering.data!.interval,
|
||||||
Tab(
|
onChange: setUpdateFrequency,
|
||||||
icon: const Icon(Icons.verified_user_rounded),
|
dialog: false,
|
||||||
text: AppLocalizations.of(context)!.whitelists,
|
),
|
||||||
),
|
backgroundColor: Colors.transparent,
|
||||||
Tab(
|
isScrollControlled: true
|
||||||
icon: const Icon(Icons.gpp_bad_rounded),
|
);
|
||||||
text: AppLocalizations.of(context)!.blacklist,
|
}
|
||||||
),
|
},
|
||||||
Tab(
|
icon: const Icon(Icons.update_rounded),
|
||||||
icon: const Icon(Icons.shield_rounded),
|
tooltip: AppLocalizations.of(context)!.updateFrequency,
|
||||||
text: AppLocalizations.of(context)!.customRules,
|
),
|
||||||
),
|
PopupMenuButton(
|
||||||
]
|
itemBuilder: (context) => [
|
||||||
|
PopupMenuItem(
|
||||||
|
onTap: fetchUpdateLists,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.sync_rounded),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Text(AppLocalizations.of(context)!.updateLists)
|
||||||
|
],
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
)
|
PopupMenuItem(
|
||||||
];
|
onTap: openBlockedServicesModal,
|
||||||
}),
|
child: Row(
|
||||||
body: TabBarView(
|
children: [
|
||||||
controller: tabController,
|
const Icon(Icons.block),
|
||||||
children: [
|
const SizedBox(width: 10),
|
||||||
FiltersList(
|
Text(AppLocalizations.of(context)!.blockedServices)
|
||||||
loadStatus: serversProvider.filtering.loadStatus,
|
],
|
||||||
scrollController: scrollController,
|
)
|
||||||
type: 'whitelist',
|
),
|
||||||
data: serversProvider.filtering.loadStatus == LoadStatus.loaded
|
PopupMenuItem(
|
||||||
? serversProvider.filtering.data!.whitelistFilters : [],
|
onTap: showCheckHostModal,
|
||||||
fetchData: fetchFilters,
|
child: Row(
|
||||||
),
|
children: [
|
||||||
FiltersList(
|
const Icon(Icons.shield_rounded),
|
||||||
loadStatus: serversProvider.filtering.loadStatus,
|
const SizedBox(width: 10),
|
||||||
scrollController: scrollController,
|
Text(AppLocalizations.of(context)!.checkHostFiltered)
|
||||||
type: 'blacklist',
|
],
|
||||||
data: serversProvider.filtering.loadStatus == LoadStatus.loaded
|
)
|
||||||
? serversProvider.filtering.data!.filters : [],
|
),
|
||||||
fetchData: fetchFilters,
|
]
|
||||||
),
|
),
|
||||||
CustomRulesList(
|
const SizedBox(width: 5),
|
||||||
loadStatus: serversProvider.filtering.loadStatus,
|
];
|
||||||
scrollController: scrollController,
|
}
|
||||||
data: serversProvider.filtering.loadStatus == LoadStatus.loaded
|
else {
|
||||||
? serversProvider.filtering.data!.userRules : [],
|
return [];
|
||||||
fetchData: fetchFilters,
|
}
|
||||||
),
|
}
|
||||||
]
|
|
||||||
)
|
if (width > 1200) {
|
||||||
)
|
return FiltersTripleColumn(
|
||||||
);
|
onRemoveCustomRule: openRemoveCustomRuleModal,
|
||||||
|
onOpenDetailsModal: openListDetails,
|
||||||
|
actions: actions(),
|
||||||
|
refreshData: fetchFilters,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return FiltersTabsView(
|
||||||
|
appConfigProvider: appConfigProvider,
|
||||||
|
fetchFilters: fetchFilters,
|
||||||
|
actions: actions(),
|
||||||
|
onRemoveCustomRule: openRemoveCustomRuleModal,
|
||||||
|
onOpenDetailsModal: openListDetails,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -7,8 +7,7 @@ import 'package:provider/provider.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.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/filters/fab.dart';
|
import 'package:adguard_home_manager/screens/filters/add_button.dart';
|
||||||
import 'package:adguard_home_manager/screens/filters/list_details_screen.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/widgets/tab_content_list.dart';
|
import 'package:adguard_home_manager/widgets/tab_content_list.dart';
|
||||||
|
|
||||||
|
@ -23,6 +22,7 @@ class FiltersList extends StatefulWidget {
|
||||||
final List<Filter> data;
|
final List<Filter> data;
|
||||||
final Future<void> Function() fetchData;
|
final Future<void> Function() fetchData;
|
||||||
final String type;
|
final String type;
|
||||||
|
final void Function(Filter, String) onOpenDetailsScreen;
|
||||||
|
|
||||||
const FiltersList({
|
const FiltersList({
|
||||||
Key? key,
|
Key? key,
|
||||||
|
@ -31,6 +31,7 @@ class FiltersList extends StatefulWidget {
|
||||||
required this.data,
|
required this.data,
|
||||||
required this.fetchData,
|
required this.fetchData,
|
||||||
required this.type,
|
required this.type,
|
||||||
|
required this.onOpenDetailsScreen
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -64,17 +65,6 @@ class _FiltersListState extends State<FiltersList> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||||
|
|
||||||
void openDetailsModal(Filter filter) {
|
|
||||||
Navigator.of(context).push(
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => ListDetailsScreen(
|
|
||||||
list: filter,
|
|
||||||
type: widget.type,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return CustomTabContentList(
|
return CustomTabContentList(
|
||||||
loadingGenerator: () => SizedBox(
|
loadingGenerator: () => SizedBox(
|
||||||
|
@ -112,7 +102,7 @@ class _FiltersListState extends State<FiltersList> {
|
||||||
? Colors.grey
|
? Colors.grey
|
||||||
: Colors.red
|
: Colors.red
|
||||||
),
|
),
|
||||||
onTap: () => openDetailsModal(widget.data[index]),
|
onTap: () => widget.onOpenDetailsScreen(widget.data[index], widget.type),
|
||||||
),
|
),
|
||||||
noData: Container(
|
noData: Container(
|
||||||
width: double.maxFinite,
|
width: double.maxFinite,
|
||||||
|
@ -166,8 +156,12 @@ class _FiltersListState extends State<FiltersList> {
|
||||||
),
|
),
|
||||||
loadStatus: widget.loadStatus,
|
loadStatus: widget.loadStatus,
|
||||||
onRefresh: widget.fetchData,
|
onRefresh: widget.fetchData,
|
||||||
fab: FiltersFab(
|
fab: AddFiltersButton(
|
||||||
type: widget.type,
|
type: widget.type,
|
||||||
|
widget: (fn) => FloatingActionButton(
|
||||||
|
onPressed: fn,
|
||||||
|
child: const Icon(Icons.add),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
fabVisible: isVisible,
|
fabVisible: isVisible,
|
||||||
);
|
);
|
||||||
|
|
143
lib/screens/filters/filters_tabs_view.dart
Normal file
143
lib/screens/filters/filters_tabs_view.dart
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
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/filters/custom_rules_list.dart';
|
||||||
|
import 'package:adguard_home_manager/screens/filters/filters_list.dart';
|
||||||
|
|
||||||
|
import 'package:adguard_home_manager/constants/enums.dart';
|
||||||
|
import 'package:adguard_home_manager/models/filtering.dart';
|
||||||
|
import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
||||||
|
import 'package:adguard_home_manager/providers/servers_provider.dart';
|
||||||
|
|
||||||
|
class FiltersTabsView extends StatefulWidget {
|
||||||
|
final AppConfigProvider appConfigProvider;
|
||||||
|
final Future Function() fetchFilters;
|
||||||
|
final List<Widget> actions;
|
||||||
|
final void Function(String) onRemoveCustomRule;
|
||||||
|
final void Function(Filter, String) onOpenDetailsModal;
|
||||||
|
|
||||||
|
const FiltersTabsView({
|
||||||
|
Key? key,
|
||||||
|
required this.appConfigProvider,
|
||||||
|
required this.fetchFilters,
|
||||||
|
required this.actions,
|
||||||
|
required this.onOpenDetailsModal,
|
||||||
|
required this.onRemoveCustomRule
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<FiltersTabsView> createState() => _FiltersTabsViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FiltersTabsViewState extends State<FiltersTabsView> with TickerProviderStateMixin {
|
||||||
|
late TabController tabController;
|
||||||
|
final ScrollController scrollController = ScrollController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
widget.fetchFilters();
|
||||||
|
super.initState();
|
||||||
|
tabController = TabController(
|
||||||
|
initialIndex: 0,
|
||||||
|
length: 3,
|
||||||
|
vsync: this,
|
||||||
|
);
|
||||||
|
tabController.addListener(() => widget.appConfigProvider.setSelectedFiltersTab(tabController.index));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final serversProvider = Provider.of<ServersProvider>(context);
|
||||||
|
|
||||||
|
return DefaultTabController(
|
||||||
|
length: 3,
|
||||||
|
child: NestedScrollView(
|
||||||
|
controller: scrollController,
|
||||||
|
headerSliverBuilder: ((context, innerBoxIsScrolled) {
|
||||||
|
return [
|
||||||
|
SliverOverlapAbsorber(
|
||||||
|
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||||
|
sliver: SliverAppBar(
|
||||||
|
title: Text(AppLocalizations.of(context)!.filters),
|
||||||
|
pinned: true,
|
||||||
|
floating: true,
|
||||||
|
forceElevated: innerBoxIsScrolled,
|
||||||
|
centerTitle: false,
|
||||||
|
actions: widget.actions,
|
||||||
|
bottom: TabBar(
|
||||||
|
controller: tabController,
|
||||||
|
isScrollable: true,
|
||||||
|
unselectedLabelColor: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
tabs: [
|
||||||
|
Tab(
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.verified_user_rounded),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(AppLocalizations.of(context)!.whitelists,)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Tab(
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.gpp_bad_rounded),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(AppLocalizations.of(context)!.blacklists)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Tab(
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.shield_rounded),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(AppLocalizations.of(context)!.customRules)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
];
|
||||||
|
}),
|
||||||
|
body: TabBarView(
|
||||||
|
controller: tabController,
|
||||||
|
children: [
|
||||||
|
FiltersList(
|
||||||
|
loadStatus: serversProvider.filtering.loadStatus,
|
||||||
|
scrollController: scrollController,
|
||||||
|
type: 'whitelist',
|
||||||
|
data: serversProvider.filtering.loadStatus == LoadStatus.loaded
|
||||||
|
? serversProvider.filtering.data!.whitelistFilters : [],
|
||||||
|
fetchData: widget.fetchFilters,
|
||||||
|
onOpenDetailsScreen: widget.onOpenDetailsModal,
|
||||||
|
),
|
||||||
|
FiltersList(
|
||||||
|
loadStatus: serversProvider.filtering.loadStatus,
|
||||||
|
scrollController: scrollController,
|
||||||
|
type: 'blacklist',
|
||||||
|
data: serversProvider.filtering.loadStatus == LoadStatus.loaded
|
||||||
|
? serversProvider.filtering.data!.filters : [],
|
||||||
|
fetchData: widget.fetchFilters,
|
||||||
|
onOpenDetailsScreen: widget.onOpenDetailsModal,
|
||||||
|
),
|
||||||
|
CustomRulesList(
|
||||||
|
loadStatus: serversProvider.filtering.loadStatus,
|
||||||
|
scrollController: scrollController,
|
||||||
|
data: serversProvider.filtering.loadStatus == LoadStatus.loaded
|
||||||
|
? serversProvider.filtering.data!.userRules : [],
|
||||||
|
fetchData: widget.fetchFilters,
|
||||||
|
onRemoveCustomRule: widget.onRemoveCustomRule,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
311
lib/screens/filters/filters_triple_column.dart
Normal file
311
lib/screens/filters/filters_triple_column.dart
Normal file
|
@ -0,0 +1,311 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:adguard_home_manager/screens/filters/add_button.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'package:adguard_home_manager/widgets/custom_list_tile.dart';
|
||||||
|
|
||||||
|
import 'package:adguard_home_manager/constants/enums.dart';
|
||||||
|
import 'package:adguard_home_manager/models/filtering.dart';
|
||||||
|
import 'package:adguard_home_manager/functions/number_format.dart';
|
||||||
|
import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
||||||
|
import 'package:adguard_home_manager/providers/servers_provider.dart';
|
||||||
|
|
||||||
|
class FiltersTripleColumn extends StatelessWidget {
|
||||||
|
final void Function(String) onRemoveCustomRule;
|
||||||
|
final void Function(Filter, String) onOpenDetailsModal;
|
||||||
|
final List<Widget> actions;
|
||||||
|
final Future Function() refreshData;
|
||||||
|
|
||||||
|
const FiltersTripleColumn({
|
||||||
|
Key? key,
|
||||||
|
required this.onRemoveCustomRule,
|
||||||
|
required this.onOpenDetailsModal,
|
||||||
|
required this.actions,
|
||||||
|
required this.refreshData
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final serversProvider = Provider.of<ServersProvider>(context);
|
||||||
|
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||||
|
|
||||||
|
bool checkIfComment(String value) {
|
||||||
|
final regex = RegExp(r'^(!|#).*$');
|
||||||
|
if (regex.hasMatch(value)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget? generateSubtitle(String rule) {
|
||||||
|
final allowRegex = RegExp(r'^@@.*$');
|
||||||
|
final blockRegex = RegExp(r'^\|\|.*$');
|
||||||
|
final commentRegex = RegExp(r'^(#|!).*$');
|
||||||
|
|
||||||
|
if (allowRegex.hasMatch(rule)) {
|
||||||
|
return Text(
|
||||||
|
AppLocalizations.of(context)!.allowed,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.green
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else if (blockRegex.hasMatch(rule)) {
|
||||||
|
return Text(
|
||||||
|
AppLocalizations.of(context)!.blocked,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.red
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else if (commentRegex.hasMatch(rule)) {
|
||||||
|
return Text(
|
||||||
|
AppLocalizations.of(context)!.comment,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.grey
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget content() {
|
||||||
|
switch (serversProvider.filtering.loadStatus) {
|
||||||
|
case LoadStatus.loading:
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const CircularProgressIndicator(),
|
||||||
|
const SizedBox(height: 30),
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context)!.loadingFilters,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 22,
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
case LoadStatus.loaded:
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 1,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context)!.whitelists,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.w500
|
||||||
|
),
|
||||||
|
),
|
||||||
|
AddFiltersButton(
|
||||||
|
type: 'whitelist',
|
||||||
|
widget: (fn) => IconButton(
|
||||||
|
onPressed: fn,
|
||||||
|
icon: const Icon(Icons.add_rounded)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: ListView.builder(
|
||||||
|
itemCount: serversProvider.filtering.data!.whitelistFilters.length,
|
||||||
|
itemBuilder: (context, index) => CustomListTile(
|
||||||
|
title: serversProvider.filtering.data!.whitelistFilters[index].name,
|
||||||
|
subtitle: "${intFormat(serversProvider.filtering.data!.whitelistFilters[index].rulesCount, Platform.localeName)} ${AppLocalizations.of(context)!.enabledRules}",
|
||||||
|
trailing: Icon(
|
||||||
|
serversProvider.filtering.data!.whitelistFilters[index].enabled == true
|
||||||
|
? Icons.check_circle_rounded
|
||||||
|
: Icons.cancel,
|
||||||
|
color: serversProvider.filtering.data!.whitelistFilters[index].enabled == true
|
||||||
|
? appConfigProvider.useThemeColorForStatus == true
|
||||||
|
? Theme.of(context).colorScheme.primary
|
||||||
|
: Colors.green
|
||||||
|
: appConfigProvider.useThemeColorForStatus == true
|
||||||
|
? Colors.grey
|
||||||
|
: Colors.red
|
||||||
|
),
|
||||||
|
onTap: () => onOpenDetailsModal(serversProvider.filtering.data!.whitelistFilters[index], 'whitelist'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
flex: 1,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context)!.blacklists,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.w500
|
||||||
|
),
|
||||||
|
),
|
||||||
|
AddFiltersButton(
|
||||||
|
type: 'blacklist',
|
||||||
|
widget: (fn) => IconButton(
|
||||||
|
onPressed: fn,
|
||||||
|
icon: const Icon(Icons.add_rounded)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: ListView.builder(
|
||||||
|
itemCount: serversProvider.filtering.data!.filters.length,
|
||||||
|
itemBuilder: (context, index) => CustomListTile(
|
||||||
|
title: serversProvider.filtering.data!.filters[index].name,
|
||||||
|
subtitle: "${intFormat(serversProvider.filtering.data!.filters[index].rulesCount, Platform.localeName)} ${AppLocalizations.of(context)!.enabledRules}",
|
||||||
|
trailing: Icon(
|
||||||
|
serversProvider.filtering.data!.filters[index].enabled == true
|
||||||
|
? Icons.check_circle_rounded
|
||||||
|
: Icons.cancel,
|
||||||
|
color: serversProvider.filtering.data!.filters[index].enabled == true
|
||||||
|
? appConfigProvider.useThemeColorForStatus == true
|
||||||
|
? Theme.of(context).colorScheme.primary
|
||||||
|
: Colors.green
|
||||||
|
: appConfigProvider.useThemeColorForStatus == true
|
||||||
|
? Colors.grey
|
||||||
|
: Colors.red
|
||||||
|
),
|
||||||
|
onTap: () => onOpenDetailsModal(serversProvider.filtering.data!.filters[index], 'blacklist'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
flex: 1,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context)!.customRules,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.w500
|
||||||
|
),
|
||||||
|
),
|
||||||
|
AddFiltersButton(
|
||||||
|
type: '',
|
||||||
|
widget: (fn) => IconButton(
|
||||||
|
onPressed: fn,
|
||||||
|
icon: const Icon(Icons.add_rounded)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: ListView.builder(
|
||||||
|
itemCount: serversProvider.filtering.data!.userRules.length,
|
||||||
|
itemBuilder: (context, index) => ListTile(
|
||||||
|
title: Text(
|
||||||
|
serversProvider.filtering.data!.userRules[index],
|
||||||
|
style: TextStyle(
|
||||||
|
color: checkIfComment(serversProvider.filtering.data!.userRules[index]) == true
|
||||||
|
? Theme.of(context).colorScheme.onSurface.withOpacity(0.6)
|
||||||
|
: Theme.of(context).colorScheme.onSurface,
|
||||||
|
fontWeight: FontWeight.normal,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
subtitle: generateSubtitle(serversProvider.filtering.data!.userRules[index]),
|
||||||
|
trailing: IconButton(
|
||||||
|
onPressed: () => onRemoveCustomRule(serversProvider.filtering.data!.userRules[index]),
|
||||||
|
icon: const Icon(Icons.delete)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
case LoadStatus.error:
|
||||||
|
return SizedBox.expand(
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: [
|
||||||
|
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)!.filtersNotLoaded,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 22,
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(AppLocalizations.of(context)!.filters),
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: refreshData,
|
||||||
|
icon: const Icon(Icons.refresh_rounded),
|
||||||
|
tooltip: AppLocalizations.of(context)!.refresh,
|
||||||
|
),
|
||||||
|
...actions
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: content(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,9 +7,9 @@ import 'package:flutter/rendering.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';
|
||||||
|
|
||||||
import 'package:adguard_home_manager/screens/filters/filter_list_tile.dart';
|
|
||||||
import 'package:adguard_home_manager/screens/filters/add_list_modal.dart';
|
import 'package:adguard_home_manager/screens/filters/add_list_modal.dart';
|
||||||
import 'package:adguard_home_manager/screens/filters/delete_list_modal.dart';
|
import 'package:adguard_home_manager/screens/filters/delete_list_modal.dart';
|
||||||
|
import 'package:adguard_home_manager/widgets/custom_list_tile.dart';
|
||||||
|
|
||||||
import 'package:adguard_home_manager/functions/format_time.dart';
|
import 'package:adguard_home_manager/functions/format_time.dart';
|
||||||
import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
||||||
|
@ -23,11 +23,13 @@ import 'package:adguard_home_manager/models/filtering.dart';
|
||||||
class ListDetailsScreen extends StatefulWidget {
|
class ListDetailsScreen extends StatefulWidget {
|
||||||
final Filter list;
|
final Filter list;
|
||||||
final String type;
|
final String type;
|
||||||
|
final bool dialog;
|
||||||
|
|
||||||
const ListDetailsScreen({
|
const ListDetailsScreen({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.list,
|
required this.list,
|
||||||
required this.type,
|
required this.type,
|
||||||
|
required this.dialog
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -68,6 +70,8 @@ class _ListDetailsScreenState extends State<ListDetailsScreen> {
|
||||||
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 width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
void enableDisableList(Filter list, bool newStatus) async {
|
void enableDisableList(Filter list, bool newStatus) async {
|
||||||
ProcessModal processModal = ProcessModal(context: context);
|
ProcessModal processModal = ProcessModal(context: context);
|
||||||
processModal.open(
|
processModal.open(
|
||||||
|
@ -216,108 +220,234 @@ class _ListDetailsScreenState extends State<ListDetailsScreen> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Scaffold(
|
List<Widget> content() {
|
||||||
appBar: AppBar(
|
return [
|
||||||
title: Text(AppLocalizations.of(context)!.listDetails),
|
CustomListTile(
|
||||||
actions: [
|
icon: Icons.shield_rounded,
|
||||||
IconButton(
|
title: AppLocalizations.of(context)!.currentStatus,
|
||||||
onPressed: () => {
|
subtitleWidget: Text(
|
||||||
|
enabled == true
|
||||||
|
? AppLocalizations.of(context)!.enabled
|
||||||
|
: AppLocalizations.of(context)!.disabled,
|
||||||
|
style: TextStyle(
|
||||||
|
color: enabled == true
|
||||||
|
? appConfigProvider.useThemeColorForStatus == true
|
||||||
|
? Theme.of(context).colorScheme.primary
|
||||||
|
: Colors.green
|
||||||
|
: appConfigProvider.useThemeColorForStatus == true
|
||||||
|
? Colors.grey
|
||||||
|
: Colors.red,
|
||||||
|
fontWeight: FontWeight.w500
|
||||||
|
),
|
||||||
|
),
|
||||||
|
padding: widget.dialog == true
|
||||||
|
? const EdgeInsets.symmetric(
|
||||||
|
horizontal: 24,
|
||||||
|
vertical: 8
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
CustomListTile(
|
||||||
|
icon: Icons.badge_rounded,
|
||||||
|
title: AppLocalizations.of(context)!.name,
|
||||||
|
subtitle: name,
|
||||||
|
padding: widget.dialog == true
|
||||||
|
? const EdgeInsets.symmetric(
|
||||||
|
horizontal: 24,
|
||||||
|
vertical: 8
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
CustomListTile(
|
||||||
|
icon: Icons.link_rounded,
|
||||||
|
title: "URL",
|
||||||
|
subtitle: widget.list.url,
|
||||||
|
padding: widget.dialog == true
|
||||||
|
? const EdgeInsets.symmetric(
|
||||||
|
horizontal: 24,
|
||||||
|
vertical: 8
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
CustomListTile(
|
||||||
|
icon: Icons.list_rounded,
|
||||||
|
title: AppLocalizations.of(context)!.rules,
|
||||||
|
subtitle: widget.list.rulesCount.toString(),
|
||||||
|
padding: widget.dialog == true
|
||||||
|
? const EdgeInsets.symmetric(
|
||||||
|
horizontal: 24,
|
||||||
|
vertical: 8
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
CustomListTile(
|
||||||
|
icon: Icons.shield_rounded,
|
||||||
|
title: AppLocalizations.of(context)!.listType,
|
||||||
|
subtitle: widget.type == 'whitelist'
|
||||||
|
? AppLocalizations.of(context)!.whitelist
|
||||||
|
: AppLocalizations.of(context)!.blacklist,
|
||||||
|
padding: widget.dialog == true
|
||||||
|
? const EdgeInsets.symmetric(
|
||||||
|
horizontal: 24,
|
||||||
|
vertical: 8
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
if (widget.list.lastUpdated != null) CustomListTile(
|
||||||
|
icon: Icons.schedule_rounded,
|
||||||
|
title: AppLocalizations.of(context)!.latestUpdate,
|
||||||
|
subtitle: convertTimestampLocalTimezone(widget.list.lastUpdated!, 'dd-MM-yyyy HH:mm'),
|
||||||
|
padding: widget.dialog == true
|
||||||
|
? const EdgeInsets.symmetric(
|
||||||
|
horizontal: 24,
|
||||||
|
vertical: 8
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
if (widget.dialog == true) Container(height: 16)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> actions() {
|
||||||
|
return [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => {
|
||||||
|
if (width > 700 || !(Platform.isAndroid || Platform.isIOS)) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (ctx) => AddListModal(
|
||||||
|
list: widget.list,
|
||||||
|
type: widget.type,
|
||||||
|
onEdit: confirmEditList,
|
||||||
|
dialog: true,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (ctx) => AddListModal(
|
builder: (ctx) => AddListModal(
|
||||||
list: widget.list,
|
list: widget.list,
|
||||||
type: widget.type,
|
type: widget.type,
|
||||||
onEdit: confirmEditList
|
onEdit: confirmEditList,
|
||||||
|
dialog: false,
|
||||||
),
|
),
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
backgroundColor: Colors.transparent
|
backgroundColor: Colors.transparent
|
||||||
)
|
)
|
||||||
},
|
}
|
||||||
icon: const Icon(Icons.edit),
|
},
|
||||||
tooltip: AppLocalizations.of(context)!.edit,
|
icon: const Icon(Icons.edit),
|
||||||
|
tooltip: AppLocalizations.of(context)!.edit,
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => DeleteListModal(
|
||||||
|
onConfirm: () => deleteList(widget.list, widget.type),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.delete),
|
||||||
|
tooltip: AppLocalizations.of(context)!.delete,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (widget.dialog == true) {
|
||||||
|
return Dialog(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
maxWidth: 500
|
||||||
),
|
),
|
||||||
IconButton(
|
child: Column(
|
||||||
onPressed: () {
|
mainAxisSize: MainAxisSize.min,
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => DeleteListModal(
|
|
||||||
onConfirm: () => deleteList(widget.list, widget.type),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.delete),
|
|
||||||
tooltip: AppLocalizations.of(context)!.delete,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
body: Stack(
|
|
||||||
children: [
|
|
||||||
ListView(
|
|
||||||
children: [
|
children: [
|
||||||
FilterListTile(
|
Padding(
|
||||||
icon: Icons.shield_rounded,
|
padding: const EdgeInsets.all(16),
|
||||||
title: AppLocalizations.of(context)!.currentStatus,
|
child: Row(
|
||||||
subtitle: enabled == true
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
? AppLocalizations.of(context)!.enabled
|
children: [
|
||||||
: AppLocalizations.of(context)!.disabled,
|
Row(
|
||||||
color: enabled == true
|
children: [
|
||||||
? appConfigProvider.useThemeColorForStatus == true
|
IconButton(
|
||||||
? Theme.of(context).colorScheme.primary
|
onPressed: () => Navigator.pop(context),
|
||||||
: Colors.green
|
icon: const Icon(Icons.clear_rounded),
|
||||||
: appConfigProvider.useThemeColorForStatus == true
|
tooltip: AppLocalizations.of(context)!.close,
|
||||||
? Colors.grey
|
),
|
||||||
: Colors.red,
|
const SizedBox(width: 8),
|
||||||
bold: true,
|
Text(
|
||||||
),
|
AppLocalizations.of(context)!.listDetails,
|
||||||
FilterListTile(
|
style: const TextStyle(
|
||||||
icon: Icons.badge_rounded,
|
fontSize: 22
|
||||||
title: AppLocalizations.of(context)!.name,
|
),
|
||||||
subtitle: name
|
)
|
||||||
),
|
],
|
||||||
FilterListTile(
|
),
|
||||||
icon: Icons.link_rounded,
|
Row(
|
||||||
title: "URL",
|
children: [
|
||||||
subtitle: widget.list.url
|
IconButton(
|
||||||
),
|
onPressed: () => enableDisableList(widget.list, !enabled),
|
||||||
FilterListTile(
|
icon: Icon(
|
||||||
icon: Icons.list_rounded,
|
enabled == true
|
||||||
title: AppLocalizations.of(context)!.rules,
|
? Icons.gpp_bad_rounded
|
||||||
subtitle: widget.list.rulesCount.toString()
|
: Icons.verified_user_rounded,
|
||||||
),
|
),
|
||||||
FilterListTile(
|
tooltip: enabled == true
|
||||||
icon: Icons.shield_rounded,
|
? AppLocalizations.of(context)!.disableList
|
||||||
title: AppLocalizations.of(context)!.listType,
|
: AppLocalizations.of(context)!.enableList,
|
||||||
subtitle: widget.type == 'whitelist'
|
),
|
||||||
? AppLocalizations.of(context)!.whitelist
|
...actions()
|
||||||
: AppLocalizations.of(context)!.blacklist,
|
],
|
||||||
),
|
)
|
||||||
if (widget.list.lastUpdated != null) FilterListTile(
|
],
|
||||||
icon: Icons.schedule_rounded,
|
),
|
||||||
title: AppLocalizations.of(context)!.latestUpdate,
|
|
||||||
subtitle: convertTimestampLocalTimezone(widget.list.lastUpdated!, 'dd-MM-yyyy HH:mm'),
|
|
||||||
),
|
),
|
||||||
|
Flexible(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Wrap(
|
||||||
|
children: content(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
AnimatedPositioned(
|
)
|
||||||
duration: const Duration(milliseconds: 100),
|
);
|
||||||
curve: Curves.easeInOut,
|
}
|
||||||
bottom: fabVisible ?
|
else {
|
||||||
appConfigProvider.showingSnackbar
|
return Scaffold(
|
||||||
? 70 : (Platform.isIOS ? 40 : 20)
|
appBar: AppBar(
|
||||||
: -70,
|
title: Text(AppLocalizations.of(context)!.listDetails),
|
||||||
right: 20,
|
actions: actions(),
|
||||||
child: FloatingActionButton(
|
),
|
||||||
onPressed: () => enableDisableList(widget.list, !enabled),
|
body: Stack(
|
||||||
child: Icon(
|
children: [
|
||||||
enabled == true
|
ListView(
|
||||||
? Icons.gpp_bad_rounded
|
children: content(),
|
||||||
: Icons.verified_user_rounded,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
)
|
AnimatedPositioned(
|
||||||
],
|
duration: const Duration(milliseconds: 100),
|
||||||
),
|
curve: Curves.easeInOut,
|
||||||
);
|
bottom: fabVisible ?
|
||||||
|
appConfigProvider.showingSnackbar
|
||||||
|
? 70 : (Platform.isIOS ? 40 : 20)
|
||||||
|
: -70,
|
||||||
|
right: 20,
|
||||||
|
child: FloatingActionButton(
|
||||||
|
onPressed: () => enableDisableList(widget.list, !enabled),
|
||||||
|
child: Icon(
|
||||||
|
enabled == true
|
||||||
|
? Icons.gpp_bad_rounded
|
||||||
|
: Icons.verified_user_rounded,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -9,11 +9,13 @@ import 'package:adguard_home_manager/widgets/option_box.dart';
|
||||||
class UpdateIntervalListsModal extends StatefulWidget {
|
class UpdateIntervalListsModal extends StatefulWidget {
|
||||||
final int interval;
|
final int interval;
|
||||||
final void Function(int) onChange;
|
final void Function(int) onChange;
|
||||||
|
final bool dialog;
|
||||||
|
|
||||||
const UpdateIntervalListsModal({
|
const UpdateIntervalListsModal({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.interval,
|
required this.interval,
|
||||||
required this.onChange,
|
required this.onChange,
|
||||||
|
required this.dialog
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -37,272 +39,266 @@ class _UpdateIntervalListsModalState extends State<UpdateIntervalListsModal> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final MediaQueryData mediaQueryData = MediaQuery.of(context);
|
final MediaQueryData mediaQueryData = MediaQuery.of(context);
|
||||||
|
|
||||||
return Padding(
|
Widget content() {
|
||||||
padding: mediaQueryData.viewInsets,
|
return Column(
|
||||||
child: Container(
|
mainAxisSize: MainAxisSize.min,
|
||||||
height: Platform.isIOS ? 406 : 390,
|
children: [
|
||||||
decoration: BoxDecoration(
|
Flexible(
|
||||||
color: Theme.of(context).dialogBackgroundColor,
|
child: SingleChildScrollView(
|
||||||
borderRadius: const BorderRadius.only(
|
child: Wrap(
|
||||||
topLeft: Radius.circular(28),
|
|
||||||
topRight: Radius.circular(28)
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: ListView(
|
|
||||||
physics: (Platform.isIOS ? 426 : 410) < MediaQuery.of(context).size.height
|
|
||||||
? const NeverScrollableScrollPhysics()
|
|
||||||
: null,
|
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Row(
|
||||||
padding: const EdgeInsets.only(top: 24),
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
child: Icon(
|
children: [
|
||||||
Icons.update_rounded,
|
Column(
|
||||||
size: 24,
|
|
||||||
color: Theme.of(context).listTileTheme.iconColor
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 24,
|
|
||||||
vertical: 16
|
|
||||||
),
|
|
||||||
width: double.maxFinite,
|
|
||||||
child: Text(
|
|
||||||
AppLocalizations.of(context)!.updateFrequency,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 24,
|
|
||||||
color: Theme.of(context).colorScheme.onSurface
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
width: double.maxFinite,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Padding(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
padding: const EdgeInsets.only(top: 24),
|
||||||
children: [
|
child: Icon(
|
||||||
Container(
|
Icons.update_rounded,
|
||||||
width: (mediaQueryData.size.width-70)/2,
|
size: 24,
|
||||||
margin: const EdgeInsets.only(
|
color: Theme.of(context).listTileTheme.iconColor
|
||||||
top: 10,
|
),
|
||||||
right: 5,
|
|
||||||
bottom: 5
|
|
||||||
),
|
|
||||||
child: OptionBox(
|
|
||||||
optionsValue: selectedOption,
|
|
||||||
itemValue: 0,
|
|
||||||
onTap: _updateRadioValue,
|
|
||||||
child: Center(
|
|
||||||
child: AnimatedDefaultTextStyle(
|
|
||||||
duration: const Duration(milliseconds: 250),
|
|
||||||
style: TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 14,
|
|
||||||
color: selectedOption == 0
|
|
||||||
? Theme.of(context).colorScheme.onInverseSurface
|
|
||||||
: Theme.of(context).colorScheme.onSurface
|
|
||||||
),
|
|
||||||
child: Text(AppLocalizations.of(context)!.never),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
width: (mediaQueryData.size.width-70)/2,
|
|
||||||
margin: const EdgeInsets.only(
|
|
||||||
top: 10,
|
|
||||||
left: 5,
|
|
||||||
bottom: 5
|
|
||||||
),
|
|
||||||
child: OptionBox(
|
|
||||||
optionsValue: selectedOption,
|
|
||||||
itemValue: 1,
|
|
||||||
onTap: _updateRadioValue,
|
|
||||||
child: Center(
|
|
||||||
child: AnimatedDefaultTextStyle(
|
|
||||||
duration: const Duration(milliseconds: 250),
|
|
||||||
style: TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 14,
|
|
||||||
color: selectedOption == 1
|
|
||||||
? Theme.of(context).colorScheme.onInverseSurface
|
|
||||||
: Theme.of(context).colorScheme.onSurface
|
|
||||||
),
|
|
||||||
child: Text(AppLocalizations.of(context)!.hour1),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
Row(
|
Container(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
padding: const EdgeInsets.symmetric(
|
||||||
children: [
|
horizontal: 24,
|
||||||
Container(
|
vertical: 16
|
||||||
width: (mediaQueryData.size.width-70)/2,
|
),
|
||||||
margin: const EdgeInsets.only(
|
child: Text(
|
||||||
top: 5,
|
AppLocalizations.of(context)!.updateFrequency,
|
||||||
right: 5,
|
textAlign: TextAlign.center,
|
||||||
bottom: 5
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
style: TextStyle(
|
||||||
child: OptionBox(
|
fontSize: 24,
|
||||||
optionsValue: selectedOption,
|
color: Theme.of(context).colorScheme.onSurface
|
||||||
itemValue: 12,
|
|
||||||
onTap: _updateRadioValue,
|
|
||||||
child: Center(
|
|
||||||
child: AnimatedDefaultTextStyle(
|
|
||||||
duration: const Duration(milliseconds: 250),
|
|
||||||
style: TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 14,
|
|
||||||
color: selectedOption == 12
|
|
||||||
? Theme.of(context).colorScheme.onInverseSurface
|
|
||||||
: Theme.of(context).colorScheme.onSurface
|
|
||||||
),
|
|
||||||
child: Text(AppLocalizations.of(context)!.hours12),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Container(
|
),
|
||||||
width: (mediaQueryData.size.width-70)/2,
|
|
||||||
margin: const EdgeInsets.only(
|
|
||||||
top: 5,
|
|
||||||
left: 5,
|
|
||||||
bottom: 5
|
|
||||||
),
|
|
||||||
child: OptionBox(
|
|
||||||
optionsValue: selectedOption,
|
|
||||||
itemValue: 24,
|
|
||||||
onTap: _updateRadioValue,
|
|
||||||
child: Center(
|
|
||||||
child: AnimatedDefaultTextStyle(
|
|
||||||
duration: const Duration(milliseconds: 250),
|
|
||||||
style: TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 14,
|
|
||||||
color: selectedOption == 24
|
|
||||||
? Theme.of(context).colorScheme.onInverseSurface
|
|
||||||
: Theme.of(context).colorScheme.onSurface
|
|
||||||
),
|
|
||||||
child: Text(AppLocalizations.of(context)!.hours24),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
width: (mediaQueryData.size.width-70)/2,
|
|
||||||
margin: const EdgeInsets.only(
|
|
||||||
top: 5,
|
|
||||||
right: 5,
|
|
||||||
bottom: 10
|
|
||||||
),
|
|
||||||
child: OptionBox(
|
|
||||||
optionsValue: selectedOption,
|
|
||||||
itemValue: 72,
|
|
||||||
onTap: _updateRadioValue,
|
|
||||||
child: Center(
|
|
||||||
child: AnimatedDefaultTextStyle(
|
|
||||||
duration: const Duration(milliseconds: 250),
|
|
||||||
style: TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 14,
|
|
||||||
color: selectedOption == 72
|
|
||||||
? Theme.of(context).colorScheme.onInverseSurface
|
|
||||||
: Theme.of(context).colorScheme.onSurface
|
|
||||||
),
|
|
||||||
child: Text(AppLocalizations.of(context)!.days3),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
width: (mediaQueryData.size.width-70)/2,
|
|
||||||
margin: const EdgeInsets.only(
|
|
||||||
top: 5,
|
|
||||||
left: 5,
|
|
||||||
bottom: 10
|
|
||||||
),
|
|
||||||
child: OptionBox(
|
|
||||||
optionsValue: selectedOption,
|
|
||||||
itemValue: 168,
|
|
||||||
onTap: _updateRadioValue,
|
|
||||||
child: Center(
|
|
||||||
child: AnimatedDefaultTextStyle(
|
|
||||||
duration: const Duration(milliseconds: 250),
|
|
||||||
style: TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 14,
|
|
||||||
color: selectedOption == 168
|
|
||||||
? Theme.of(context).colorScheme.onInverseSurface
|
|
||||||
: Theme.of(context).colorScheme.onSurface
|
|
||||||
),
|
|
||||||
child: Text(AppLocalizations.of(context)!.days7),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
child: Wrap(
|
||||||
|
runSpacing: 16,
|
||||||
|
children: [
|
||||||
|
FractionallySizedBox(
|
||||||
|
widthFactor: 0.5,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 6),
|
||||||
|
child: OptionBox(
|
||||||
|
optionsValue: selectedOption,
|
||||||
|
itemValue: 0,
|
||||||
|
onTap: _updateRadioValue,
|
||||||
|
child: Center(
|
||||||
|
child: AnimatedDefaultTextStyle(
|
||||||
|
duration: const Duration(milliseconds: 250),
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 14,
|
||||||
|
color: selectedOption == 0
|
||||||
|
? Theme.of(context).colorScheme.onInverseSurface
|
||||||
|
: Theme.of(context).colorScheme.onSurface
|
||||||
|
),
|
||||||
|
child: Text(AppLocalizations.of(context)!.never),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
FractionallySizedBox(
|
||||||
|
widthFactor: 0.5,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 6),
|
||||||
|
child: OptionBox(
|
||||||
|
optionsValue: selectedOption,
|
||||||
|
itemValue: 1,
|
||||||
|
onTap: _updateRadioValue,
|
||||||
|
child: Center(
|
||||||
|
child: AnimatedDefaultTextStyle(
|
||||||
|
duration: const Duration(milliseconds: 250),
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 14,
|
||||||
|
color: selectedOption == 1
|
||||||
|
? Theme.of(context).colorScheme.onInverseSurface
|
||||||
|
: Theme.of(context).colorScheme.onSurface
|
||||||
|
),
|
||||||
|
child: Text(AppLocalizations.of(context)!.hour1),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
FractionallySizedBox(
|
||||||
|
widthFactor: 0.5,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 6),
|
||||||
|
child: OptionBox(
|
||||||
|
optionsValue: selectedOption,
|
||||||
|
itemValue: 12,
|
||||||
|
onTap: _updateRadioValue,
|
||||||
|
child: Center(
|
||||||
|
child: AnimatedDefaultTextStyle(
|
||||||
|
duration: const Duration(milliseconds: 250),
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 14,
|
||||||
|
color: selectedOption == 12
|
||||||
|
? Theme.of(context).colorScheme.onInverseSurface
|
||||||
|
: Theme.of(context).colorScheme.onSurface
|
||||||
|
),
|
||||||
|
child: Text(AppLocalizations.of(context)!.hours12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
FractionallySizedBox(
|
||||||
|
widthFactor: 0.5,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 6),
|
||||||
|
child: OptionBox(
|
||||||
|
optionsValue: selectedOption,
|
||||||
|
itemValue: 24,
|
||||||
|
onTap: _updateRadioValue,
|
||||||
|
child: Center(
|
||||||
|
child: AnimatedDefaultTextStyle(
|
||||||
|
duration: const Duration(milliseconds: 250),
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 14,
|
||||||
|
color: selectedOption == 24
|
||||||
|
? Theme.of(context).colorScheme.onInverseSurface
|
||||||
|
: Theme.of(context).colorScheme.onSurface
|
||||||
|
),
|
||||||
|
child: Text(AppLocalizations.of(context)!.hours24),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
FractionallySizedBox(
|
||||||
|
widthFactor: 0.5,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 6),
|
||||||
|
child: OptionBox(
|
||||||
|
optionsValue: selectedOption,
|
||||||
|
itemValue: 72,
|
||||||
|
onTap: _updateRadioValue,
|
||||||
|
child: Center(
|
||||||
|
child: AnimatedDefaultTextStyle(
|
||||||
|
duration: const Duration(milliseconds: 250),
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 14,
|
||||||
|
color: selectedOption == 72
|
||||||
|
? Theme.of(context).colorScheme.onInverseSurface
|
||||||
|
: Theme.of(context).colorScheme.onSurface
|
||||||
|
),
|
||||||
|
child: Text(AppLocalizations.of(context)!.days3),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
FractionallySizedBox(
|
||||||
|
widthFactor: 0.5,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 6),
|
||||||
|
child: OptionBox(
|
||||||
|
optionsValue: selectedOption,
|
||||||
|
itemValue: 168,
|
||||||
|
onTap: _updateRadioValue,
|
||||||
|
child: Center(
|
||||||
|
child: AnimatedDefaultTextStyle(
|
||||||
|
duration: const Duration(milliseconds: 250),
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 14,
|
||||||
|
color: selectedOption == 168
|
||||||
|
? Theme.of(context).colorScheme.onInverseSurface
|
||||||
|
: Theme.of(context).colorScheme.onSurface
|
||||||
|
),
|
||||||
|
child: Text(AppLocalizations.of(context)!.days7),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
),
|
||||||
padding: const EdgeInsets.all(24),
|
Padding(
|
||||||
child: Row(
|
padding: const EdgeInsets.all(24),
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
child: Row(
|
||||||
children: [
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
TextButton(
|
children: [
|
||||||
onPressed: () => Navigator.pop(context),
|
TextButton(
|
||||||
child: Text(AppLocalizations.of(context)!.cancel),
|
onPressed: () => Navigator.pop(context),
|
||||||
),
|
child: Text(AppLocalizations.of(context)!.cancel),
|
||||||
const SizedBox(width: 20),
|
),
|
||||||
TextButton(
|
const SizedBox(width: 20),
|
||||||
onPressed: selectedOption != null
|
TextButton(
|
||||||
? () {
|
onPressed: selectedOption != null
|
||||||
Navigator.pop(context);
|
? () {
|
||||||
widget.onChange(selectedOption!);
|
Navigator.pop(context);
|
||||||
}
|
widget.onChange(selectedOption!);
|
||||||
: null,
|
}
|
||||||
style: ButtonStyle(
|
: null,
|
||||||
overlayColor: MaterialStateProperty.all(
|
style: ButtonStyle(
|
||||||
Theme.of(context).colorScheme.primary.withOpacity(0.1)
|
overlayColor: MaterialStateProperty.all(
|
||||||
),
|
Theme.of(context).colorScheme.primary.withOpacity(0.1)
|
||||||
foregroundColor: MaterialStateProperty.all(
|
),
|
||||||
selectedOption != null
|
foregroundColor: MaterialStateProperty.all(
|
||||||
? Theme.of(context).colorScheme.primary
|
selectedOption != null
|
||||||
: Colors.grey,
|
? Theme.of(context).colorScheme.primary
|
||||||
),
|
: Colors.grey,
|
||||||
),
|
),
|
||||||
child: Text(AppLocalizations.of(context)!.confirm),
|
),
|
||||||
),
|
child: Text(AppLocalizations.of(context)!.confirm),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
if (Platform.isIOS) const SizedBox(height: 16)
|
),
|
||||||
],
|
if (Platform.isIOS) const SizedBox(height: 16)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (widget.dialog == true) {
|
||||||
|
return Dialog(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
maxWidth: 500
|
||||||
|
),
|
||||||
|
child: content()
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
}
|
||||||
|
else {
|
||||||
|
return Padding(
|
||||||
|
padding: mediaQueryData.viewInsets,
|
||||||
|
child: Container(
|
||||||
|
height: Platform.isIOS ? 406 : 390,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).dialogBackgroundColor,
|
||||||
|
borderRadius: const BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(28),
|
||||||
|
topRight: Radius.circular(28)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: content()
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -48,12 +48,15 @@ class HomeChart extends StatelessWidget {
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Flexible(
|
||||||
label,
|
child: Text(
|
||||||
style: TextStyle(
|
label,
|
||||||
fontSize: 18,
|
overflow: TextOverflow.ellipsis,
|
||||||
fontWeight: FontWeight.w500,
|
style: TextStyle(
|
||||||
color: Theme.of(context).colorScheme.onSurface
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
!isEmpty
|
!isEmpty
|
||||||
|
|
|
@ -12,13 +12,27 @@ class HomeFab extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final serversProvider = Provider.of<ServersProvider>(context);
|
final serversProvider = Provider.of<ServersProvider>(context);
|
||||||
|
|
||||||
|
final width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
void openManagementBottomSheet() {
|
void openManagementBottomSheet() {
|
||||||
showModalBottomSheet(
|
if (width > 700) {
|
||||||
context: context,
|
showDialog(
|
||||||
isScrollControlled: true,
|
context: context,
|
||||||
builder: (context) => const ManagementModal(),
|
builder: (context) => const ManagementModal(
|
||||||
backgroundColor: Colors.transparent,
|
dialog: true,
|
||||||
);
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
builder: (context) => const ManagementModal(
|
||||||
|
dialog: false,
|
||||||
|
),
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return serversProvider.serverStatus.loadStatus == 1
|
return serversProvider.serverStatus.loadStatus == 1
|
||||||
|
|
|
@ -55,6 +55,8 @@ class _HomeState extends State<Home> {
|
||||||
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 width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
Widget status() {
|
Widget status() {
|
||||||
switch (serversProvider.serverStatus.loadStatus) {
|
switch (serversProvider.serverStatus.loadStatus) {
|
||||||
case 0:
|
case 0:
|
||||||
|
@ -92,72 +94,138 @@ class _HomeState extends State<Home> {
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
HomeChart(
|
Wrap(
|
||||||
data: serversProvider.serverStatus.data!.stats.dnsQueries,
|
children: [
|
||||||
label: AppLocalizations.of(context)!.dnsQueries,
|
FractionallySizedBox(
|
||||||
primaryValue: intFormat(serversProvider.serverStatus.data!.stats.numDnsQueries, Platform.localeName),
|
widthFactor: width > 700 ? 0.5 : 1,
|
||||||
secondaryValue: "${doubleFormat(serversProvider.serverStatus.data!.stats.avgProcessingTime*1000, Platform.localeName)} ms",
|
child: HomeChart(
|
||||||
color: Colors.blue,
|
data: serversProvider.serverStatus.data!.stats.dnsQueries,
|
||||||
),
|
label: AppLocalizations.of(context)!.dnsQueries,
|
||||||
|
primaryValue: intFormat(serversProvider.serverStatus.data!.stats.numDnsQueries, Platform.localeName),
|
||||||
HomeChart(
|
secondaryValue: "${doubleFormat(serversProvider.serverStatus.data!.stats.avgProcessingTime*1000, Platform.localeName)} ms",
|
||||||
data: serversProvider.serverStatus.data!.stats.blockedFiltering,
|
color: Colors.blue,
|
||||||
label: AppLocalizations.of(context)!.blockedFilters,
|
),
|
||||||
primaryValue: intFormat(serversProvider.serverStatus.data!.stats.numBlockedFiltering, Platform.localeName),
|
),
|
||||||
secondaryValue: "${serversProvider.serverStatus.data!.stats.numDnsQueries > 0 ? doubleFormat((serversProvider.serverStatus.data!.stats.numBlockedFiltering/serversProvider.serverStatus.data!.stats.numDnsQueries)*100, Platform.localeName) : 0}%",
|
FractionallySizedBox(
|
||||||
color: Colors.red,
|
widthFactor: width > 700 ? 0.5 : 1,
|
||||||
|
child: HomeChart(
|
||||||
|
data: serversProvider.serverStatus.data!.stats.blockedFiltering,
|
||||||
|
label: AppLocalizations.of(context)!.blockedFilters,
|
||||||
|
primaryValue: intFormat(serversProvider.serverStatus.data!.stats.numBlockedFiltering, Platform.localeName),
|
||||||
|
secondaryValue: "${serversProvider.serverStatus.data!.stats.numDnsQueries > 0 ? doubleFormat((serversProvider.serverStatus.data!.stats.numBlockedFiltering/serversProvider.serverStatus.data!.stats.numDnsQueries)*100, Platform.localeName) : 0}%",
|
||||||
|
color: Colors.red,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
FractionallySizedBox(
|
||||||
|
widthFactor: width > 700 ? 0.5 : 1,
|
||||||
|
child: HomeChart(
|
||||||
|
data: serversProvider.serverStatus.data!.stats.replacedSafebrowsing,
|
||||||
|
label: AppLocalizations.of(context)!.malwarePhisingBlocked,
|
||||||
|
primaryValue: intFormat(serversProvider.serverStatus.data!.stats.numReplacedSafebrowsing, Platform.localeName),
|
||||||
|
secondaryValue: "${serversProvider.serverStatus.data!.stats.numDnsQueries > 0 ? doubleFormat((serversProvider.serverStatus.data!.stats.numReplacedSafebrowsing/serversProvider.serverStatus.data!.stats.numDnsQueries)*100, Platform.localeName) : 0}%",
|
||||||
|
color: Colors.green,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
FractionallySizedBox(
|
||||||
|
widthFactor: width > 700 ? 0.5 : 1,
|
||||||
|
child: HomeChart(
|
||||||
|
data: serversProvider.serverStatus.data!.stats.replacedParental,
|
||||||
|
label: AppLocalizations.of(context)!.blockedAdultWebsites,
|
||||||
|
primaryValue: intFormat(serversProvider.serverStatus.data!.stats.numReplacedParental, Platform.localeName),
|
||||||
|
secondaryValue: "${serversProvider.serverStatus.data!.stats.numDnsQueries > 0 ? doubleFormat((serversProvider.serverStatus.data!.stats.numReplacedParental/serversProvider.serverStatus.data!.stats.numDnsQueries)*100, Platform.localeName) : 0}%",
|
||||||
|
color: Colors.orange,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
HomeChart(
|
if (width <= 700) ...[
|
||||||
data: serversProvider.serverStatus.data!.stats.replacedSafebrowsing,
|
TopItems(
|
||||||
label: AppLocalizations.of(context)!.malwarePhisingBlocked,
|
label: AppLocalizations.of(context)!.topQueriedDomains,
|
||||||
primaryValue: intFormat(serversProvider.serverStatus.data!.stats.numReplacedSafebrowsing, Platform.localeName),
|
data: serversProvider.serverStatus.data!.stats.topQueriedDomains,
|
||||||
secondaryValue: "${serversProvider.serverStatus.data!.stats.numDnsQueries > 0 ? doubleFormat((serversProvider.serverStatus.data!.stats.numReplacedSafebrowsing/serversProvider.serverStatus.data!.stats.numDnsQueries)*100, Platform.localeName) : 0}%",
|
type: 'topQueriedDomains',
|
||||||
color: Colors.green,
|
|
||||||
),
|
|
||||||
|
|
||||||
HomeChart(
|
|
||||||
data: serversProvider.serverStatus.data!.stats.replacedParental,
|
|
||||||
label: AppLocalizations.of(context)!.blockedAdultWebsites,
|
|
||||||
primaryValue: intFormat(serversProvider.serverStatus.data!.stats.numReplacedParental, Platform.localeName),
|
|
||||||
secondaryValue: "${serversProvider.serverStatus.data!.stats.numDnsQueries > 0 ? doubleFormat((serversProvider.serverStatus.data!.stats.numReplacedParental/serversProvider.serverStatus.data!.stats.numDnsQueries)*100, Platform.localeName) : 0}%",
|
|
||||||
color: Colors.orange,
|
|
||||||
),
|
|
||||||
|
|
||||||
TopItems(
|
|
||||||
label: AppLocalizations.of(context)!.topQueriedDomains,
|
|
||||||
data: serversProvider.serverStatus.data!.stats.topQueriedDomains,
|
|
||||||
type: 'topQueriedDomains',
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
|
||||||
child: Divider(
|
|
||||||
thickness: 1,
|
|
||||||
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.2),
|
|
||||||
),
|
),
|
||||||
),
|
Padding(
|
||||||
const SizedBox(height: 20),
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
|
child: Divider(
|
||||||
TopItems(
|
thickness: 1,
|
||||||
label: AppLocalizations.of(context)!.topBlockedDomains,
|
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.2),
|
||||||
data: serversProvider.serverStatus.data!.stats.topBlockedDomains,
|
),
|
||||||
type: 'topBlockedDomains',
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
|
||||||
child: Divider(
|
|
||||||
thickness: 1,
|
|
||||||
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.2),
|
|
||||||
),
|
),
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
|
|
||||||
TopItems(
|
const SizedBox(height: 20),
|
||||||
label: AppLocalizations.of(context)!.topClients,
|
|
||||||
data: serversProvider.serverStatus.data!.stats.topClients,
|
TopItems(
|
||||||
type: 'topClients',
|
label: AppLocalizations.of(context)!.topBlockedDomains,
|
||||||
clients: true,
|
data: serversProvider.serverStatus.data!.stats.topBlockedDomains,
|
||||||
),
|
type: 'topBlockedDomains',
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
|
child: Divider(
|
||||||
|
thickness: 1,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.2),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
TopItems(
|
||||||
|
label: AppLocalizations.of(context)!.topClients,
|
||||||
|
data: serversProvider.serverStatus.data!.stats.topClients,
|
||||||
|
type: 'topClients',
|
||||||
|
clients: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
if (width > 700) Column(
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Wrap(
|
||||||
|
alignment: WrapAlignment.center,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 16),
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
maxWidth: 500
|
||||||
|
),
|
||||||
|
child: TopItems(
|
||||||
|
label: AppLocalizations.of(context)!.topQueriedDomains,
|
||||||
|
data: serversProvider.serverStatus.data!.stats.topQueriedDomains,
|
||||||
|
type: 'topQueriedDomains',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 16),
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
maxWidth: 500
|
||||||
|
),
|
||||||
|
child: TopItems(
|
||||||
|
label: AppLocalizations.of(context)!.topBlockedDomains,
|
||||||
|
data: serversProvider.serverStatus.data!.stats.topBlockedDomains,
|
||||||
|
type: 'topBlockedDomains',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 16),
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
maxWidth: 500
|
||||||
|
),
|
||||||
|
child: TopItems(
|
||||||
|
label: AppLocalizations.of(context)!.topBlockedDomains,
|
||||||
|
data: serversProvider.serverStatus.data!.stats.topBlockedDomains,
|
||||||
|
type: 'topBlockedDomains',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,12 @@ import 'package:adguard_home_manager/services/http_requests.dart';
|
||||||
import 'package:adguard_home_manager/providers/servers_provider.dart';
|
import 'package:adguard_home_manager/providers/servers_provider.dart';
|
||||||
|
|
||||||
class ManagementModal extends StatefulWidget {
|
class ManagementModal extends StatefulWidget {
|
||||||
const ManagementModal({Key? key}) : super(key: key);
|
final bool dialog;
|
||||||
|
|
||||||
|
const ManagementModal({
|
||||||
|
Key? key,
|
||||||
|
required this.dialog
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ManagementModal> createState() => _ManagementModalState();
|
State<ManagementModal> createState() => _ManagementModalState();
|
||||||
|
@ -364,8 +369,112 @@ class _ManagementModalState extends State<ManagementModal> with SingleTickerProv
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return SafeArea(
|
Widget header() {
|
||||||
child: Container(
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 24),
|
||||||
|
child: Icon(
|
||||||
|
Icons.shield_rounded,
|
||||||
|
size: 24,
|
||||||
|
color: Theme.of(context).listTileTheme.iconColor
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
|
child: Text(
|
||||||
|
AppLocalizations.of(context)!.manageServer,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 24,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> toggles() {
|
||||||
|
return [
|
||||||
|
mainSwitch(),
|
||||||
|
Container(height: 10),
|
||||||
|
smallSwitch(
|
||||||
|
AppLocalizations.of(context)!.ruleFiltering,
|
||||||
|
Icons.filter_list_rounded,
|
||||||
|
serversProvider.serverStatus.data!.filteringEnabled,
|
||||||
|
(value) => updateBlocking(value: value, filter: 'filtering'),
|
||||||
|
serversProvider.protectionsManagementProcess.contains('filtering')
|
||||||
|
),
|
||||||
|
smallSwitch(
|
||||||
|
AppLocalizations.of(context)!.safeBrowsing,
|
||||||
|
Icons.vpn_lock_rounded,
|
||||||
|
serversProvider.serverStatus.data!.safeBrowsingEnabled,
|
||||||
|
(value) => updateBlocking(value: value, filter: 'safeBrowsing'),
|
||||||
|
serversProvider.protectionsManagementProcess.contains('safeBrowsing')
|
||||||
|
),
|
||||||
|
smallSwitch(
|
||||||
|
AppLocalizations.of(context)!.parentalFiltering,
|
||||||
|
Icons.block,
|
||||||
|
serversProvider.serverStatus.data!.parentalControlEnabled,
|
||||||
|
(value) => updateBlocking(value: value, filter: 'parentalControl'),
|
||||||
|
serversProvider.protectionsManagementProcess.contains('parentalControl')
|
||||||
|
),
|
||||||
|
smallSwitch(
|
||||||
|
AppLocalizations.of(context)!.safeSearch,
|
||||||
|
Icons.search_rounded,
|
||||||
|
serversProvider.serverStatus.data!.safeSearchEnabled,
|
||||||
|
(value) => updateBlocking(value: value, filter: 'safeSearch'),
|
||||||
|
serversProvider.protectionsManagementProcess.contains('safeSearch')
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (widget.dialog == true) {
|
||||||
|
return Dialog(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
maxWidth: 400
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Wrap(
|
||||||
|
children: [
|
||||||
|
header(),
|
||||||
|
...toggles()
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(24),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
child: Text(AppLocalizations.of(context)!.close),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (Platform.isIOS) const SizedBox(height: 16)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).colorScheme.surface,
|
color: Theme.of(context).colorScheme.surface,
|
||||||
borderRadius: const BorderRadius.only(
|
borderRadius: const BorderRadius.only(
|
||||||
|
@ -373,66 +482,18 @@ class _ManagementModalState extends State<ManagementModal> with SingleTickerProv
|
||||||
topRight: Radius.circular(28)
|
topRight: Radius.circular(28)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
child: Wrap(
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Flexible(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
child: SingleChildScrollView(
|
||||||
children: [
|
child: Wrap(
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
header(),
|
||||||
padding: const EdgeInsets.only(top: 24),
|
...toggles()
|
||||||
child: Icon(
|
|
||||||
Icons.shield_rounded,
|
|
||||||
size: 24,
|
|
||||||
color: Theme.of(context).listTileTheme.iconColor
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
||||||
child: Text(
|
|
||||||
AppLocalizations.of(context)!.manageServer,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 24,
|
|
||||||
color: Theme.of(context).colorScheme.onSurface,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
|
||||||
mainSwitch(),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
smallSwitch(
|
|
||||||
AppLocalizations.of(context)!.ruleFiltering,
|
|
||||||
Icons.filter_list_rounded,
|
|
||||||
serversProvider.serverStatus.data!.filteringEnabled,
|
|
||||||
(value) => updateBlocking(value: value, filter: 'filtering'),
|
|
||||||
serversProvider.protectionsManagementProcess.contains('filtering')
|
|
||||||
),
|
|
||||||
smallSwitch(
|
|
||||||
AppLocalizations.of(context)!.safeBrowsing,
|
|
||||||
Icons.vpn_lock_rounded,
|
|
||||||
serversProvider.serverStatus.data!.safeBrowsingEnabled,
|
|
||||||
(value) => updateBlocking(value: value, filter: 'safeBrowsing'),
|
|
||||||
serversProvider.protectionsManagementProcess.contains('safeBrowsing')
|
|
||||||
),
|
|
||||||
smallSwitch(
|
|
||||||
AppLocalizations.of(context)!.parentalFiltering,
|
|
||||||
Icons.block,
|
|
||||||
serversProvider.serverStatus.data!.parentalControlEnabled,
|
|
||||||
(value) => updateBlocking(value: value, filter: 'parentalControl'),
|
|
||||||
serversProvider.protectionsManagementProcess.contains('parentalControl')
|
|
||||||
),
|
|
||||||
smallSwitch(
|
|
||||||
AppLocalizations.of(context)!.safeSearch,
|
|
||||||
Icons.search_rounded,
|
|
||||||
serversProvider.serverStatus.data!.safeSearchEnabled,
|
|
||||||
(value) => updateBlocking(value: value, filter: 'safeSearch'),
|
|
||||||
serversProvider.protectionsManagementProcess.contains('safeSearch')
|
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(24),
|
padding: const EdgeInsets.all(24),
|
||||||
|
@ -449,7 +510,7 @@ class _ManagementModalState extends State<ManagementModal> with SingleTickerProv
|
||||||
if (Platform.isIOS) const SizedBox(height: 16)
|
if (Platform.isIOS) const SizedBox(height: 16)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -15,8 +15,12 @@ class ServerStatus extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(20),
|
padding: width > 700
|
||||||
|
? const EdgeInsets.only(left: 20, right: 20, bottom: 20)
|
||||||
|
: const EdgeInsets.all(20),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
|
@ -29,11 +33,11 @@ class ServerStatus extends StatelessWidget {
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 140,
|
height: width > 700 ? 70 : 140,
|
||||||
child: GridView(
|
child: GridView(
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
crossAxisCount: 2,
|
crossAxisCount: width > 700 ? 4 : 2,
|
||||||
crossAxisSpacing: 10,
|
crossAxisSpacing: 10,
|
||||||
mainAxisSpacing: 10,
|
mainAxisSpacing: 10,
|
||||||
mainAxisExtent: 65
|
mainAxisExtent: 65
|
||||||
|
|
|
@ -44,13 +44,15 @@ class StatusBox extends StatelessWidget {
|
||||||
: Colors.grey.computeLuminance() > 0.5 ? Colors.black : Colors.white,
|
: Colors.grey.computeLuminance() > 0.5 ? Colors.black : Colors.white,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Text(
|
Flexible(
|
||||||
label,
|
child: Text(
|
||||||
style: TextStyle(
|
label,
|
||||||
color: appConfigProvider.useThemeColorForStatus == true
|
style: TextStyle(
|
||||||
? Theme.of(context).colorScheme.primary.computeLuminance() > 0.5 ? Colors.black : Colors.white
|
color: appConfigProvider.useThemeColorForStatus == true
|
||||||
: Colors.grey.computeLuminance() > 0.5 ? Colors.black : Colors.white,
|
? Theme.of(context).colorScheme.primary.computeLuminance() > 0.5 ? Colors.black : Colors.white
|
||||||
fontWeight: FontWeight.w500
|
: Colors.grey.computeLuminance() > 0.5 ? Colors.black : Colors.white,
|
||||||
|
fontWeight: FontWeight.w500
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
// ignore_for_file: use_build_context_synchronously
|
// ignore_for_file: use_build_context_synchronously
|
||||||
|
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:adguard_home_manager/screens/top_items/top_items_modal.dart';
|
||||||
|
import 'package:animations/animations.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
@ -35,6 +39,8 @@ class TopItems extends StatelessWidget {
|
||||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||||
final logsProvider = Provider.of<LogsProvider>(context);
|
final logsProvider = Provider.of<LogsProvider>(context);
|
||||||
|
|
||||||
|
final width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
bool? getIsBlocked() {
|
bool? getIsBlocked() {
|
||||||
if (type == 'topBlockedDomains') {
|
if (type == 'topBlockedDomains') {
|
||||||
return true;
|
return true;
|
||||||
|
@ -266,16 +272,32 @@ class TopItems extends StatelessWidget {
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.of(context).push(
|
onPressed: () => {
|
||||||
MaterialPageRoute(
|
if (width > 700 || !(Platform.isAndroid || Platform.isIOS)) {
|
||||||
builder: (context) => TopItemsScreen(
|
showDialog(
|
||||||
type: type,
|
context: context,
|
||||||
title: label,
|
barrierDismissible: false,
|
||||||
isClient: clients,
|
builder: (context) => TopItemsModal(
|
||||||
data: generateData(),
|
type: type,
|
||||||
|
title: label,
|
||||||
|
isClient: clients,
|
||||||
|
data: generateData(),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
),
|
else {
|
||||||
|
Navigator.of(context).push(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => TopItemsScreen(
|
||||||
|
type: type,
|
||||||
|
title: label,
|
||||||
|
isClient: clients,
|
||||||
|
data: generateData(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
|
|
|
@ -8,10 +8,12 @@ import 'package:adguard_home_manager/providers/logs_provider.dart';
|
||||||
|
|
||||||
class ClientsModal extends StatefulWidget {
|
class ClientsModal extends StatefulWidget {
|
||||||
final List<String>? value;
|
final List<String>? value;
|
||||||
|
final bool dialog;
|
||||||
|
|
||||||
const ClientsModal({
|
const ClientsModal({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.value
|
required this.value,
|
||||||
|
required this.dialog
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -94,44 +96,36 @@ class _ClientsModalState extends State<ClientsModal> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return Container(
|
Widget content() {
|
||||||
height: height >= (logsProvider.clients!.length*64) == true
|
return Column(
|
||||||
? logsProvider.clients!.length*64
|
mainAxisSize: MainAxisSize.min,
|
||||||
: height-50,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: const BorderRadius.only(
|
|
||||||
topLeft: Radius.circular(28),
|
|
||||||
topRight: Radius.circular(28)
|
|
||||||
),
|
|
||||||
color: Theme.of(context).dialogBackgroundColor
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Column(
|
||||||
padding: const EdgeInsets.only(
|
children: [
|
||||||
top: 24,
|
Padding(
|
||||||
bottom: 16,
|
padding: const EdgeInsets.only(
|
||||||
),
|
top: 24,
|
||||||
child: Icon(
|
bottom: 16,
|
||||||
Icons.smartphone_rounded,
|
),
|
||||||
size: 24,
|
child: Icon(
|
||||||
color: Theme.of(context).listTileTheme.iconColor
|
Icons.smartphone_rounded,
|
||||||
),
|
size: 24,
|
||||||
|
color: Theme.of(context).listTileTheme.iconColor
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context)!.clients,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
Text(
|
Flexible(
|
||||||
AppLocalizations.of(context)!.clients,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 24,
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
color: Theme.of(context).colorScheme.onSurface
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Expanded(
|
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
physics: height >= (logsProvider.clients!.length*64) == true
|
|
||||||
? const NeverScrollableScrollPhysics()
|
|
||||||
: null,
|
|
||||||
itemCount: logsProvider.clients!.length,
|
itemCount: logsProvider.clients!.length,
|
||||||
itemBuilder: (context, index) => listItem(
|
itemBuilder: (context, index) => listItem(
|
||||||
label: logsProvider.clients![index].ip,
|
label: logsProvider.clients![index].ip,
|
||||||
|
@ -150,7 +144,7 @@ class _ClientsModalState extends State<ClientsModal> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
),
|
)
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(24),
|
padding: const EdgeInsets.all(24),
|
||||||
|
@ -176,7 +170,35 @@ class _ClientsModalState extends State<ClientsModal> {
|
||||||
),
|
),
|
||||||
if (Platform.isIOS) const SizedBox(height: 16)
|
if (Platform.isIOS) const SizedBox(height: 16)
|
||||||
],
|
],
|
||||||
),
|
);
|
||||||
);
|
}
|
||||||
|
|
||||||
|
if (widget.dialog == true) {
|
||||||
|
return Dialog(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
maxWidth: 500
|
||||||
|
),
|
||||||
|
child: content()
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
maxHeight: height-50
|
||||||
|
),
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: const BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(28),
|
||||||
|
topRight: Radius.circular(28)
|
||||||
|
),
|
||||||
|
color: Theme.of(context).dialogBackgroundColor
|
||||||
|
),
|
||||||
|
child: content()
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -8,10 +8,12 @@ import 'package:adguard_home_manager/providers/logs_provider.dart';
|
||||||
|
|
||||||
class FilterStatusModal extends StatefulWidget {
|
class FilterStatusModal extends StatefulWidget {
|
||||||
final String value;
|
final String value;
|
||||||
|
final bool dialog;
|
||||||
|
|
||||||
const FilterStatusModal({
|
const FilterStatusModal({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.value
|
required this.value,
|
||||||
|
required this.dialog
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -31,8 +33,6 @@ class _FilterStatusModalState extends State<FilterStatusModal> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final logsProvider = Provider.of<LogsProvider>(context);
|
final logsProvider = Provider.of<LogsProvider>(context);
|
||||||
|
|
||||||
final height = MediaQuery.of(context).size.height;
|
|
||||||
|
|
||||||
void apply() async {
|
void apply() async {
|
||||||
logsProvider.setSelectedResultStatus(selectedResultStatus);
|
logsProvider.setSelectedResultStatus(selectedResultStatus);
|
||||||
|
|
||||||
|
@ -83,95 +83,94 @@ class _FilterStatusModalState extends State<FilterStatusModal> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Container(
|
Widget content() {
|
||||||
height: height >= (Platform.isIOS ? 736 : 720) == true
|
return Column(
|
||||||
? (Platform.isIOS ? 736 : 720)
|
mainAxisSize: MainAxisSize.min,
|
||||||
: height-25,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: const BorderRadius.only(
|
|
||||||
topLeft: Radius.circular(28),
|
|
||||||
topRight: Radius.circular(28)
|
|
||||||
),
|
|
||||||
color: Theme.of(context).dialogBackgroundColor
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Flexible(
|
||||||
padding: const EdgeInsets.only(
|
child: SingleChildScrollView(
|
||||||
top: 24,
|
child: Wrap(
|
||||||
bottom: 16,
|
children: [
|
||||||
),
|
Row(
|
||||||
child: Icon(
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
Icons.shield_rounded,
|
children: [
|
||||||
size: 24,
|
Column(
|
||||||
color: Theme.of(context).listTileTheme.iconColor
|
children: [
|
||||||
),
|
Padding(
|
||||||
),
|
padding: const EdgeInsets.only(
|
||||||
Text(
|
top: 24,
|
||||||
AppLocalizations.of(context)!.responseStatus,
|
bottom: 16,
|
||||||
style: TextStyle(
|
),
|
||||||
fontSize: 24,
|
child: Icon(
|
||||||
fontWeight: FontWeight.w400,
|
Icons.shield_rounded,
|
||||||
color: Theme.of(context).colorScheme.onSurface
|
size: 24,
|
||||||
),
|
color: Theme.of(context).listTileTheme.iconColor
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
),
|
||||||
Expanded(
|
Text(
|
||||||
child: ListView(
|
AppLocalizations.of(context)!.responseStatus,
|
||||||
physics: height >= 720 == true
|
style: TextStyle(
|
||||||
? const NeverScrollableScrollPhysics()
|
fontSize: 24,
|
||||||
: null,
|
fontWeight: FontWeight.w400,
|
||||||
children: [
|
color: Theme.of(context).colorScheme.onSurface
|
||||||
filterStatusListItem(
|
),
|
||||||
id: "all",
|
),
|
||||||
icon: Icons.shield_rounded,
|
],
|
||||||
label: AppLocalizations.of(context)!.all,
|
)
|
||||||
onChanged: (value) => setState(() => selectedResultStatus = value!)
|
],
|
||||||
),
|
),
|
||||||
filterStatusListItem(
|
Container(height: 16),
|
||||||
id: "filtered",
|
filterStatusListItem(
|
||||||
icon: Icons.shield_rounded,
|
id: "all",
|
||||||
label: AppLocalizations.of(context)!.filtered,
|
icon: Icons.shield_rounded,
|
||||||
onChanged: (value) => setState(() => selectedResultStatus = value!)
|
label: AppLocalizations.of(context)!.all,
|
||||||
),
|
onChanged: (value) => setState(() => selectedResultStatus = value!)
|
||||||
filterStatusListItem(
|
),
|
||||||
id: "processed",
|
filterStatusListItem(
|
||||||
icon: Icons.verified_user_rounded,
|
id: "filtered",
|
||||||
label: AppLocalizations.of(context)!.processedRow,
|
icon: Icons.shield_rounded,
|
||||||
onChanged: (value) => setState(() => selectedResultStatus = value!)
|
label: AppLocalizations.of(context)!.filtered,
|
||||||
),
|
onChanged: (value) => setState(() => selectedResultStatus = value!)
|
||||||
filterStatusListItem(
|
),
|
||||||
id: "whitelisted",
|
filterStatusListItem(
|
||||||
icon: Icons.verified_user_rounded,
|
id: "processed",
|
||||||
label: AppLocalizations.of(context)!.processedWhitelistRow,
|
icon: Icons.verified_user_rounded,
|
||||||
onChanged: (value) => setState(() => selectedResultStatus = value!)
|
label: AppLocalizations.of(context)!.processedRow,
|
||||||
),
|
onChanged: (value) => setState(() => selectedResultStatus = value!)
|
||||||
filterStatusListItem(
|
),
|
||||||
id: "blocked",
|
filterStatusListItem(
|
||||||
icon: Icons.gpp_bad_rounded,
|
id: "whitelisted",
|
||||||
label: AppLocalizations.of(context)!.blocked,
|
icon: Icons.verified_user_rounded,
|
||||||
onChanged: (value) => setState(() => selectedResultStatus = value!)
|
label: AppLocalizations.of(context)!.processedWhitelistRow,
|
||||||
),
|
onChanged: (value) => setState(() => selectedResultStatus = value!)
|
||||||
filterStatusListItem(
|
),
|
||||||
id: "blocked_safebrowsing",
|
filterStatusListItem(
|
||||||
icon: Icons.gpp_bad_rounded,
|
id: "blocked",
|
||||||
label: AppLocalizations.of(context)!.blockedSafeBrowsingRow,
|
icon: Icons.gpp_bad_rounded,
|
||||||
onChanged: (value) => setState(() => selectedResultStatus = value!)
|
label: AppLocalizations.of(context)!.blocked,
|
||||||
),
|
onChanged: (value) => setState(() => selectedResultStatus = value!)
|
||||||
filterStatusListItem(
|
),
|
||||||
id: "blocked_parental",
|
filterStatusListItem(
|
||||||
icon: Icons.gpp_bad_rounded,
|
id: "blocked_safebrowsing",
|
||||||
label: AppLocalizations.of(context)!.blockedParentalRow,
|
icon: Icons.gpp_bad_rounded,
|
||||||
onChanged: (value) => setState(() => selectedResultStatus = value!)
|
label: AppLocalizations.of(context)!.blockedSafeBrowsingRow,
|
||||||
),
|
onChanged: (value) => setState(() => selectedResultStatus = value!)
|
||||||
filterStatusListItem(
|
),
|
||||||
id: "safe_search",
|
filterStatusListItem(
|
||||||
icon: Icons.gpp_bad_rounded,
|
id: "blocked_parental",
|
||||||
label: AppLocalizations.of(context)!.blockedSafeSearchRow,
|
icon: Icons.gpp_bad_rounded,
|
||||||
onChanged: (value) => setState(() => selectedResultStatus = value!)
|
label: AppLocalizations.of(context)!.blockedParentalRow,
|
||||||
),
|
onChanged: (value) => setState(() => selectedResultStatus = value!)
|
||||||
|
),
|
||||||
],
|
filterStatusListItem(
|
||||||
|
id: "safe_search",
|
||||||
|
icon: Icons.gpp_bad_rounded,
|
||||||
|
label: AppLocalizations.of(context)!.blockedSafeSearchRow,
|
||||||
|
onChanged: (value) => setState(() => selectedResultStatus = value!)
|
||||||
|
),
|
||||||
|
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
|
@ -188,7 +187,30 @@ class _FilterStatusModalState extends State<FilterStatusModal> {
|
||||||
),
|
),
|
||||||
if (Platform.isIOS) const SizedBox(height: 16)
|
if (Platform.isIOS) const SizedBox(height: 16)
|
||||||
],
|
],
|
||||||
),
|
);
|
||||||
);
|
}
|
||||||
|
|
||||||
|
if (widget.dialog == true) {
|
||||||
|
return Dialog(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
maxWidth: 400
|
||||||
|
),
|
||||||
|
child: content()
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: const BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(28),
|
||||||
|
topRight: Radius.circular(28)
|
||||||
|
),
|
||||||
|
color: Theme.of(context).dialogBackgroundColor
|
||||||
|
),
|
||||||
|
child: content()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -19,10 +19,12 @@ import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
||||||
|
|
||||||
class LogDetailsScreen extends StatelessWidget {
|
class LogDetailsScreen extends StatelessWidget {
|
||||||
final Log log;
|
final Log log;
|
||||||
|
final bool dialog;
|
||||||
|
|
||||||
const LogDetailsScreen({
|
const LogDetailsScreen({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.log
|
required this.log,
|
||||||
|
required this.dialog
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -105,25 +107,8 @@ class LogDetailsScreen extends StatelessWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Scaffold(
|
Widget content() {
|
||||||
appBar: AppBar(
|
return ListView(
|
||||||
title: Text(AppLocalizations.of(context)!.logDetails),
|
|
||||||
actions: [
|
|
||||||
IconButton(
|
|
||||||
onPressed: () => blockUnblock(log, getFilteredStatus(context, appConfigProvider, log.reason, true)['filtered'] == true ? 'unblock' : 'block'),
|
|
||||||
icon: Icon(
|
|
||||||
getFilteredStatus(context, appConfigProvider, log.reason, true)['filtered'] == true
|
|
||||||
? Icons.check_circle_rounded
|
|
||||||
: Icons.block
|
|
||||||
),
|
|
||||||
tooltip: getFilteredStatus(context, appConfigProvider, log.reason, true)['filtered'] == true
|
|
||||||
? AppLocalizations.of(context)!.unblockDomain
|
|
||||||
: AppLocalizations.of(context)!.blockDomain,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 10)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
body: ListView(
|
|
||||||
children: [
|
children: [
|
||||||
SectionLabel(label: AppLocalizations.of(context)!.status),
|
SectionLabel(label: AppLocalizations.of(context)!.status),
|
||||||
LogListTile(
|
LogListTile(
|
||||||
|
@ -247,7 +232,87 @@ class LogDetailsScreen extends StatelessWidget {
|
||||||
)).toList()
|
)).toList()
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
),
|
);
|
||||||
);
|
}
|
||||||
|
|
||||||
|
if (dialog) {
|
||||||
|
return Dialog(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
maxWidth: 500
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
icon: const Icon(Icons.clear_rounded)
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context)!.logDetails,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 22
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => blockUnblock(log, getFilteredStatus(context, appConfigProvider, log.reason, true)['filtered'] == true ? 'unblock' : 'block'),
|
||||||
|
icon: Icon(
|
||||||
|
getFilteredStatus(context, appConfigProvider, log.reason, true)['filtered'] == true
|
||||||
|
? Icons.check_circle_rounded
|
||||||
|
: Icons.block
|
||||||
|
),
|
||||||
|
tooltip: getFilteredStatus(context, appConfigProvider, log.reason, true)['filtered'] == true
|
||||||
|
? AppLocalizations.of(context)!.unblockDomain
|
||||||
|
: AppLocalizations.of(context)!.blockDomain,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
|
child: content(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(AppLocalizations.of(context)!.logDetails),
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => blockUnblock(log, getFilteredStatus(context, appConfigProvider, log.reason, true)['filtered'] == true ? 'unblock' : 'block'),
|
||||||
|
icon: Icon(
|
||||||
|
getFilteredStatus(context, appConfigProvider, log.reason, true)['filtered'] == true
|
||||||
|
? Icons.check_circle_rounded
|
||||||
|
: Icons.block
|
||||||
|
),
|
||||||
|
tooltip: getFilteredStatus(context, appConfigProvider, log.reason, true)['filtered'] == true
|
||||||
|
? AppLocalizations.of(context)!.unblockDomain
|
||||||
|
: AppLocalizations.of(context)!.blockDomain,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: content()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,10 +1,10 @@
|
||||||
// ignore_for_file: use_build_context_synchronously
|
// ignore_for_file: use_build_context_synchronously
|
||||||
|
|
||||||
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';
|
||||||
|
|
||||||
import 'package:adguard_home_manager/screens/home/top_items_options_modal.dart';
|
import 'package:adguard_home_manager/screens/home/top_items_options_modal.dart';
|
||||||
import 'package:adguard_home_manager/screens/logs/log_details_screen.dart';
|
|
||||||
|
|
||||||
import 'package:adguard_home_manager/functions/copy_clipboard.dart';
|
import 'package:adguard_home_manager/functions/copy_clipboard.dart';
|
||||||
import 'package:adguard_home_manager/functions/block_unblock_domain.dart';
|
import 'package:adguard_home_manager/functions/block_unblock_domain.dart';
|
||||||
|
@ -18,12 +18,18 @@ class LogTile extends StatelessWidget {
|
||||||
final Log log;
|
final Log log;
|
||||||
final int length;
|
final int length;
|
||||||
final int index;
|
final int index;
|
||||||
|
final bool? isLogSelected;
|
||||||
|
final void Function(Log) onLogTap;
|
||||||
|
final bool? useAlwaysNormalTile;
|
||||||
|
|
||||||
const LogTile({
|
const LogTile({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.log,
|
required this.log,
|
||||||
required this.length,
|
required this.length,
|
||||||
required this.index
|
required this.index,
|
||||||
|
this.isLogSelected,
|
||||||
|
required this.onLogTap,
|
||||||
|
this.useAlwaysNormalTile
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -38,7 +44,7 @@ class LogTile extends StatelessWidget {
|
||||||
required String text
|
required String text
|
||||||
}) {
|
}) {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: 70,
|
width: 80,
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
|
@ -95,133 +101,250 @@ class LogTile extends StatelessWidget {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Material(
|
if (width > 1100 && !(useAlwaysNormalTile == true)) {
|
||||||
color: Colors.transparent,
|
return Padding(
|
||||||
child: InkWell(
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
onTap: () => Navigator.push(context, MaterialPageRoute(
|
child: Material(
|
||||||
builder: (context) => LogDetailsScreen(log: log)
|
color: Colors.transparent,
|
||||||
)),
|
borderRadius: BorderRadius.circular(28),
|
||||||
onLongPress: () => openOptionsModal(log),
|
child: InkWell(
|
||||||
child: Container(
|
borderRadius: BorderRadius.circular(28),
|
||||||
width: double.maxFinite,
|
onTap: () => onLogTap(log),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
child: Container(
|
||||||
child: Row(
|
width: double.maxFinite,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
children: [
|
decoration: BoxDecoration(
|
||||||
SizedBox(
|
borderRadius: BorderRadius.circular(28),
|
||||||
width: width-130,
|
color: isLogSelected == true
|
||||||
child: Column(
|
? Theme.of(context).colorScheme.primaryContainer
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
: null
|
||||||
children: [
|
),
|
||||||
Text(
|
child: Row(
|
||||||
log.question.name,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
style: TextStyle(
|
children: [
|
||||||
fontSize: 16,
|
Flexible(
|
||||||
height: 1.5,
|
child: Row(
|
||||||
fontWeight: FontWeight.w400,
|
mainAxisSize: MainAxisSize.min,
|
||||||
color: Theme.of(context).colorScheme.onSurface
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 5),
|
|
||||||
if (log.client.length <= 15 && appConfigProvider.showNameTimeLogs == false) Row(
|
|
||||||
children: [
|
children: [
|
||||||
...[
|
Flexible(
|
||||||
Icon(
|
child: Column(
|
||||||
Icons.smartphone_rounded,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
size: 16,
|
children: [
|
||||||
color: Theme.of(context).listTileTheme.textColor,
|
Text(
|
||||||
),
|
log.question.name,
|
||||||
const SizedBox(width: 5),
|
style: TextStyle(
|
||||||
Flexible(
|
fontSize: 16,
|
||||||
child: Text(
|
fontWeight: FontWeight.w400,
|
||||||
log.client,
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
overflow: TextOverflow.ellipsis,
|
),
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context).listTileTheme.textColor,
|
|
||||||
fontSize: 14,
|
|
||||||
height: 1.4,
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 5),
|
||||||
)
|
if (log.client.length <= 15 && appConfigProvider.showNameTimeLogs == false) Row(
|
||||||
],
|
children: [
|
||||||
const SizedBox(width: 15),
|
...[
|
||||||
...[
|
Icon(
|
||||||
Icon(
|
Icons.smartphone_rounded,
|
||||||
Icons.schedule_rounded,
|
size: 16,
|
||||||
size: 16,
|
color: Theme.of(context).listTileTheme.textColor,
|
||||||
color: Theme.of(context).listTileTheme.textColor,
|
),
|
||||||
),
|
const SizedBox(width: 5),
|
||||||
const SizedBox(width: 5),
|
Flexible(
|
||||||
Flexible(
|
child: Text(
|
||||||
child: Text(
|
log.client,
|
||||||
convertTimestampLocalTimezone(log.time, 'HH:mm:ss'),
|
overflow: TextOverflow.ellipsis,
|
||||||
overflow: TextOverflow.ellipsis,
|
style: TextStyle(
|
||||||
style: TextStyle(
|
color: Theme.of(context).listTileTheme.textColor,
|
||||||
color: Theme.of(context).listTileTheme.textColor,
|
fontSize: 14,
|
||||||
fontSize: 13
|
height: 1.4,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
const SizedBox(width: 15),
|
||||||
|
...[
|
||||||
|
Icon(
|
||||||
|
Icons.schedule_rounded,
|
||||||
|
size: 16,
|
||||||
|
color: Theme.of(context).listTileTheme.textColor,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 5),
|
||||||
|
Flexible(
|
||||||
|
child: Text(
|
||||||
|
convertTimestampLocalTimezone(log.time, 'HH:mm:ss'),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).listTileTheme.textColor,
|
||||||
|
fontSize: 13
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
if (log.client.length > 15 || appConfigProvider.showNameTimeLogs == true) Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.smartphone_rounded,
|
||||||
|
size: 16,
|
||||||
|
color: Theme.of(context).listTileTheme.textColor,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 15),
|
||||||
|
Flexible(
|
||||||
|
child: Text(
|
||||||
|
log.client,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).listTileTheme.textColor,
|
||||||
|
fontSize: 13
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (appConfigProvider.showNameTimeLogs == true && log.clientInfo!.name != '') ...[
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.badge_rounded,
|
||||||
|
size: 16,
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 15),
|
||||||
|
Flexible(
|
||||||
|
child: Text(
|
||||||
|
log.clientInfo!.name,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).listTileTheme.textColor,
|
||||||
|
fontSize: 13
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.schedule_rounded,
|
||||||
|
size: 16,
|
||||||
|
color: Theme.of(context).listTileTheme.textColor,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 15),
|
||||||
|
SizedBox(
|
||||||
|
child: Text(
|
||||||
|
convertTimestampLocalTimezone(log.time, 'HH:mm:ss'),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).listTileTheme.textColor,
|
||||||
|
fontSize: 13
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (appConfigProvider.showNameTimeLogs == true && log.elapsedMs != '') ...[
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.timer,
|
||||||
|
size: 16,
|
||||||
|
color: Theme.of(context).listTileTheme.textColor,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 15),
|
||||||
|
SizedBox(
|
||||||
|
child: Text(
|
||||||
|
"${double.parse(log.elapsedMs).toStringAsFixed(2)} ms",
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).listTileTheme.textColor,
|
||||||
|
fontSize: 13
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (log.client.length > 15 || appConfigProvider.showNameTimeLogs == true) Column(
|
),
|
||||||
children: [
|
generateLogStatus()
|
||||||
Row(
|
],
|
||||||
children: [
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () => onLogTap(log),
|
||||||
|
onLongPress: () => openOptionsModal(log),
|
||||||
|
child: Container(
|
||||||
|
width: double.maxFinite,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
log.question.name,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
height: 1.5,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 5),
|
||||||
|
if (log.client.length <= 15 && appConfigProvider.showNameTimeLogs == false) Row(
|
||||||
|
children: [
|
||||||
|
...[
|
||||||
Icon(
|
Icon(
|
||||||
Icons.smartphone_rounded,
|
Icons.smartphone_rounded,
|
||||||
size: 16,
|
size: 16,
|
||||||
color: Theme.of(context).listTileTheme.textColor,
|
color: Theme.of(context).listTileTheme.textColor,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 15),
|
const SizedBox(width: 5),
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Text(
|
child: Text(
|
||||||
log.client,
|
log.client,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(context).listTileTheme.textColor,
|
color: Theme.of(context).listTileTheme.textColor,
|
||||||
fontSize: 13
|
fontSize: 14,
|
||||||
|
height: 1.4,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
const SizedBox(width: 15),
|
||||||
if (appConfigProvider.showNameTimeLogs == true && log.clientInfo!.name != '') ...[
|
...[
|
||||||
const SizedBox(height: 10),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
Icons.badge_rounded,
|
|
||||||
size: 16,
|
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 15),
|
|
||||||
Flexible(
|
|
||||||
child: Text(
|
|
||||||
log.clientInfo!.name,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context).listTileTheme.textColor,
|
|
||||||
fontSize: 13
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Icon(
|
Icon(
|
||||||
Icons.schedule_rounded,
|
Icons.schedule_rounded,
|
||||||
size: 16,
|
size: 16,
|
||||||
color: Theme.of(context).listTileTheme.textColor,
|
color: Theme.of(context).listTileTheme.textColor,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 15),
|
const SizedBox(width: 5),
|
||||||
SizedBox(
|
Flexible(
|
||||||
child: Text(
|
child: Text(
|
||||||
convertTimestampLocalTimezone(log.time, 'HH:mm:ss'),
|
convertTimestampLocalTimezone(log.time, 'HH:mm:ss'),
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
|
@ -230,22 +353,23 @@ class LogTile extends StatelessWidget {
|
||||||
fontSize: 13
|
fontSize: 13
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
],
|
||||||
if (appConfigProvider.showNameTimeLogs == true && log.elapsedMs != '') ...[
|
),
|
||||||
const SizedBox(height: 10),
|
if (log.client.length > 15 || appConfigProvider.showNameTimeLogs == true) Column(
|
||||||
|
children: [
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
Icons.timer,
|
Icons.smartphone_rounded,
|
||||||
size: 16,
|
size: 16,
|
||||||
color: Theme.of(context).listTileTheme.textColor,
|
color: Theme.of(context).listTileTheme.textColor,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 15),
|
const SizedBox(width: 15),
|
||||||
SizedBox(
|
Flexible(
|
||||||
child: Text(
|
child: Text(
|
||||||
"${double.parse(log.elapsedMs).toStringAsFixed(2)} ms",
|
log.client,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(context).listTileTheme.textColor,
|
color: Theme.of(context).listTileTheme.textColor,
|
||||||
|
@ -255,18 +379,85 @@ class LogTile extends StatelessWidget {
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
if (appConfigProvider.showNameTimeLogs == true && log.clientInfo!.name != '') ...[
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.badge_rounded,
|
||||||
|
size: 16,
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 15),
|
||||||
|
Flexible(
|
||||||
|
child: Text(
|
||||||
|
log.clientInfo!.name,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).listTileTheme.textColor,
|
||||||
|
fontSize: 13
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.schedule_rounded,
|
||||||
|
size: 16,
|
||||||
|
color: Theme.of(context).listTileTheme.textColor,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 15),
|
||||||
|
SizedBox(
|
||||||
|
child: Text(
|
||||||
|
convertTimestampLocalTimezone(log.time, 'HH:mm:ss'),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).listTileTheme.textColor,
|
||||||
|
fontSize: 13
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (appConfigProvider.showNameTimeLogs == true && log.elapsedMs != '') ...[
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.timer,
|
||||||
|
size: 16,
|
||||||
|
color: Theme.of(context).listTileTheme.textColor,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 15),
|
||||||
|
SizedBox(
|
||||||
|
child: Text(
|
||||||
|
"${double.parse(log.elapsedMs).toStringAsFixed(2)} ms",
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).listTileTheme.textColor,
|
||||||
|
fontSize: 13
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
],
|
],
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(width: 10),
|
||||||
const SizedBox(width: 10),
|
generateLogStatus()
|
||||||
generateLogStatus()
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
// 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: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';
|
||||||
|
@ -7,6 +9,7 @@ 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_filters_modal.dart';
|
||||||
import 'package:adguard_home_manager/screens/logs/logs_config_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_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/functions/snackbar.dart';
|
||||||
import 'package:adguard_home_manager/classes/process_modal.dart';
|
import 'package:adguard_home_manager/classes/process_modal.dart';
|
||||||
|
@ -64,6 +67,8 @@ class _LogsWidgetState extends State<LogsWidget> {
|
||||||
|
|
||||||
bool showDivider = true;
|
bool showDivider = true;
|
||||||
|
|
||||||
|
Log? selectedLog;
|
||||||
|
|
||||||
Future fetchLogs({
|
Future fetchLogs({
|
||||||
int? inOffset,
|
int? inOffset,
|
||||||
bool? loadingMore,
|
bool? loadingMore,
|
||||||
|
@ -188,6 +193,8 @@ class _LogsWidgetState extends State<LogsWidget> {
|
||||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||||
final logsProvider = Provider.of<LogsProvider>(context);
|
final logsProvider = Provider.of<LogsProvider>(context);
|
||||||
|
|
||||||
|
final width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
void updateConfig(Map<String, dynamic> data) async {
|
void updateConfig(Map<String, dynamic> data) async {
|
||||||
ProcessModal processModal = ProcessModal(context: context);
|
ProcessModal processModal = ProcessModal(context: context);
|
||||||
processModal.open(AppLocalizations.of(context)!.updatingSettings);
|
processModal.open(AppLocalizations.of(context)!.updatingSettings);
|
||||||
|
@ -252,12 +259,25 @@ class _LogsWidgetState extends State<LogsWidget> {
|
||||||
|
|
||||||
|
|
||||||
void openFilersModal() {
|
void openFilersModal() {
|
||||||
showModalBottomSheet(
|
if (width > 700 || !(Platform.isAndroid || Platform.isIOS)) {
|
||||||
context: context,
|
showDialog(
|
||||||
builder: (context) => const LogsFiltersModal(),
|
context: context,
|
||||||
backgroundColor: Colors.transparent,
|
builder: (context) => const LogsFiltersModal(
|
||||||
isScrollControlled: true
|
dialog: true,
|
||||||
);
|
),
|
||||||
|
barrierDismissible: false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => const LogsFiltersModal(
|
||||||
|
dialog: false,
|
||||||
|
),
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
isScrollControlled: true
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final Map<String, String> translatedString = {
|
final Map<String, String> translatedString = {
|
||||||
|
@ -319,6 +339,18 @@ class _LogsWidgetState extends State<LogsWidget> {
|
||||||
log: logsProvider.logsData!.data[index],
|
log: logsProvider.logsData!.data[index],
|
||||||
index: index,
|
index: index,
|
||||||
length: logsProvider.logsData!.data.length,
|
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);
|
||||||
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -383,161 +415,210 @@ class _LogsWidgetState extends State<LogsWidget> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Scaffold(
|
Widget logsScreen() {
|
||||||
appBar: AppBar(
|
return Scaffold(
|
||||||
title: Text(AppLocalizations.of(context)!.logs),
|
appBar: AppBar(
|
||||||
centerTitle: false,
|
title: Text(AppLocalizations.of(context)!.logs),
|
||||||
actions: [
|
centerTitle: false,
|
||||||
logsProvider.loadStatus == 1
|
actions: [
|
||||||
? IconButton(
|
if (!(Platform.isAndroid || Platform.isIOS)) IconButton(
|
||||||
onPressed: openFilersModal,
|
onPressed: () => fetchLogs(inOffset: 0),
|
||||||
icon: const Icon(Icons.filter_list_rounded)
|
icon: const Icon(Icons.refresh_rounded),
|
||||||
)
|
tooltip: AppLocalizations.of(context)!.refresh,
|
||||||
: const SizedBox(),
|
),
|
||||||
IconButton(
|
logsProvider.loadStatus == 1
|
||||||
onPressed: () => {
|
? IconButton(
|
||||||
showModalBottomSheet(
|
onPressed: openFilersModal,
|
||||||
context: context,
|
icon: const Icon(Icons.filter_list_rounded),
|
||||||
builder: (context) => LogsConfigModal(
|
tooltip: AppLocalizations.of(context)!.filters,
|
||||||
onConfirm: updateConfig,
|
)
|
||||||
onClear: clearQueries,
|
: const SizedBox(),
|
||||||
),
|
IconButton(
|
||||||
backgroundColor: Colors.transparent,
|
tooltip: AppLocalizations.of(context)!.settings,
|
||||||
isScrollControlled: true
|
onPressed: () => {
|
||||||
)
|
if (width > 700 || !(Platform.isAndroid || Platform.isIOS)) {
|
||||||
},
|
showDialog(
|
||||||
icon: const Icon(Icons.settings)
|
context: context,
|
||||||
),
|
builder: (context) => LogsConfigModal(
|
||||||
const SizedBox(width: 5),
|
onConfirm: updateConfig,
|
||||||
],
|
onClear: clearQueries,
|
||||||
bottom: logsProvider.appliedFilters.searchText != null || logsProvider.appliedFilters.selectedResultStatus != 'all' || logsProvider.appliedFilters.clients != null
|
dialog: true,
|
||||||
? PreferredSize(
|
),
|
||||||
preferredSize: const Size(double.maxFinite, 50),
|
barrierDismissible: false
|
||||||
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(
|
else {
|
||||||
scrollDirection: Axis.horizontal,
|
showModalBottomSheet(
|
||||||
children: [
|
context: context,
|
||||||
if (logsProvider.appliedFilters.searchText != null) ...[
|
builder: (context) => LogsConfigModal(
|
||||||
|
onConfirm: updateConfig,
|
||||||
|
onClear: clearQueries,
|
||||||
|
dialog: false,
|
||||||
|
),
|
||||||
|
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, 50),
|
||||||
|
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.link_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);
|
||||||
|
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');
|
||||||
|
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);
|
||||||
|
fetchLogs(
|
||||||
|
inOffset: 0,
|
||||||
|
responseStatus: logsProvider.appliedFilters.selectedResultStatus
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
const SizedBox(width: 15),
|
const SizedBox(width: 15),
|
||||||
Chip(
|
|
||||||
avatar: const Icon(
|
|
||||||
Icons.link_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);
|
|
||||||
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');
|
|
||||||
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);
|
|
||||||
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
|
||||||
|
? LogDetailsScreen(
|
||||||
|
log: selectedLog!,
|
||||||
|
dialog: false,
|
||||||
|
)
|
||||||
|
: const SizedBox()
|
||||||
)
|
)
|
||||||
: null,
|
],
|
||||||
),
|
),
|
||||||
body: generateBody()
|
);
|
||||||
);
|
}
|
||||||
|
else {
|
||||||
|
return logsScreen();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -12,11 +12,13 @@ import 'package:adguard_home_manager/providers/servers_provider.dart';
|
||||||
class LogsConfigModal extends StatelessWidget {
|
class LogsConfigModal extends StatelessWidget {
|
||||||
final void Function(Map<String, dynamic>) onConfirm;
|
final void Function(Map<String, dynamic>) onConfirm;
|
||||||
final void Function() onClear;
|
final void Function() onClear;
|
||||||
|
final bool dialog;
|
||||||
|
|
||||||
const LogsConfigModal({
|
const LogsConfigModal({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.onConfirm,
|
required this.onConfirm,
|
||||||
required this.onClear,
|
required this.onClear,
|
||||||
|
required this.dialog
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -30,6 +32,7 @@ class LogsConfigModal extends StatelessWidget {
|
||||||
context: context,
|
context: context,
|
||||||
onConfirm: onConfirm,
|
onConfirm: onConfirm,
|
||||||
onClear: onClear,
|
onClear: onClear,
|
||||||
|
dialog: dialog,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,6 +43,7 @@ class LogsConfigModalWidget extends StatefulWidget {
|
||||||
final BuildContext context;
|
final BuildContext context;
|
||||||
final void Function(Map<String, dynamic>) onConfirm;
|
final void Function(Map<String, dynamic>) onConfirm;
|
||||||
final void Function() onClear;
|
final void Function() onClear;
|
||||||
|
final bool dialog;
|
||||||
|
|
||||||
const LogsConfigModalWidget({
|
const LogsConfigModalWidget({
|
||||||
Key? key,
|
Key? key,
|
||||||
|
@ -48,6 +52,7 @@ class LogsConfigModalWidget extends StatefulWidget {
|
||||||
required this.context,
|
required this.context,
|
||||||
required this.onConfirm,
|
required this.onConfirm,
|
||||||
required this.onClear,
|
required this.onClear,
|
||||||
|
required this.dialog
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -146,125 +151,154 @@ class _LogsConfigModalWidgetState extends State<LogsConfigModalWidget> {
|
||||||
Widget generateBody() {
|
Widget generateBody() {
|
||||||
switch (loadStatus) {
|
switch (loadStatus) {
|
||||||
case 0:
|
case 0:
|
||||||
return const Center(
|
return Padding(
|
||||||
child: CircularProgressIndicator(),
|
padding: const EdgeInsets.all(24),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const CircularProgressIndicator(),
|
||||||
|
const SizedBox(height: 30),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
|
child: Text(
|
||||||
|
AppLocalizations.of(context)!.loadingLogsSettings,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 22,
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
case 1:
|
case 1:
|
||||||
return Column(
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Flexible(
|
||||||
child: ListView(
|
child: SingleChildScrollView(
|
||||||
physics: (Platform.isIOS ? 436 : 420) < MediaQuery.of(context).size.height
|
child: Wrap(
|
||||||
? const NeverScrollableScrollPhysics()
|
children: [
|
||||||
: null,
|
Row(
|
||||||
children: [
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
Padding(
|
children: [
|
||||||
padding: const EdgeInsets.only(top: 24),
|
Column(
|
||||||
child: Icon(
|
children: [
|
||||||
Icons.settings,
|
Padding(
|
||||||
size: 24,
|
padding: const EdgeInsets.only(top: 24),
|
||||||
color: Theme.of(context).listTileTheme.iconColor
|
child: Icon(
|
||||||
),
|
Icons.settings,
|
||||||
),
|
size: 24,
|
||||||
const SizedBox(height: 16),
|
color: Theme.of(context).listTileTheme.iconColor
|
||||||
Text(
|
|
||||||
AppLocalizations.of(context)!.logsSettings,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 24,
|
|
||||||
color: Theme.of(context).colorScheme.onSurface
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
|
||||||
child: Material(
|
|
||||||
color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
|
|
||||||
borderRadius: BorderRadius.circular(28),
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () => setState(() => generalSwitch = !generalSwitch),
|
|
||||||
borderRadius: BorderRadius.circular(28),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 20,
|
|
||||||
vertical: 8
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
AppLocalizations.of(context)!.enableLog,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 18,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Switch(
|
),
|
||||||
value: generalSwitch,
|
const SizedBox(height: 16),
|
||||||
onChanged: (value) => setState(() => generalSwitch = value),
|
Text(
|
||||||
)
|
AppLocalizations.of(context)!.logsSettings,
|
||||||
],
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 24,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
child: Material(
|
||||||
|
color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(28),
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () => setState(() => generalSwitch = !generalSwitch),
|
||||||
|
borderRadius: BorderRadius.circular(28),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 20,
|
||||||
|
vertical: 8
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context)!.enableLog,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Switch(
|
||||||
|
value: generalSwitch,
|
||||||
|
onChanged: (value) => setState(() => generalSwitch = value),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
Container(height: 16),
|
||||||
const SizedBox(height: 16),
|
Padding(
|
||||||
Padding(
|
padding: const EdgeInsets.symmetric(horizontal: 14),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 14),
|
child: Column(
|
||||||
child: Column(
|
children: [
|
||||||
children: [
|
Material(
|
||||||
Material(
|
color: Colors.transparent,
|
||||||
color: Colors.transparent,
|
child: InkWell(
|
||||||
child: InkWell(
|
onTap: () => setState(() => anonymizeClientIp = !anonymizeClientIp),
|
||||||
onTap: () => setState(() => anonymizeClientIp = !anonymizeClientIp),
|
child: Padding(
|
||||||
child: Padding(
|
padding: const EdgeInsets.symmetric(horizontal: 30),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 30),
|
child: Row(
|
||||||
child: Row(
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
children: [
|
||||||
children: [
|
Text(
|
||||||
Text(
|
AppLocalizations.of(context)!.anonymizeClientIp,
|
||||||
AppLocalizations.of(context)!.anonymizeClientIp,
|
style: const TextStyle(
|
||||||
style: const TextStyle(
|
fontSize: 16
|
||||||
fontSize: 16
|
),
|
||||||
),
|
),
|
||||||
),
|
Switch(
|
||||||
Switch(
|
value: anonymizeClientIp,
|
||||||
value: anonymizeClientIp,
|
onChanged: (value) => setState(() => anonymizeClientIp = value),
|
||||||
onChanged: (value) => setState(() => anonymizeClientIp = value),
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(height: 16),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
child: DropdownButtonFormField(
|
||||||
|
items: retentionItems.map<DropdownMenuItem<String>>((Map<String, dynamic> item) {
|
||||||
|
return DropdownMenuItem<String>(
|
||||||
|
value: item['value'].toString(),
|
||||||
|
child: Text(item['label']),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
value: retentionTime,
|
||||||
|
onChanged: (value) => setState(() => retentionTime = value),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: const OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(10)
|
||||||
)
|
)
|
||||||
],
|
),
|
||||||
|
label: Text(AppLocalizations.of(context)!.retentionTime)
|
||||||
),
|
),
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
const SizedBox(height: 16),
|
),
|
||||||
Padding(
|
)
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
],
|
||||||
child: DropdownButtonFormField(
|
),
|
||||||
items: retentionItems.map<DropdownMenuItem<String>>((Map<String, dynamic> item) {
|
|
||||||
return DropdownMenuItem<String>(
|
|
||||||
value: item['value'].toString(),
|
|
||||||
child: Text(item['label']),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
value: retentionTime,
|
|
||||||
onChanged: (value) => setState(() => retentionTime = value),
|
|
||||||
decoration: InputDecoration(
|
|
||||||
border: const OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(
|
|
||||||
Radius.circular(10)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
label: Text(AppLocalizations.of(context)!.retentionTime)
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
|
@ -316,31 +350,29 @@ class _LogsConfigModalWidgetState extends State<LogsConfigModalWidget> {
|
||||||
);
|
);
|
||||||
|
|
||||||
case 2:
|
case 2:
|
||||||
return SizedBox(
|
return Column(
|
||||||
width: double.maxFinite,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
child: Column(
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
children: [
|
||||||
children: [
|
const Icon(
|
||||||
const Icon(
|
Icons.error,
|
||||||
Icons.error,
|
color: Colors.red,
|
||||||
color: Colors.red,
|
size: 50,
|
||||||
size: 50,
|
),
|
||||||
),
|
const SizedBox(height: 30),
|
||||||
const SizedBox(height: 30),
|
Padding(
|
||||||
Padding(
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
child: Text(
|
||||||
child: Text(
|
AppLocalizations.of(context)!.logSettingsNotLoaded,
|
||||||
AppLocalizations.of(context)!.logSettingsNotLoaded,
|
textAlign: TextAlign.center,
|
||||||
textAlign: TextAlign.center,
|
style: TextStyle(
|
||||||
style: const TextStyle(
|
fontSize: 22,
|
||||||
fontSize: 22,
|
color: Theme.of(context).colorScheme.onSurfaceVariant
|
||||||
color: Colors.grey,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
],
|
)
|
||||||
),
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -348,16 +380,28 @@ class _LogsConfigModalWidgetState extends State<LogsConfigModalWidget> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Container(
|
if (widget.dialog == true) {
|
||||||
height: Platform.isIOS ? 436 : 420,
|
return Dialog(
|
||||||
decoration: BoxDecoration(
|
child: ConstrainedBox(
|
||||||
borderRadius: const BorderRadius.only(
|
constraints: const BoxConstraints(
|
||||||
topLeft: Radius.circular(28),
|
maxWidth: 500
|
||||||
topRight: Radius.circular(28)
|
),
|
||||||
|
child: generateBody()
|
||||||
),
|
),
|
||||||
color: Theme.of(context).dialogBackgroundColor
|
);
|
||||||
),
|
}
|
||||||
child: generateBody()
|
else {
|
||||||
);
|
return Container(
|
||||||
|
height: Platform.isIOS ? 436 : 420,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: const BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(28),
|
||||||
|
topRight: Radius.circular(28)
|
||||||
|
),
|
||||||
|
color: Theme.of(context).dialogBackgroundColor
|
||||||
|
),
|
||||||
|
child: generateBody()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -18,24 +18,32 @@ import 'package:adguard_home_manager/models/applied_filters.dart';
|
||||||
import 'package:adguard_home_manager/providers/logs_provider.dart';
|
import 'package:adguard_home_manager/providers/logs_provider.dart';
|
||||||
|
|
||||||
class LogsFiltersModal extends StatelessWidget {
|
class LogsFiltersModal extends StatelessWidget {
|
||||||
const LogsFiltersModal({Key? key}) : super(key: key);
|
final bool dialog;
|
||||||
|
|
||||||
|
const LogsFiltersModal({
|
||||||
|
Key? key,
|
||||||
|
required this.dialog
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final logsProvider = Provider.of<LogsProvider>(context);
|
final logsProvider = Provider.of<LogsProvider>(context);
|
||||||
|
|
||||||
return LogsFiltersModalWidget(
|
return LogsFiltersModalWidget(
|
||||||
logsProvider: logsProvider
|
logsProvider: logsProvider,
|
||||||
|
dialog: dialog,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class LogsFiltersModalWidget extends StatefulWidget {
|
class LogsFiltersModalWidget extends StatefulWidget {
|
||||||
final LogsProvider logsProvider;
|
final LogsProvider logsProvider;
|
||||||
|
final bool dialog;
|
||||||
|
|
||||||
const LogsFiltersModalWidget({
|
const LogsFiltersModalWidget({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.logsProvider
|
required this.logsProvider,
|
||||||
|
required this.dialog
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -57,6 +65,8 @@ class _LogsFiltersModalWidgetState extends State<LogsFiltersModalWidget> {
|
||||||
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 width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
final Map<String, String> translatedString = {
|
final Map<String, String> translatedString = {
|
||||||
"all": AppLocalizations.of(context)!.all,
|
"all": AppLocalizations.of(context)!.all,
|
||||||
"filtered": AppLocalizations.of(context)!.filtered,
|
"filtered": AppLocalizations.of(context)!.filtered,
|
||||||
|
@ -101,25 +111,51 @@ class _LogsFiltersModalWidgetState extends State<LogsFiltersModalWidget> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void openSelectFilterStatus() {
|
void openSelectFilterStatus() {
|
||||||
showModalBottomSheet(
|
if (width > 700 || !(Platform.isAndroid || Platform.isIOS)) {
|
||||||
context: context,
|
showDialog(
|
||||||
builder: (context) => FilterStatusModal(
|
barrierDismissible: false,
|
||||||
value: logsProvider.selectedResultStatus,
|
context: context,
|
||||||
),
|
builder: (context) => FilterStatusModal(
|
||||||
isScrollControlled: true,
|
value: logsProvider.selectedResultStatus,
|
||||||
backgroundColor: Colors.transparent
|
dialog: true,
|
||||||
);
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => FilterStatusModal(
|
||||||
|
value: logsProvider.selectedResultStatus,
|
||||||
|
dialog: false,
|
||||||
|
),
|
||||||
|
isScrollControlled: true,
|
||||||
|
backgroundColor: Colors.transparent
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void openSelectClients() {
|
void openSelectClients() {
|
||||||
showModalBottomSheet(
|
if (width > 700 || !(Platform.isAndroid || Platform.isIOS)) {
|
||||||
context: context,
|
showDialog(
|
||||||
builder: (context) => ClientsModal(
|
context: context,
|
||||||
value: logsProvider.selectedClients,
|
builder: (context) => ClientsModal(
|
||||||
),
|
value: logsProvider.selectedClients,
|
||||||
isScrollControlled: true,
|
dialog: true,
|
||||||
backgroundColor: Colors.transparent
|
),
|
||||||
);
|
barrierDismissible: false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => ClientsModal(
|
||||||
|
value: logsProvider.selectedClients,
|
||||||
|
dialog: false,
|
||||||
|
),
|
||||||
|
isScrollControlled: true,
|
||||||
|
backgroundColor: Colors.transparent
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void filterLogs() async {
|
void filterLogs() async {
|
||||||
|
@ -161,47 +197,45 @@ class _LogsFiltersModalWidgetState extends State<LogsFiltersModalWidget> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Padding(
|
Widget content() {
|
||||||
padding: MediaQuery.of(context).viewInsets,
|
return Column(
|
||||||
child: Container(
|
mainAxisSize: MainAxisSize.min,
|
||||||
height: Platform.isIOS ? 446 : 430,
|
children: [
|
||||||
decoration: BoxDecoration(
|
Flexible(
|
||||||
color: Theme.of(context).dialogBackgroundColor,
|
child: SingleChildScrollView(
|
||||||
borderRadius: const BorderRadius.only(
|
child: Wrap(
|
||||||
topLeft: Radius.circular(28),
|
|
||||||
topRight: Radius.circular(28)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: ListView(
|
|
||||||
physics: (Platform.isIOS ? 416 : 400) < MediaQuery.of(context).size.height
|
|
||||||
? const NeverScrollableScrollPhysics()
|
|
||||||
: null,
|
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Row(
|
||||||
padding: const EdgeInsets.only(
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
top: 24,
|
children: [
|
||||||
bottom: 16,
|
Column(
|
||||||
),
|
children: [
|
||||||
child: Icon(
|
Padding(
|
||||||
Icons.filter_list_rounded,
|
padding: const EdgeInsets.only(
|
||||||
size: 24,
|
top: 24,
|
||||||
color: Theme.of(context).listTileTheme.iconColor
|
bottom: 16,
|
||||||
),
|
),
|
||||||
|
child: Icon(
|
||||||
|
Icons.filter_list_rounded,
|
||||||
|
size: 24,
|
||||||
|
color: Theme.of(context).listTileTheme.iconColor
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context)!.filters,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
height: 1.3,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
Text(
|
|
||||||
AppLocalizations.of(context)!.filters,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 24,
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
height: 1.3,
|
|
||||||
color: Theme.of(context).colorScheme.onSurface
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
child: Row(
|
child: Row(
|
||||||
|
@ -233,7 +267,7 @@ class _LogsFiltersModalWidgetState extends State<LogsFiltersModalWidget> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
Container(height: 16),
|
||||||
CustomListTile(
|
CustomListTile(
|
||||||
title: AppLocalizations.of(context)!.client,
|
title: AppLocalizations.of(context)!.client,
|
||||||
subtitle: logsProvider.selectedClients != null
|
subtitle: logsProvider.selectedClients != null
|
||||||
|
@ -270,26 +304,55 @@ class _LogsFiltersModalWidgetState extends State<LogsFiltersModalWidget> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
),
|
||||||
padding: const EdgeInsets.all(24),
|
Padding(
|
||||||
child: Row(
|
padding: const EdgeInsets.all(24),
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
child: Row(
|
||||||
children: [
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
TextButton(
|
children: [
|
||||||
onPressed: resetFilters,
|
TextButton(
|
||||||
child: Text(AppLocalizations.of(context)!.resetFilters)
|
onPressed: resetFilters,
|
||||||
),
|
child: Text(AppLocalizations.of(context)!.resetFilters)
|
||||||
TextButton(
|
),
|
||||||
onPressed: () => filterLogs(),
|
TextButton(
|
||||||
child: Text(AppLocalizations.of(context)!.apply)
|
onPressed: () => filterLogs(),
|
||||||
),
|
child: Text(AppLocalizations.of(context)!.apply)
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
if (Platform.isIOS) const SizedBox(height: 16)
|
),
|
||||||
],
|
if (Platform.isIOS) const SizedBox(height: 16)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (widget.dialog == true) {
|
||||||
|
return Padding(
|
||||||
|
padding: MediaQuery.of(context).viewInsets,
|
||||||
|
child: Dialog(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
maxWidth: 500
|
||||||
|
),
|
||||||
|
child: content()
|
||||||
|
)
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
}
|
||||||
|
else {
|
||||||
|
return Padding(
|
||||||
|
padding: MediaQuery.of(context).viewInsets,
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).dialogBackgroundColor,
|
||||||
|
borderRadius: const BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(28),
|
||||||
|
topRight: Radius.circular(28)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
child: content()
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -13,7 +13,12 @@ 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 Servers extends StatefulWidget {
|
class Servers extends StatefulWidget {
|
||||||
const Servers({Key? key}) : super(key: key);
|
final double? breakingWidth;
|
||||||
|
|
||||||
|
const Servers({
|
||||||
|
Key? key,
|
||||||
|
this.breakingWidth
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<Servers> createState() => _ServersState();
|
State<Servers> createState() => _ServersState();
|
||||||
|
@ -55,16 +60,31 @@ class _ServersState extends State<Servers> {
|
||||||
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 width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
for (var i = 0; i < serversProvider.serversList.length; i++) {
|
for (var i = 0; i < serversProvider.serversList.length; i++) {
|
||||||
expandableControllerList.add(ExpandableController());
|
expandableControllerList.add(ExpandableController());
|
||||||
}
|
}
|
||||||
|
|
||||||
void openAddServerModal() async {
|
void openAddServerModal() async {
|
||||||
await Future.delayed(const Duration(seconds: 0), (() => {
|
await Future.delayed(const Duration(seconds: 0), (() => {
|
||||||
Navigator.push(context, MaterialPageRoute(
|
if (width > 700) {
|
||||||
fullscreenDialog: true,
|
showDialog(
|
||||||
builder: (BuildContext context) => const AddServerModal()
|
context: context,
|
||||||
))
|
barrierDismissible: false,
|
||||||
|
builder: (context) => const AddServerModal(
|
||||||
|
window: true,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Navigator.push(context, MaterialPageRoute(
|
||||||
|
fullscreenDialog: true,
|
||||||
|
builder: (BuildContext context) => const AddServerModal(
|
||||||
|
window: false,
|
||||||
|
)
|
||||||
|
))
|
||||||
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,7 +99,8 @@ class _ServersState extends State<Servers> {
|
||||||
context: context,
|
context: context,
|
||||||
controllers: expandableControllerList,
|
controllers: expandableControllerList,
|
||||||
onChange: expandOrContract,
|
onChange: expandOrContract,
|
||||||
scrollController: scrollController
|
scrollController: scrollController,
|
||||||
|
breakingWidth: widget.breakingWidth ?? 700,
|
||||||
),
|
),
|
||||||
AnimatedPositioned(
|
AnimatedPositioned(
|
||||||
duration: const Duration(milliseconds: 100),
|
duration: const Duration(milliseconds: 100),
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
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';
|
||||||
|
@ -72,78 +74,114 @@ class _AccessSettingsWidgetState extends State<AccessSettingsWidget> with Ticker
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final serversProvider = Provider.of<ServersProvider>(context);
|
final serversProvider = Provider.of<ServersProvider>(context);
|
||||||
return Scaffold(
|
|
||||||
body: DefaultTabController(
|
Widget body() {
|
||||||
length: 3,
|
return TabBarView(
|
||||||
child: NestedScrollView(
|
controller: tabController,
|
||||||
controller: scrollController,
|
children: [
|
||||||
headerSliverBuilder: ((context, innerBoxIsScrolled) {
|
ClientsList(
|
||||||
return [
|
type: 'allowed',
|
||||||
SliverOverlapAbsorber(
|
scrollController: scrollController,
|
||||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
loadStatus: serversProvider.clients.loadStatus,
|
||||||
sliver: SliverSafeArea(
|
data: serversProvider.clients.loadStatus == LoadStatus.loaded
|
||||||
top: false,
|
? serversProvider.clients.data!.clientsAllowedBlocked!.allowedClients : [],
|
||||||
sliver: SliverAppBar(
|
fetchClients: fetchClients
|
||||||
title: Text(AppLocalizations.of(context)!.accessSettings),
|
),
|
||||||
pinned: true,
|
ClientsList(
|
||||||
floating: true,
|
type: 'disallowed',
|
||||||
centerTitle: false,
|
scrollController: scrollController,
|
||||||
forceElevated: innerBoxIsScrolled,
|
loadStatus: serversProvider.clients.loadStatus,
|
||||||
bottom: TabBar(
|
data: serversProvider.clients.loadStatus == LoadStatus.loaded
|
||||||
controller: tabController,
|
? serversProvider.clients.data!.clientsAllowedBlocked!.disallowedClients : [],
|
||||||
isScrollable: true,
|
fetchClients: fetchClients
|
||||||
unselectedLabelColor: Theme.of(context).colorScheme.onSurfaceVariant,
|
),
|
||||||
tabs: [
|
ClientsList(
|
||||||
Tab(
|
type: 'domains',
|
||||||
icon: const Icon(Icons.check),
|
scrollController: scrollController,
|
||||||
text: AppLocalizations.of(context)!.allowedClients,
|
loadStatus: serversProvider.clients.loadStatus,
|
||||||
),
|
data: serversProvider.clients.loadStatus == LoadStatus.loaded
|
||||||
Tab(
|
? serversProvider.clients.data!.clientsAllowedBlocked!.blockedHosts : [],
|
||||||
icon: const Icon(Icons.block),
|
fetchClients: fetchClients
|
||||||
text: AppLocalizations.of(context)!.disallowedClients,
|
),
|
||||||
),
|
]
|
||||||
Tab(
|
);
|
||||||
icon: const Icon(Icons.link_rounded),
|
}
|
||||||
text: AppLocalizations.of(context)!.disallowedDomains,
|
|
||||||
),
|
PreferredSizeWidget tabBar() {
|
||||||
]
|
return TabBar(
|
||||||
)
|
controller: tabController,
|
||||||
|
isScrollable: true,
|
||||||
|
unselectedLabelColor: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
tabs: [
|
||||||
|
Tab(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.check),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(AppLocalizations.of(context)!.allowedClients)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Tab(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.block),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(AppLocalizations.of(context)!.disallowedClients)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Tab(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.link_rounded),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(AppLocalizations.of(context)!.disallowedDomains)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Platform.isAndroid || Platform.isIOS) {
|
||||||
|
return Scaffold(
|
||||||
|
body: DefaultTabController(
|
||||||
|
length: 3,
|
||||||
|
child: NestedScrollView(
|
||||||
|
controller: scrollController,
|
||||||
|
headerSliverBuilder: ((context, innerBoxIsScrolled) {
|
||||||
|
return [
|
||||||
|
SliverOverlapAbsorber(
|
||||||
|
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||||
|
sliver: SliverSafeArea(
|
||||||
|
top: false,
|
||||||
|
sliver: SliverAppBar(
|
||||||
|
title: Text(AppLocalizations.of(context)!.accessSettings),
|
||||||
|
pinned: true,
|
||||||
|
floating: true,
|
||||||
|
centerTitle: false,
|
||||||
|
forceElevated: innerBoxIsScrolled,
|
||||||
|
bottom: tabBar()
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
)
|
];
|
||||||
];
|
}),
|
||||||
}),
|
body: body()
|
||||||
body: TabBarView(
|
|
||||||
controller: tabController,
|
|
||||||
children: [
|
|
||||||
ClientsList(
|
|
||||||
type: 'allowed',
|
|
||||||
scrollController: scrollController,
|
|
||||||
loadStatus: serversProvider.clients.loadStatus,
|
|
||||||
data: serversProvider.clients.loadStatus == LoadStatus.loaded
|
|
||||||
? serversProvider.clients.data!.clientsAllowedBlocked!.allowedClients : [],
|
|
||||||
fetchClients: fetchClients
|
|
||||||
),
|
|
||||||
ClientsList(
|
|
||||||
type: 'disallowed',
|
|
||||||
scrollController: scrollController,
|
|
||||||
loadStatus: serversProvider.clients.loadStatus,
|
|
||||||
data: serversProvider.clients.loadStatus == LoadStatus.loaded
|
|
||||||
? serversProvider.clients.data!.clientsAllowedBlocked!.disallowedClients : [],
|
|
||||||
fetchClients: fetchClients
|
|
||||||
),
|
|
||||||
ClientsList(
|
|
||||||
type: 'domains',
|
|
||||||
scrollController: scrollController,
|
|
||||||
loadStatus: serversProvider.clients.loadStatus,
|
|
||||||
data: serversProvider.clients.loadStatus == LoadStatus.loaded
|
|
||||||
? serversProvider.clients.data!.clientsAllowedBlocked!.blockedHosts : [],
|
|
||||||
fetchClients: fetchClients
|
|
||||||
),
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
)
|
),
|
||||||
),
|
);
|
||||||
);
|
}
|
||||||
|
else {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(AppLocalizations.of(context)!.accessSettings),
|
||||||
|
centerTitle: false,
|
||||||
|
bottom: tabBar()
|
||||||
|
),
|
||||||
|
body: body(),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -6,11 +6,13 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
class AddClientModal extends StatefulWidget {
|
class AddClientModal extends StatefulWidget {
|
||||||
final String type;
|
final String type;
|
||||||
final void Function(String, String) onConfirm;
|
final void Function(String, String) onConfirm;
|
||||||
|
final bool dialog;
|
||||||
|
|
||||||
const AddClientModal({
|
const AddClientModal({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.type,
|
required this.type,
|
||||||
required this.onConfirm
|
required this.onConfirm,
|
||||||
|
required this.dialog,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -65,59 +67,61 @@ class _AddClientModalState extends State<AddClientModal> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Padding(
|
Widget content() {
|
||||||
padding: MediaQuery.of(context).viewInsets,
|
return Padding(
|
||||||
child: Container(
|
|
||||||
height: Platform.isIOS ? 321 : 305,
|
|
||||||
padding: const EdgeInsets.all(24),
|
padding: const EdgeInsets.all(24),
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Theme.of(context).dialogBackgroundColor,
|
|
||||||
borderRadius: const BorderRadius.only(
|
|
||||||
topLeft: Radius.circular(28),
|
|
||||||
topRight: Radius.circular(28)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
child: Column(
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Flexible(
|
||||||
child: ListView(
|
child: SingleChildScrollView(
|
||||||
physics: (Platform.isIOS ? 338 : 322) < MediaQuery.of(context).size.height
|
child: Wrap(
|
||||||
? const NeverScrollableScrollPhysics()
|
children: [
|
||||||
: null,
|
Row(
|
||||||
children: [
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
Icon(
|
children: [
|
||||||
icon(),
|
Icon(
|
||||||
size: 24,
|
icon(),
|
||||||
color: Theme.of(context).listTileTheme.iconColor
|
size: 24,
|
||||||
),
|
color: Theme.of(context).listTileTheme.iconColor
|
||||||
const SizedBox(height: 16),
|
),
|
||||||
Text(
|
],
|
||||||
title(),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 24,
|
|
||||||
color: Theme.of(context).colorScheme.onSurface
|
|
||||||
),
|
),
|
||||||
),
|
Padding(
|
||||||
const SizedBox(height: 16),
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
TextFormField(
|
child: Row(
|
||||||
controller: fieldController,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
onChanged: (_) => checkValidValues(),
|
children: [
|
||||||
decoration: InputDecoration(
|
Text(
|
||||||
prefixIcon: const Icon(Icons.link_rounded),
|
title(),
|
||||||
border: const OutlineInputBorder(
|
textAlign: TextAlign.center,
|
||||||
borderRadius: BorderRadius.all(
|
style: TextStyle(
|
||||||
Radius.circular(10)
|
fontSize: 24,
|
||||||
)
|
color: Theme.of(context).colorScheme.onSurface
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
helperText: widget.type == 'allowed' || widget.type == 'disallowed'
|
|
||||||
? AppLocalizations.of(context)!.addClientFieldDescription : null,
|
|
||||||
labelText: widget.type == 'allowed' || widget.type == 'disallowed'
|
|
||||||
? AppLocalizations.of(context)!.clientIdentifier
|
|
||||||
: AppLocalizations.of(context)!.domain,
|
|
||||||
),
|
),
|
||||||
),
|
TextFormField(
|
||||||
],
|
controller: fieldController,
|
||||||
|
onChanged: (_) => checkValidValues(),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
prefixIcon: const Icon(Icons.link_rounded),
|
||||||
|
border: const OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(10)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
helperText: widget.type == 'allowed' || widget.type == 'disallowed'
|
||||||
|
? AppLocalizations.of(context)!.addClientFieldDescription : null,
|
||||||
|
labelText: widget.type == 'allowed' || widget.type == 'disallowed'
|
||||||
|
? AppLocalizations.of(context)!.clientIdentifier
|
||||||
|
: AppLocalizations.of(context)!.domain,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
|
@ -129,7 +133,7 @@ class _AddClientModalState extends State<AddClientModal> {
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
child: Text(AppLocalizations.of(context)!.cancel)
|
child: Text(AppLocalizations.of(context)!.cancel)
|
||||||
),
|
),
|
||||||
const SizedBox(width: 20),
|
const SizedBox(width: 16),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: validData == true
|
onPressed: validData == true
|
||||||
? () {
|
? () {
|
||||||
|
@ -149,10 +153,38 @@ class _AddClientModalState extends State<AddClientModal> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (Platform.isIOS) const SizedBox(height: 16)
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
}
|
||||||
|
|
||||||
|
if (widget.dialog == true) {
|
||||||
|
return Padding(
|
||||||
|
padding: MediaQuery.of(context).viewInsets,
|
||||||
|
child: Dialog(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
maxWidth: 400
|
||||||
|
),
|
||||||
|
child: content()
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return Padding(
|
||||||
|
padding: MediaQuery.of(context).viewInsets,
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).dialogBackgroundColor,
|
||||||
|
borderRadius: const BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(28),
|
||||||
|
topRight: Radius.circular(28)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
child: content()
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
// 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:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
@ -68,6 +70,8 @@ class _ClientsListState extends State<ClientsList> {
|
||||||
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 width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
void confirmRemoveItem(String client, String type) async {
|
void confirmRemoveItem(String client, String type) async {
|
||||||
Map<String, List<String>> body = {
|
Map<String, List<String>> body = {
|
||||||
"allowed_clients": serversProvider.clients.data!.clientsAllowedBlocked?.allowedClients ?? [],
|
"allowed_clients": serversProvider.clients.data!.clientsAllowedBlocked?.allowedClients ?? [],
|
||||||
|
@ -209,6 +213,7 @@ class _ClientsListState extends State<ClientsList> {
|
||||||
}
|
}
|
||||||
|
|
||||||
return CustomTabContentList(
|
return CustomTabContentList(
|
||||||
|
noSliver: !(Platform.isAndroid || Platform.isIOS) ? true : false,
|
||||||
loadingGenerator: () => SizedBox(
|
loadingGenerator: () => SizedBox(
|
||||||
width: double.maxFinite,
|
width: double.maxFinite,
|
||||||
height: MediaQuery.of(context).size.height-171,
|
height: MediaQuery.of(context).size.height-171,
|
||||||
|
@ -362,15 +367,28 @@ class _ClientsListState extends State<ClientsList> {
|
||||||
refreshIndicatorOffset: 0,
|
refreshIndicatorOffset: 0,
|
||||||
fab: FloatingActionButton(
|
fab: FloatingActionButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
showModalBottomSheet(
|
if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) {
|
||||||
context: context,
|
showDialog(
|
||||||
builder: (context) => AddClientModal(
|
context: context,
|
||||||
type: widget.type,
|
builder: (context) => AddClientModal(
|
||||||
onConfirm: confirmAddItem
|
type: widget.type,
|
||||||
),
|
onConfirm: confirmAddItem,
|
||||||
backgroundColor: Colors.transparent,
|
dialog: true,
|
||||||
isScrollControlled: true
|
),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AddClientModal(
|
||||||
|
type: widget.type,
|
||||||
|
onConfirm: confirmAddItem,
|
||||||
|
dialog: false,
|
||||||
|
),
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
isScrollControlled: true
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
child: const Icon(Icons.add),
|
child: const Icon(Icons.add),
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
// 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:flutter_split_view/flutter_split_view.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';
|
||||||
|
|
||||||
|
@ -15,6 +18,8 @@ class AdvancedSettings extends StatelessWidget {
|
||||||
@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;
|
||||||
|
|
||||||
Future updateSslCheck(bool newStatus) async {
|
Future updateSslCheck(bool newStatus) async {
|
||||||
final result = await appConfigProvider.setOverrideSslCheck(newStatus);
|
final result = await appConfigProvider.setOverrideSslCheck(newStatus);
|
||||||
|
@ -64,11 +69,16 @@ class AdvancedSettings extends StatelessWidget {
|
||||||
title: AppLocalizations.of(context)!.logs,
|
title: AppLocalizations.of(context)!.logs,
|
||||||
subtitle: AppLocalizations.of(context)!.checkAppLogs,
|
subtitle: AppLocalizations.of(context)!.checkAppLogs,
|
||||||
onTap: () => {
|
onTap: () => {
|
||||||
Navigator.of(context).push(
|
if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) {
|
||||||
MaterialPageRoute(
|
SplitView.of(context).push(const AppLogs())
|
||||||
builder: (context) => const AppLogs()
|
}
|
||||||
|
else {
|
||||||
|
Navigator.of(context).push(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => const AppLogs()
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
},
|
},
|
||||||
padding: const EdgeInsets.only(
|
padding: const EdgeInsets.only(
|
||||||
top: 10,
|
top: 10,
|
||||||
|
|
|
@ -5,10 +5,12 @@ import 'package:adguard_home_manager/models/dhcp.dart';
|
||||||
|
|
||||||
class AddStaticLeaseModal extends StatefulWidget {
|
class AddStaticLeaseModal extends StatefulWidget {
|
||||||
final void Function(Lease) onConfirm;
|
final void Function(Lease) onConfirm;
|
||||||
|
final bool dialog;
|
||||||
|
|
||||||
const AddStaticLeaseModal({
|
const AddStaticLeaseModal({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.onConfirm,
|
required this.onConfirm,
|
||||||
|
required this.dialog
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -65,45 +67,47 @@ class _AddStaticLeaseModalState extends State<AddStaticLeaseModal> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Padding(
|
Widget content() {
|
||||||
padding: MediaQuery.of(context).viewInsets,
|
return Column(
|
||||||
child: Container(
|
mainAxisSize: MainAxisSize.min,
|
||||||
height: 510,
|
children: [
|
||||||
decoration: BoxDecoration(
|
Flexible(
|
||||||
color: Theme.of(context).dialogBackgroundColor,
|
child: SingleChildScrollView(
|
||||||
borderRadius: const BorderRadius.only(
|
child: Wrap(
|
||||||
topLeft: Radius.circular(28),
|
|
||||||
topRight: Radius.circular(28)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: ListView(
|
|
||||||
physics: 550 < MediaQuery.of(context).size.height
|
|
||||||
? const NeverScrollableScrollPhysics()
|
|
||||||
: null,
|
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 24),
|
padding: const EdgeInsets.only(bottom: 16),
|
||||||
child: Icon(
|
child: Row(
|
||||||
Icons.add,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
size: 24,
|
children: [
|
||||||
color: Theme.of(context).listTileTheme.iconColor
|
Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 24),
|
||||||
|
child: Icon(
|
||||||
|
Icons.add,
|
||||||
|
size: 24,
|
||||||
|
color: Theme.of(context).listTileTheme.iconColor
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context)!.addStaticLease,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 24,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
|
||||||
Text(
|
|
||||||
AppLocalizations.of(context)!.addStaticLease,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 24,
|
|
||||||
color: Theme.of(context).colorScheme.onSurface
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 28),
|
padding: const EdgeInsets.only(
|
||||||
|
left: 24, right: 24, bottom: 12
|
||||||
|
),
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
controller: macController,
|
controller: macController,
|
||||||
onChanged: validateMac,
|
onChanged: validateMac,
|
||||||
|
@ -119,9 +123,8 @@ class _AddStaticLeaseModalState extends State<AddStaticLeaseModal> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 30),
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 28),
|
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
controller: ipController,
|
controller: ipController,
|
||||||
onChanged: validateIp,
|
onChanged: validateIp,
|
||||||
|
@ -137,9 +140,10 @@ class _AddStaticLeaseModalState extends State<AddStaticLeaseModal> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 30),
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 28),
|
padding: const EdgeInsets.only(
|
||||||
|
left: 24, right: 24, top: 12
|
||||||
|
),
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
controller: hostNameController,
|
controller: hostNameController,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
|
@ -166,44 +170,70 @@ class _AddStaticLeaseModalState extends State<AddStaticLeaseModal> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
),
|
||||||
padding: const EdgeInsets.all(20),
|
Padding(
|
||||||
child: Row(
|
padding: const EdgeInsets.all(24),
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
child: Row(
|
||||||
children: [
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
TextButton(
|
children: [
|
||||||
onPressed: () => Navigator.pop(context),
|
TextButton(
|
||||||
child: Text(AppLocalizations.of(context)!.cancel),
|
onPressed: () => Navigator.pop(context),
|
||||||
),
|
child: Text(AppLocalizations.of(context)!.cancel),
|
||||||
const SizedBox(width: 20),
|
),
|
||||||
TextButton(
|
const SizedBox(width: 20),
|
||||||
onPressed: validData == true
|
TextButton(
|
||||||
? () {
|
onPressed: validData == true
|
||||||
Navigator.pop(context);
|
? () {
|
||||||
widget.onConfirm(
|
Navigator.pop(context);
|
||||||
Lease(
|
widget.onConfirm(
|
||||||
mac: macController.text,
|
Lease(
|
||||||
hostname: hostNameController.text,
|
mac: macController.text,
|
||||||
ip: ipController.text
|
hostname: hostNameController.text,
|
||||||
)
|
ip: ipController.text
|
||||||
);
|
)
|
||||||
}
|
);
|
||||||
: null,
|
}
|
||||||
child: Text(
|
: null,
|
||||||
AppLocalizations.of(context)!.confirm,
|
child: Text(
|
||||||
style: TextStyle(
|
AppLocalizations.of(context)!.confirm,
|
||||||
color: validData == true
|
style: TextStyle(
|
||||||
? Theme.of(context).colorScheme.primary
|
color: validData == true
|
||||||
: Colors.grey
|
? Theme.of(context).colorScheme.primary
|
||||||
),
|
: Colors.grey
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
)
|
),
|
||||||
],
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (widget.dialog == true) {
|
||||||
|
return Dialog(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
maxWidth: 400
|
||||||
|
),
|
||||||
|
child: content(),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
}
|
||||||
|
else {
|
||||||
|
return Padding(
|
||||||
|
padding: MediaQuery.of(context).viewInsets,
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).dialogBackgroundColor,
|
||||||
|
borderRadius: const BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(28),
|
||||||
|
topRight: Radius.circular(28)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
child: content()
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,8 +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:flutter/material.dart';
|
||||||
|
import 'package:flutter_split_view/flutter_split_view.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:bottom_sheet/bottom_sheet.dart';
|
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
import 'package:adguard_home_manager/widgets/section_label.dart';
|
import 'package:adguard_home_manager/widgets/section_label.dart';
|
||||||
|
@ -204,7 +206,7 @@ class _DhcpWidgetState extends State<DhcpWidget> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
loadDhcpStatus();
|
if (mounted) loadDhcpStatus();
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,6 +215,8 @@ class _DhcpWidgetState extends State<DhcpWidget> {
|
||||||
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 width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
void saveSettings() async {
|
void saveSettings() async {
|
||||||
ProcessModal processModal = ProcessModal(context: context);
|
ProcessModal processModal = ProcessModal(context: context);
|
||||||
processModal.open(AppLocalizations.of(context)!.savingSettings);
|
processModal.open(AppLocalizations.of(context)!.savingSettings);
|
||||||
|
@ -354,24 +358,33 @@ class _DhcpWidgetState extends State<DhcpWidget> {
|
||||||
void selectInterface() {
|
void selectInterface() {
|
||||||
ScaffoldMessenger.of(context).clearSnackBars();
|
ScaffoldMessenger.of(context).clearSnackBars();
|
||||||
Future.delayed(const Duration(seconds: 0), () {
|
Future.delayed(const Duration(seconds: 0), () {
|
||||||
showFlexibleBottomSheet(
|
if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) {
|
||||||
minHeight: 0.6,
|
showDialog(
|
||||||
initHeight: 0.6,
|
context: context,
|
||||||
maxHeight: 0.95,
|
builder: (context) => SelectInterfaceModal(
|
||||||
isCollapsible: true,
|
interfaces: serversProvider.dhcp.data!.networkInterfaces,
|
||||||
duration: const Duration(milliseconds: 250),
|
onSelect: (interface) => setState(() {
|
||||||
anchors: [0.95],
|
clearAll();
|
||||||
context: context,
|
selectedInterface = interface;
|
||||||
builder: (ctx, controller, offset) => SelectInterfaceModal(
|
}),
|
||||||
interfaces: serversProvider.dhcp.data!.networkInterfaces,
|
dialog: true,
|
||||||
scrollController: controller,
|
)
|
||||||
onSelect: (interface) => setState(() {
|
);
|
||||||
clearAll();
|
}
|
||||||
selectedInterface = interface;
|
else {
|
||||||
})
|
showModalBottomSheet(
|
||||||
),
|
context: context,
|
||||||
bottomSheetColor: Colors.transparent
|
builder: (context) => SelectInterfaceModal(
|
||||||
);
|
interfaces: serversProvider.dhcp.data!.networkInterfaces,
|
||||||
|
onSelect: (i) => setState(() {
|
||||||
|
clearAll();
|
||||||
|
selectedInterface = i;
|
||||||
|
}),
|
||||||
|
dialog: false,
|
||||||
|
),
|
||||||
|
isScrollControlled: true
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -399,334 +412,436 @@ class _DhcpWidgetState extends State<DhcpWidget> {
|
||||||
|
|
||||||
case 1:
|
case 1:
|
||||||
if (selectedInterface != null) {
|
if (selectedInterface != null) {
|
||||||
return ListView(
|
return SingleChildScrollView(
|
||||||
children: [
|
child: Wrap(
|
||||||
Padding(
|
children: [
|
||||||
padding: const EdgeInsets.only(
|
Padding(
|
||||||
top: 10,
|
padding: const EdgeInsets.only(
|
||||||
left: 16,
|
top: 10,
|
||||||
right: 16
|
left: 16,
|
||||||
),
|
right: 16
|
||||||
child: Material(
|
),
|
||||||
color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
|
child: Material(
|
||||||
borderRadius: BorderRadius.circular(28),
|
color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
|
||||||
child: InkWell(
|
|
||||||
onTap: selectedInterface != null
|
|
||||||
? () => setState(() => enabled = !enabled)
|
|
||||||
: null,
|
|
||||||
borderRadius: BorderRadius.circular(28),
|
borderRadius: BorderRadius.circular(28),
|
||||||
child: Padding(
|
child: InkWell(
|
||||||
padding: const EdgeInsets.symmetric(
|
onTap: selectedInterface != null
|
||||||
horizontal: 20,
|
? () => setState(() => enabled = !enabled)
|
||||||
vertical: 12
|
: null,
|
||||||
|
borderRadius: BorderRadius.circular(28),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 20,
|
||||||
|
vertical: 12
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context)!.enableDhcpServer,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (selectedInterface != null) ...[
|
||||||
|
Text(
|
||||||
|
selectedInterface!.name,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Theme.of(context).listTileTheme.textColor
|
||||||
|
),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Switch(
|
||||||
|
value: enabled,
|
||||||
|
onChanged: selectedInterface != null
|
||||||
|
? (value) => setState(() => enabled = value)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (selectedInterface!.ipv4Addresses.isNotEmpty) ...[
|
||||||
|
SectionLabel(
|
||||||
|
label: AppLocalizations.of(context)!.ipv4settings,
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
top: 24, left: 16, right: 16, bottom: 8
|
||||||
|
)
|
||||||
|
),
|
||||||
|
FractionallySizedBox(
|
||||||
|
widthFactor: width > 900 ? 0.5 : 1,
|
||||||
|
child: Padding(
|
||||||
|
padding: width > 900
|
||||||
|
? const EdgeInsets.only(top: 12, bottom: 12, left: 16, right: 8)
|
||||||
|
: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
|
child: TextFormField(
|
||||||
|
controller: ipv4StartRangeController,
|
||||||
|
onChanged: (value) => validateIpV4(value, 'ipv4StartRangeError', AppLocalizations.of(context)!.ipNotValid),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
prefixIcon: const Icon(Icons.skip_previous_rounded),
|
||||||
|
border: const OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(10)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
errorText: ipv4StartRangeError,
|
||||||
|
labelText: AppLocalizations.of(context)!.startOfRange,
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
FractionallySizedBox(
|
||||||
|
widthFactor: width > 900 ? 0.5 : 1,
|
||||||
|
child: Padding(
|
||||||
|
padding: width > 900
|
||||||
|
? const EdgeInsets.only(top: 12, bottom: 12, left: 8, right: 16)
|
||||||
|
: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
|
child: TextFormField(
|
||||||
|
controller: ipv4EndRangeController,
|
||||||
|
onChanged: (value) => validateIpV4(value, 'ipv4EndRangeError', AppLocalizations.of(context)!.ipNotValid),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
prefixIcon: const Icon(Icons.skip_next_rounded),
|
||||||
|
border: const OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(10)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
errorText: ipv4EndRangeError,
|
||||||
|
labelText: AppLocalizations.of(context)!.endOfRange,
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
FractionallySizedBox(
|
||||||
|
widthFactor: width > 900 ? 0.5 : 1,
|
||||||
|
child: Padding(
|
||||||
|
padding: width > 900
|
||||||
|
? const EdgeInsets.only(top: 12, bottom: 12, left: 16, right: 8)
|
||||||
|
: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
|
child: TextFormField(
|
||||||
|
controller: ipv4SubnetMaskController,
|
||||||
|
onChanged: (value) => validateIpV4(value, 'ipv4SubnetMaskError', AppLocalizations.of(context)!.subnetMaskNotValid),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
prefixIcon: const Icon(Icons.hub_rounded),
|
||||||
|
border: const OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(10)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
errorText: ipv4SubnetMaskError,
|
||||||
|
labelText: AppLocalizations.of(context)!.subnetMask,
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
FractionallySizedBox(
|
||||||
|
widthFactor: width > 900 ? 0.5 : 1,
|
||||||
|
child: Padding(
|
||||||
|
padding: width > 900
|
||||||
|
? const EdgeInsets.only(top: 12, bottom: 12, left: 8, right: 16)
|
||||||
|
: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
|
child: TextFormField(
|
||||||
|
controller: ipv4GatewayController,
|
||||||
|
onChanged: (value) => validateIpV4(value, 'ipv4GatewayError', AppLocalizations.of(context)!.gatewayNotValid),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
prefixIcon: const Icon(Icons.router_rounded),
|
||||||
|
border: const OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(10)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
errorText: ipv4GatewayError,
|
||||||
|
labelText: AppLocalizations.of(context)!.gateway,
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
FractionallySizedBox(
|
||||||
|
widthFactor: 1,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
|
child: TextFormField(
|
||||||
|
controller: ipv4LeaseTimeController,
|
||||||
|
onChanged: (value) {
|
||||||
|
if (int.tryParse(value).runtimeType == int) {
|
||||||
|
setState(() => ipv4LeaseTimeError = null);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setState(() => ipv4LeaseTimeError = AppLocalizations.of(context)!.leaseTimeNotValid);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
decoration: InputDecoration(
|
||||||
|
prefixIcon: const Icon(Icons.timer),
|
||||||
|
border: const OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(10)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
errorText: ipv4LeaseTimeError,
|
||||||
|
labelText: AppLocalizations.of(context)!.leaseTime,
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
if (selectedInterface!.ipv6Addresses.isNotEmpty) ...[
|
||||||
|
SectionLabel(
|
||||||
|
label: AppLocalizations.of(context)!.ipv6settings,
|
||||||
|
padding: const EdgeInsets.all(16)
|
||||||
|
),
|
||||||
|
FractionallySizedBox(
|
||||||
|
widthFactor: width > 900 ? 0.5 : 1,
|
||||||
|
child: Padding(
|
||||||
|
padding: width > 900
|
||||||
|
? const EdgeInsets.only(top: 8, bottom: 12, left: 16, right: 8)
|
||||||
|
: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
|
child: TextFormField(
|
||||||
|
controller: ipv6StartRangeController,
|
||||||
|
onChanged: (value) => validateIpV4(value, 'ipv6StartRangeError', AppLocalizations.of(context)!.ipNotValid),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
prefixIcon: const Icon(Icons.skip_next_rounded),
|
||||||
|
border: const OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(10)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
errorText: ipv6StartRangeError,
|
||||||
|
labelText: AppLocalizations.of(context)!.startOfRange,
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
FractionallySizedBox(
|
||||||
|
widthFactor: width > 900 ? 0.5 : 1,
|
||||||
|
child: Padding(
|
||||||
|
padding: width > 900
|
||||||
|
? const EdgeInsets.only(top: 8, bottom: 12, left: 8, right: 16)
|
||||||
|
: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
|
child: TextFormField(
|
||||||
|
controller: ipv6EndRangeController,
|
||||||
|
onChanged: (value) => validateIpV4(value, 'ipv6EndRangeError', AppLocalizations.of(context)!.ipNotValid),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
prefixIcon: const Icon(Icons.skip_previous_rounded),
|
||||||
|
border: const OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(10)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
errorText: ipv6EndRangeError,
|
||||||
|
labelText: AppLocalizations.of(context)!.endOfRange,
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
FractionallySizedBox(
|
||||||
|
widthFactor: 1,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
|
child: TextFormField(
|
||||||
|
controller: ipv6LeaseTimeController,
|
||||||
|
onChanged: (value) {
|
||||||
|
if (int.tryParse(value).runtimeType == int) {
|
||||||
|
setState(() => ipv6LeaseTimeError = null);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setState(() => ipv6LeaseTimeError = AppLocalizations.of(context)!.leaseTimeNotValid);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
decoration: InputDecoration(
|
||||||
|
prefixIcon: const Icon(Icons.timer),
|
||||||
|
border: const OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(10)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
errorText: ipv6LeaseTimeError,
|
||||||
|
labelText: AppLocalizations.of(context)!.leaseTime,
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
SectionLabel(
|
||||||
|
label: AppLocalizations.of(context)!.dhcpLeases,
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
),
|
||||||
|
if (width <= 900) Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
Navigator.push(context, MaterialPageRoute(
|
||||||
|
builder: (context) => DhcpLeases(
|
||||||
|
items: serversProvider.dhcp.data!.dhcpStatus.leases,
|
||||||
|
staticLeases: false,
|
||||||
|
)
|
||||||
|
));
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Column(
|
Text(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
AppLocalizations.of(context)!.dhcpLeases,
|
||||||
children: [
|
textAlign: TextAlign.center,
|
||||||
Text(
|
style: TextStyle(
|
||||||
AppLocalizations.of(context)!.enableDhcpServer,
|
fontSize: 16,
|
||||||
style: TextStyle(
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
fontSize: 16,
|
),
|
||||||
color: Theme.of(context).colorScheme.onSurface
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (selectedInterface != null) ...[
|
|
||||||
Text(
|
|
||||||
selectedInterface!.name,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
color: Theme.of(context).listTileTheme.textColor
|
|
||||||
),
|
|
||||||
)
|
|
||||||
]
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Switch(
|
|
||||||
value: enabled,
|
|
||||||
onChanged: selectedInterface != null
|
|
||||||
? (value) => setState(() => enabled = value)
|
|
||||||
: null,
|
|
||||||
),
|
),
|
||||||
|
Icon(
|
||||||
|
Icons.arrow_forward_rounded,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
if (width <= 900) Material(
|
||||||
if (selectedInterface!.ipv4Addresses.isNotEmpty) ...[
|
color: Colors.transparent,
|
||||||
SectionLabel(
|
child: InkWell(
|
||||||
label: AppLocalizations.of(context)!.ipv4settings,
|
onTap: () {
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24),
|
Navigator.push(context, MaterialPageRoute(
|
||||||
),
|
builder: (context) => DhcpLeases(
|
||||||
Padding(
|
items: serversProvider.dhcp.data!.dhcpStatus.staticLeases,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
staticLeases: true,
|
||||||
child: TextFormField(
|
|
||||||
controller: ipv4StartRangeController,
|
|
||||||
onChanged: (value) => validateIpV4(value, 'ipv4StartRangeError', AppLocalizations.of(context)!.ipNotValid),
|
|
||||||
decoration: InputDecoration(
|
|
||||||
prefixIcon: const Icon(Icons.skip_previous_rounded),
|
|
||||||
border: const OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(
|
|
||||||
Radius.circular(10)
|
|
||||||
)
|
)
|
||||||
),
|
));
|
||||||
errorText: ipv4StartRangeError,
|
|
||||||
labelText: AppLocalizations.of(context)!.startOfRange,
|
|
||||||
),
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 30),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
||||||
child: TextFormField(
|
|
||||||
controller: ipv4EndRangeController,
|
|
||||||
onChanged: (value) => validateIpV4(value, 'ipv4EndRangeError', AppLocalizations.of(context)!.ipNotValid),
|
|
||||||
decoration: InputDecoration(
|
|
||||||
prefixIcon: const Icon(Icons.skip_next_rounded),
|
|
||||||
border: const OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(
|
|
||||||
Radius.circular(10)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
errorText: ipv4EndRangeError,
|
|
||||||
labelText: AppLocalizations.of(context)!.endOfRange,
|
|
||||||
),
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 30),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
||||||
child: TextFormField(
|
|
||||||
controller: ipv4SubnetMaskController,
|
|
||||||
onChanged: (value) => validateIpV4(value, 'ipv4SubnetMaskError', AppLocalizations.of(context)!.subnetMaskNotValid),
|
|
||||||
decoration: InputDecoration(
|
|
||||||
prefixIcon: const Icon(Icons.hub_rounded),
|
|
||||||
border: const OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(
|
|
||||||
Radius.circular(10)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
errorText: ipv4SubnetMaskError,
|
|
||||||
labelText: AppLocalizations.of(context)!.subnetMask,
|
|
||||||
),
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 30),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
||||||
child: TextFormField(
|
|
||||||
controller: ipv4GatewayController,
|
|
||||||
onChanged: (value) => validateIpV4(value, 'ipv4GatewayError', AppLocalizations.of(context)!.gatewayNotValid),
|
|
||||||
decoration: InputDecoration(
|
|
||||||
prefixIcon: const Icon(Icons.router_rounded),
|
|
||||||
border: const OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(
|
|
||||||
Radius.circular(10)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
errorText: ipv4GatewayError,
|
|
||||||
labelText: AppLocalizations.of(context)!.gateway,
|
|
||||||
),
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 30),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
||||||
child: TextFormField(
|
|
||||||
controller: ipv4LeaseTimeController,
|
|
||||||
onChanged: (value) {
|
|
||||||
if (int.tryParse(value).runtimeType == int) {
|
|
||||||
setState(() => ipv4LeaseTimeError = null);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
setState(() => ipv4LeaseTimeError = AppLocalizations.of(context)!.leaseTimeNotValid);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
decoration: InputDecoration(
|
child: Container(
|
||||||
prefixIcon: const Icon(Icons.timer),
|
padding: const EdgeInsets.all(16),
|
||||||
border: const OutlineInputBorder(
|
child: Row(
|
||||||
borderRadius: BorderRadius.all(
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
Radius.circular(10)
|
children: [
|
||||||
)
|
Text(
|
||||||
),
|
AppLocalizations.of(context)!.dhcpStatic,
|
||||||
errorText: ipv4LeaseTimeError,
|
textAlign: TextAlign.center,
|
||||||
labelText: AppLocalizations.of(context)!.leaseTime,
|
style: TextStyle(
|
||||||
),
|
fontSize: 16,
|
||||||
keyboardType: TextInputType.number,
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
|
||||||
if (selectedInterface!.ipv6Addresses.isNotEmpty) ...[
|
|
||||||
SectionLabel(
|
|
||||||
label: AppLocalizations.of(context)!.ipv6settings,
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
||||||
child: TextFormField(
|
|
||||||
controller: ipv6StartRangeController,
|
|
||||||
onChanged: (value) => validateIpV4(value, 'ipv6StartRangeError', AppLocalizations.of(context)!.ipNotValid),
|
|
||||||
decoration: InputDecoration(
|
|
||||||
prefixIcon: const Icon(Icons.skip_next_rounded),
|
|
||||||
border: const OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(
|
|
||||||
Radius.circular(10)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
errorText: ipv6StartRangeError,
|
|
||||||
labelText: AppLocalizations.of(context)!.startOfRange,
|
|
||||||
),
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 30),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
||||||
child: TextFormField(
|
|
||||||
controller: ipv6EndRangeController,
|
|
||||||
onChanged: (value) => validateIpV4(value, 'ipv6EndRangeError', AppLocalizations.of(context)!.ipNotValid),
|
|
||||||
decoration: InputDecoration(
|
|
||||||
prefixIcon: const Icon(Icons.skip_previous_rounded),
|
|
||||||
border: const OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(
|
|
||||||
Radius.circular(10)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
errorText: ipv6EndRangeError,
|
|
||||||
labelText: AppLocalizations.of(context)!.endOfRange,
|
|
||||||
),
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 30),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
||||||
child: TextFormField(
|
|
||||||
controller: ipv6LeaseTimeController,
|
|
||||||
onChanged: (value) {
|
|
||||||
if (int.tryParse(value).runtimeType == int) {
|
|
||||||
setState(() => ipv6LeaseTimeError = null);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
setState(() => ipv6LeaseTimeError = AppLocalizations.of(context)!.leaseTimeNotValid);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
decoration: InputDecoration(
|
|
||||||
prefixIcon: const Icon(Icons.timer),
|
|
||||||
border: const OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(
|
|
||||||
Radius.circular(10)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
errorText: ipv6LeaseTimeError,
|
|
||||||
labelText: AppLocalizations.of(context)!.leaseTime,
|
|
||||||
),
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
SectionLabel(
|
|
||||||
label: AppLocalizations.of(context)!.dhcpLeases,
|
|
||||||
),
|
|
||||||
Material(
|
|
||||||
color: Colors.transparent,
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () {
|
|
||||||
Navigator.push(context, MaterialPageRoute(
|
|
||||||
builder: (context) => DhcpLeases(
|
|
||||||
items: serversProvider.dhcp.data!.dhcpStatus.leases,
|
|
||||||
staticLeases: false,
|
|
||||||
)
|
|
||||||
));
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
AppLocalizations.of(context)!.dhcpLeases,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
color: Theme.of(context).colorScheme.onSurface,
|
|
||||||
),
|
),
|
||||||
),
|
Icon(
|
||||||
Icon(
|
Icons.arrow_forward_rounded,
|
||||||
Icons.arrow_forward_rounded,
|
|
||||||
color: Theme.of(context).colorScheme.onSurface,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Material(
|
|
||||||
color: Colors.transparent,
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () {
|
|
||||||
Navigator.push(context, MaterialPageRoute(
|
|
||||||
builder: (context) => DhcpLeases(
|
|
||||||
items: serversProvider.dhcp.data!.dhcpStatus.staticLeases,
|
|
||||||
staticLeases: true,
|
|
||||||
)
|
|
||||||
));
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
AppLocalizations.of(context)!.dhcpStatic,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
color: Theme.of(context).colorScheme.onSurface,
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
),
|
)
|
||||||
),
|
],
|
||||||
Icon(
|
),
|
||||||
Icons.arrow_forward_rounded,
|
|
||||||
color: Theme.of(context).colorScheme.onSurface,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
if (width > 900) Row(
|
||||||
const SizedBox(height: 10)
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
],
|
children: [
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
if (!(Platform.isAndroid || Platform.isIOS)) {
|
||||||
|
SplitView.of(context).push(
|
||||||
|
DhcpLeases(
|
||||||
|
items: serversProvider.dhcp.data!.dhcpStatus.leases,
|
||||||
|
staticLeases: false,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Navigator.push(context, MaterialPageRoute(
|
||||||
|
builder: (context) => DhcpLeases(
|
||||||
|
items: serversProvider.dhcp.data!.dhcpStatus.leases,
|
||||||
|
staticLeases: false,
|
||||||
|
)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Text(AppLocalizations.of(context)!.dhcpLeases),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
const Icon(Icons.arrow_forward_rounded)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
if (!(Platform.isAndroid || Platform.isIOS)) {
|
||||||
|
SplitView.of(context).push(
|
||||||
|
DhcpLeases(
|
||||||
|
items: serversProvider.dhcp.data!.dhcpStatus.staticLeases,
|
||||||
|
staticLeases: true,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Navigator.push(context, MaterialPageRoute(
|
||||||
|
builder: (context) => DhcpLeases(
|
||||||
|
items: serversProvider.dhcp.data!.dhcpStatus.staticLeases,
|
||||||
|
staticLeases: true,
|
||||||
|
)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Text(AppLocalizations.of(context)!.dhcpStatic),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
const Icon(Icons.arrow_forward_rounded)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10)
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return Column(
|
return Row(
|
||||||
mainAxisSize: MainAxisSize.max,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Flexible(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
child: Column(
|
||||||
child: Text(
|
mainAxisSize: MainAxisSize.max,
|
||||||
AppLocalizations.of(context)!.neededSelectInterface,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
textAlign: TextAlign.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
style: TextStyle(
|
children: [
|
||||||
fontSize: 22,
|
Padding(
|
||||||
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.5)
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
),
|
child: Text(
|
||||||
|
AppLocalizations.of(context)!.neededSelectInterface,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 22,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.5)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 30),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: selectInterface,
|
||||||
|
child: Text(AppLocalizations.of(context)!.selectInterface)
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 30),
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: selectInterface,
|
|
||||||
child: Text(AppLocalizations.of(context)!.selectInterface)
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
// 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:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:animations/animations.dart';
|
import 'package:animations/animations.dart';
|
||||||
|
@ -30,6 +32,8 @@ class DhcpLeases extends StatelessWidget {
|
||||||
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 width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
void deleteLease(Lease lease) async {
|
void deleteLease(Lease lease) async {
|
||||||
ProcessModal processModal = ProcessModal(context: context);
|
ProcessModal processModal = ProcessModal(context: context);
|
||||||
processModal.open(AppLocalizations.of(context)!.deleting);
|
processModal.open(AppLocalizations.of(context)!.deleting);
|
||||||
|
@ -119,14 +123,26 @@ class DhcpLeases extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
void openAddStaticLease() {
|
void openAddStaticLease() {
|
||||||
showModalBottomSheet(
|
if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) {
|
||||||
context: context,
|
showDialog(
|
||||||
builder: (context) => AddStaticLeaseModal(
|
context: context,
|
||||||
onConfirm: createLease
|
builder: (context) => AddStaticLeaseModal(
|
||||||
),
|
onConfirm: createLease,
|
||||||
backgroundColor: Colors.transparent,
|
dialog: true,
|
||||||
isScrollControlled: true
|
),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AddStaticLeaseModal(
|
||||||
|
onConfirm: createLease,
|
||||||
|
dialog: false,
|
||||||
|
),
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
isScrollControlled: true
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
|
|
@ -7,173 +7,177 @@ import 'package:adguard_home_manager/models/dhcp.dart';
|
||||||
|
|
||||||
class SelectInterfaceModal extends StatelessWidget {
|
class SelectInterfaceModal extends StatelessWidget {
|
||||||
final List<NetworkInterface> interfaces;
|
final List<NetworkInterface> interfaces;
|
||||||
final ScrollController scrollController;
|
|
||||||
final void Function(NetworkInterface) onSelect;
|
final void Function(NetworkInterface) onSelect;
|
||||||
|
final bool dialog;
|
||||||
|
|
||||||
const SelectInterfaceModal({
|
const SelectInterfaceModal({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.interfaces,
|
required this.interfaces,
|
||||||
required this.scrollController,
|
|
||||||
required this.onSelect,
|
required this.onSelect,
|
||||||
|
required this.dialog
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
Widget content() {
|
||||||
decoration: BoxDecoration(
|
return Column(
|
||||||
color: Theme.of(context).dialogBackgroundColor,
|
mainAxisSize: MainAxisSize.min,
|
||||||
borderRadius: const BorderRadius.only(
|
|
||||||
topLeft: Radius.circular(28),
|
|
||||||
topRight: Radius.circular(28)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Flexible(
|
||||||
child: ListView(
|
child: SingleChildScrollView(
|
||||||
controller: scrollController,
|
child: Wrap(
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Row(
|
||||||
padding: const EdgeInsets.only(top: 24),
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
child: Icon(
|
children: [
|
||||||
Icons.settings_ethernet_rounded,
|
Column(
|
||||||
size: 24,
|
children: [
|
||||||
color: Theme.of(context).listTileTheme.iconColor
|
Padding(
|
||||||
),
|
padding: const EdgeInsets.only(top: 24),
|
||||||
),
|
child: Icon(
|
||||||
const SizedBox(height: 16),
|
Icons.settings_ethernet_rounded,
|
||||||
Text(
|
size: 24,
|
||||||
AppLocalizations.of(context)!.selectInterface,
|
color: Theme.of(context).listTileTheme.iconColor
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 24,
|
|
||||||
color: Theme.of(context).colorScheme.onSurface
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
ListView.builder(
|
|
||||||
primary: false,
|
|
||||||
shrinkWrap: true,
|
|
||||||
itemCount: interfaces.length,
|
|
||||||
itemBuilder: (context, index) => Material(
|
|
||||||
color: Colors.transparent,
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () {
|
|
||||||
Navigator.pop(context);
|
|
||||||
onSelect(interfaces[index]);
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
interfaces[index].name,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
color: Theme.of(context).colorScheme.onSurface
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Row(
|
),
|
||||||
children: [
|
const SizedBox(height: 16),
|
||||||
Text(
|
Text(
|
||||||
"${AppLocalizations.of(context)!.hardwareAddress}: ",
|
AppLocalizations.of(context)!.selectInterface,
|
||||||
style: TextStyle(
|
textAlign: TextAlign.center,
|
||||||
fontSize: 14,
|
style: TextStyle(
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant
|
fontSize: 24,
|
||||||
),
|
color: Theme.of(context).colorScheme.onSurface
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
ListView.builder(
|
||||||
|
primary: false,
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemCount: interfaces.length,
|
||||||
|
itemBuilder: (context, index) => Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
onSelect(interfaces[index]);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
interfaces[index].name,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface
|
||||||
),
|
),
|
||||||
Text(
|
),
|
||||||
interfaces[index].hardwareAddress,
|
Row(
|
||||||
style: TextStyle(
|
children: [
|
||||||
fontSize: 14,
|
Text(
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant
|
"${AppLocalizations.of(context)!.hardwareAddress}: ",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
Text(
|
||||||
|
interfaces[index].hardwareAddress,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 5),
|
||||||
|
if (interfaces[index].flags.isNotEmpty) ...[
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Flags: ",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
interfaces[index].flags.join(', '),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 5),
|
||||||
],
|
],
|
||||||
),
|
if (interfaces[index].gatewayIp != '') ...[
|
||||||
const SizedBox(height: 5),
|
Row(
|
||||||
if (interfaces[index].flags.isNotEmpty) ...[
|
children: [
|
||||||
Row(
|
Text(
|
||||||
children: [
|
"${AppLocalizations.of(context)!.gatewayIp}: ",
|
||||||
Text(
|
|
||||||
"Flags: ",
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
interfaces[index].flags.join(', '),
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 5),
|
|
||||||
],
|
|
||||||
if (interfaces[index].gatewayIp != '') ...[
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
"${AppLocalizations.of(context)!.gatewayIp}: ",
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
interfaces[index].gatewayIp,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 5),
|
|
||||||
],
|
|
||||||
if (interfaces[index].ipv4Addresses.isNotEmpty) ...[
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Flexible(
|
|
||||||
child: Text(
|
|
||||||
"${AppLocalizations.of(context)!.ipv4addresses}: ${interfaces[index].ipv4Addresses.join(', ')}",
|
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant
|
color: Theme.of(context).colorScheme.onSurfaceVariant
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
Text(
|
||||||
],
|
interfaces[index].gatewayIp,
|
||||||
),
|
|
||||||
const SizedBox(height: 5),
|
|
||||||
],
|
|
||||||
if (interfaces[index].ipv6Addresses.isNotEmpty) ...[
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Flexible(
|
|
||||||
child: Text(
|
|
||||||
"${AppLocalizations.of(context)!.ipv6addresses}: ${interfaces[index].ipv6Addresses.join(', ')}",
|
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant
|
color: Theme.of(context).colorScheme.onSurfaceVariant
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
],
|
||||||
],
|
),
|
||||||
),
|
const SizedBox(height: 5),
|
||||||
]
|
],
|
||||||
],
|
if (interfaces[index].ipv4Addresses.isNotEmpty) ...[
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
child: Text(
|
||||||
|
"${AppLocalizations.of(context)!.ipv4addresses}: ${interfaces[index].ipv4Addresses.join(', ')}",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 5),
|
||||||
|
],
|
||||||
|
if (interfaces[index].ipv6Addresses.isNotEmpty) ...[
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
child: Text(
|
||||||
|
"${AppLocalizations.of(context)!.ipv6addresses}: ${interfaces[index].ipv6Addresses.join(', ')}",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
)
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
|
@ -190,7 +194,30 @@ class SelectInterfaceModal extends StatelessWidget {
|
||||||
),
|
),
|
||||||
if (Platform.isIOS) const SizedBox(height: 16)
|
if (Platform.isIOS) const SizedBox(height: 16)
|
||||||
],
|
],
|
||||||
),
|
);
|
||||||
);
|
}
|
||||||
|
|
||||||
|
if (dialog == true) {
|
||||||
|
return Dialog(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
maxWidth: 500
|
||||||
|
),
|
||||||
|
child: content()
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).dialogBackgroundColor,
|
||||||
|
borderRadius: const BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(28),
|
||||||
|
topRight: Radius.circular(28)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
child: content()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -182,8 +182,7 @@ class _BootstrapDnsScreenState extends State<BootstrapDnsScreen> {
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
Expanded(
|
||||||
width: MediaQuery.of(context).size.width-74,
|
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
controller: c['controller'],
|
controller: c['controller'],
|
||||||
onChanged: (value) => validateIp(c, value),
|
onChanged: (value) => validateIp(c, value),
|
||||||
|
@ -199,6 +198,7 @@ class _BootstrapDnsScreenState extends State<BootstrapDnsScreen> {
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
setState(() => bootstrapControllers = bootstrapControllers.where((con) => con != c).toList());
|
setState(() => bootstrapControllers = bootstrapControllers.where((con) => con != c).toList());
|
||||||
|
|
|
@ -257,7 +257,8 @@ class _CacheConfigDnsScreenState extends State<CacheConfigDnsScreen> {
|
||||||
label: Text(AppLocalizations.of(context)!.clearDnsCache),
|
label: Text(AppLocalizations.of(context)!.clearDnsCache),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
),
|
||||||
|
const SizedBox(height: 16)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -4,11 +4,13 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
class CommentModal extends StatefulWidget {
|
class CommentModal extends StatefulWidget {
|
||||||
final String? comment;
|
final String? comment;
|
||||||
final void Function(String) onConfirm;
|
final void Function(String) onConfirm;
|
||||||
|
final bool dialog;
|
||||||
|
|
||||||
const CommentModal({
|
const CommentModal({
|
||||||
Key? key,
|
Key? key,
|
||||||
this.comment,
|
this.comment,
|
||||||
required this.onConfirm
|
required this.onConfirm,
|
||||||
|
required this.dialog
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -30,43 +32,41 @@ class _CommentModalState extends State<CommentModal> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Padding(
|
Widget content() {
|
||||||
padding: MediaQuery.of(context).viewInsets,
|
return Column(
|
||||||
child: Container(
|
mainAxisSize: MainAxisSize.min,
|
||||||
height: 310,
|
children: [
|
||||||
decoration: BoxDecoration(
|
Flexible(
|
||||||
borderRadius: const BorderRadius.only(
|
child: SingleChildScrollView(
|
||||||
topLeft: Radius.circular(28),
|
child: Wrap(
|
||||||
topRight: Radius.circular(28)
|
|
||||||
),
|
|
||||||
color: Theme.of(context).dialogBackgroundColor
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: ListView(
|
|
||||||
physics: MediaQuery.of(context).size.height >= 330 == true
|
|
||||||
? const NeverScrollableScrollPhysics()
|
|
||||||
: null,
|
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Row(
|
||||||
padding: const EdgeInsets.only(top: 24),
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
child: Icon(
|
children: [
|
||||||
Icons.comment_rounded,
|
Column(
|
||||||
size: 24,
|
children: [
|
||||||
color: Theme.of(context).colorScheme.secondary,
|
Padding(
|
||||||
),
|
padding: const EdgeInsets.only(top: 24),
|
||||||
|
child: Icon(
|
||||||
|
Icons.comment_rounded,
|
||||||
|
size: 24,
|
||||||
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context)!.comment,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 24,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
|
||||||
Text(
|
|
||||||
AppLocalizations.of(context)!.comment,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 24,
|
|
||||||
color: Theme.of(context).colorScheme.onSurface
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
|
@ -95,38 +95,64 @@ class _CommentModalState extends State<CommentModal> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
),
|
||||||
padding: const EdgeInsets.all(24),
|
Padding(
|
||||||
child: Row(
|
padding: const EdgeInsets.all(24),
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
child: Row(
|
||||||
children: [
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
TextButton(
|
children: [
|
||||||
onPressed: () => Navigator.pop(context),
|
TextButton(
|
||||||
child: Text(AppLocalizations.of(context)!.cancel)
|
onPressed: () => Navigator.pop(context),
|
||||||
),
|
child: Text(AppLocalizations.of(context)!.cancel)
|
||||||
const SizedBox(width: 20),
|
),
|
||||||
TextButton(
|
const SizedBox(width: 20),
|
||||||
onPressed: validData == true
|
TextButton(
|
||||||
? () {
|
onPressed: validData == true
|
||||||
Navigator.pop(context);
|
? () {
|
||||||
widget.onConfirm("# ${commentController.text}");
|
Navigator.pop(context);
|
||||||
}
|
widget.onConfirm("# ${commentController.text}");
|
||||||
: null,
|
}
|
||||||
child: Text(
|
: null,
|
||||||
AppLocalizations.of(context)!.confirm,
|
child: Text(
|
||||||
style: TextStyle(
|
AppLocalizations.of(context)!.confirm,
|
||||||
color: validData == true
|
style: TextStyle(
|
||||||
? Theme.of(context).colorScheme.primary
|
color: validData == true
|
||||||
: Colors.grey
|
? Theme.of(context).colorScheme.primary
|
||||||
),
|
: Colors.grey
|
||||||
)
|
),
|
||||||
),
|
)
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
)
|
),
|
||||||
],
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (widget.dialog == true) {
|
||||||
|
return Dialog(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
maxWidth: 400
|
||||||
|
),
|
||||||
|
child: content()
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
}
|
||||||
|
else {
|
||||||
|
return Padding(
|
||||||
|
padding: MediaQuery.of(context).viewInsets,
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: const BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(28),
|
||||||
|
topRight: Radius.circular(28)
|
||||||
|
),
|
||||||
|
color: Theme.of(context).dialogBackgroundColor
|
||||||
|
),
|
||||||
|
child: content()
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,6 +1,9 @@
|
||||||
// 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:flutter_split_view/flutter_split_view.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';
|
||||||
|
|
||||||
|
@ -76,6 +79,19 @@ class _DnsSettingsWidgetState extends State<DnsSettingsWidget> {
|
||||||
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 width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
|
void navigate(Widget widget) {
|
||||||
|
if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) {
|
||||||
|
SplitView.of(context).push(widget);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Navigator.push(context, MaterialPageRoute(
|
||||||
|
builder: (context) => widget
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Widget generateBody() {
|
Widget generateBody() {
|
||||||
switch (widget.serversProvider.dnsInfo.loadStatus) {
|
switch (widget.serversProvider.dnsInfo.loadStatus) {
|
||||||
case 0:
|
case 0:
|
||||||
|
@ -105,51 +121,51 @@ class _DnsSettingsWidgetState extends State<DnsSettingsWidget> {
|
||||||
CustomListTile(
|
CustomListTile(
|
||||||
title: AppLocalizations.of(context)!.upstreamDns,
|
title: AppLocalizations.of(context)!.upstreamDns,
|
||||||
subtitle: AppLocalizations.of(context)!.upstreamDnsDescription,
|
subtitle: AppLocalizations.of(context)!.upstreamDnsDescription,
|
||||||
onTap: () => Navigator.push(context, MaterialPageRoute(
|
onTap: () => navigate(
|
||||||
builder: (context) => UpstreamDnsScreen(
|
UpstreamDnsScreen(
|
||||||
serversProvider: serversProvider
|
serversProvider: serversProvider
|
||||||
)
|
)
|
||||||
)),
|
),
|
||||||
icon: Icons.upload_rounded,
|
icon: Icons.upload_rounded,
|
||||||
),
|
),
|
||||||
CustomListTile(
|
CustomListTile(
|
||||||
title: AppLocalizations.of(context)!.bootstrapDns,
|
title: AppLocalizations.of(context)!.bootstrapDns,
|
||||||
subtitle: AppLocalizations.of(context)!.bootstrapDnsDescription,
|
subtitle: AppLocalizations.of(context)!.bootstrapDnsDescription,
|
||||||
onTap: () => Navigator.push(context, MaterialPageRoute(
|
onTap: () => navigate(
|
||||||
builder: (context) => BootstrapDnsScreen(
|
BootstrapDnsScreen(
|
||||||
serversProvider: serversProvider
|
serversProvider: serversProvider
|
||||||
)
|
)
|
||||||
)),
|
),
|
||||||
icon: Icons.dns_rounded,
|
icon: Icons.dns_rounded,
|
||||||
),
|
),
|
||||||
CustomListTile(
|
CustomListTile(
|
||||||
title: AppLocalizations.of(context)!.privateReverseDnsServers,
|
title: AppLocalizations.of(context)!.privateReverseDnsServers,
|
||||||
subtitle: AppLocalizations.of(context)!.privateReverseDnsDescription,
|
subtitle: AppLocalizations.of(context)!.privateReverseDnsDescription,
|
||||||
onTap: () => Navigator.push(context, MaterialPageRoute(
|
onTap: () => navigate(
|
||||||
builder: (context) => PrivateReverseDnsServersScreen(
|
PrivateReverseDnsServersScreen(
|
||||||
serversProvider: serversProvider
|
serversProvider: serversProvider
|
||||||
)
|
)
|
||||||
)),
|
),
|
||||||
icon: Icons.person_rounded,
|
icon: Icons.person_rounded,
|
||||||
),
|
),
|
||||||
CustomListTile(
|
CustomListTile(
|
||||||
title: AppLocalizations.of(context)!.dnsServerSettings,
|
title: AppLocalizations.of(context)!.dnsServerSettings,
|
||||||
subtitle: AppLocalizations.of(context)!.dnsServerSettingsDescription,
|
subtitle: AppLocalizations.of(context)!.dnsServerSettingsDescription,
|
||||||
onTap: () => Navigator.push(context, MaterialPageRoute(
|
onTap: () => navigate(
|
||||||
builder: (context) => DnsServerSettingsScreen(
|
DnsServerSettingsScreen(
|
||||||
serversProvider: serversProvider
|
serversProvider: serversProvider
|
||||||
)
|
)
|
||||||
)),
|
),
|
||||||
icon: Icons.settings,
|
icon: Icons.settings,
|
||||||
),
|
),
|
||||||
CustomListTile(
|
CustomListTile(
|
||||||
title: AppLocalizations.of(context)!.dnsCacheConfig,
|
title: AppLocalizations.of(context)!.dnsCacheConfig,
|
||||||
subtitle: AppLocalizations.of(context)!.dnsCacheConfigDescription,
|
subtitle: AppLocalizations.of(context)!.dnsCacheConfigDescription,
|
||||||
onTap: () => Navigator.push(context, MaterialPageRoute(
|
onTap: () => navigate(
|
||||||
builder: (context) => CacheConfigDnsScreen(
|
CacheConfigDnsScreen(
|
||||||
serversProvider: serversProvider
|
serversProvider: serversProvider
|
||||||
)
|
)
|
||||||
)),
|
),
|
||||||
icon: Icons.storage_rounded,
|
icon: Icons.storage_rounded,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
@ -229,8 +229,7 @@ class _PrivateReverseDnsServersScreenState extends State<PrivateReverseDnsServer
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
Expanded(
|
||||||
width: MediaQuery.of(context).size.width-74,
|
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
controller: c['controller'],
|
controller: c['controller'],
|
||||||
onChanged: (value) => validateAddress(c, value),
|
onChanged: (value) => validateAddress(c, value),
|
||||||
|
@ -246,6 +245,7 @@ class _PrivateReverseDnsServersScreenState extends State<PrivateReverseDnsServer
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
setState(() => reverseResolversControllers = reverseResolversControllers.where((con) => con != c).toList());
|
setState(() => reverseResolversControllers = reverseResolversControllers.where((con) => con != c).toList());
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
// 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: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';
|
||||||
|
@ -71,36 +73,73 @@ class _UpstreamDnsScreenState extends State<UpstreamDnsScreen> {
|
||||||
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 width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
void openAddCommentModal() {
|
void openAddCommentModal() {
|
||||||
showModalBottomSheet(
|
if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) {
|
||||||
context: context,
|
showDialog(
|
||||||
builder: (context) => CommentModal(
|
context: context,
|
||||||
onConfirm: (value) {
|
builder: (context) => CommentModal(
|
||||||
dnsServers.add({
|
onConfirm: (value) {
|
||||||
'comment': value
|
setState(() {
|
||||||
});
|
dnsServers.add({
|
||||||
},
|
'comment': value
|
||||||
),
|
});
|
||||||
backgroundColor: Colors.transparent,
|
});
|
||||||
isScrollControlled: true,
|
},
|
||||||
isDismissible: true
|
dialog: true,
|
||||||
);
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => CommentModal(
|
||||||
|
onConfirm: (value) {
|
||||||
|
setState(() {
|
||||||
|
dnsServers.add({
|
||||||
|
'comment': value
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
dialog: false,
|
||||||
|
),
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
isScrollControlled: true,
|
||||||
|
isDismissible: true
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void openEditCommentModal(Map<String, dynamic> item, int position) {
|
void openEditCommentModal(Map<String, dynamic> item, int position) {
|
||||||
showModalBottomSheet(
|
if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) {
|
||||||
context: context,
|
showDialog(
|
||||||
builder: (context) => CommentModal(
|
context: context,
|
||||||
comment: item['comment'],
|
builder: (context) => CommentModal(
|
||||||
onConfirm: (value) {
|
comment: item['comment'],
|
||||||
setState(() => dnsServers[position] = { 'comment': value });
|
onConfirm: (value) {
|
||||||
},
|
setState(() => dnsServers[position] = { 'comment': value });
|
||||||
),
|
},
|
||||||
backgroundColor: Colors.transparent,
|
dialog: true,
|
||||||
isScrollControlled: true,
|
),
|
||||||
isDismissible: true
|
);
|
||||||
);
|
}
|
||||||
|
else {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => CommentModal(
|
||||||
|
comment: item['comment'],
|
||||||
|
onConfirm: (value) {
|
||||||
|
setState(() => dnsServers[position] = { 'comment': value });
|
||||||
|
},
|
||||||
|
dialog: false,
|
||||||
|
),
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
isScrollControlled: true,
|
||||||
|
isDismissible: true
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void saveData() async {
|
void saveData() async {
|
||||||
|
@ -185,13 +224,12 @@ class _UpstreamDnsScreenState extends State<UpstreamDnsScreen> {
|
||||||
),
|
),
|
||||||
...dnsServers.map((item) => Padding(
|
...dnsServers.map((item) => Padding(
|
||||||
padding: const EdgeInsets.only(
|
padding: const EdgeInsets.only(
|
||||||
left: 16, right: 6, bottom: 20
|
left: 16, right: 6, bottom: 24
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
if (item['controller'] != null) SizedBox(
|
if (item['controller'] != null) Expanded(
|
||||||
width: MediaQuery.of(context).size.width-74,
|
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
controller: item['controller'],
|
controller: item['controller'],
|
||||||
onChanged: (_) => checkValidValues(),
|
onChanged: (_) => checkValidValues(),
|
||||||
|
@ -206,6 +244,7 @@ class _UpstreamDnsScreenState extends State<UpstreamDnsScreen> {
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
if (item['comment'] != null) Expanded(
|
if (item['comment'] != null) Expanded(
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
@ -232,10 +271,12 @@ class _UpstreamDnsScreenState extends State<UpstreamDnsScreen> {
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.remove_circle_outline),
|
icon: const Icon(Icons.remove_circle_outline),
|
||||||
tooltip: AppLocalizations.of(context)!.remove,
|
tooltip: AppLocalizations.of(context)!.remove,
|
||||||
)
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)).toList(),
|
)).toList(),
|
||||||
|
const SizedBox(height: 12),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
|
|
@ -7,10 +7,12 @@ import 'package:adguard_home_manager/models/rewrite_rules.dart';
|
||||||
|
|
||||||
class AddDnsRewriteModal extends StatefulWidget {
|
class AddDnsRewriteModal extends StatefulWidget {
|
||||||
final void Function(RewriteRulesData) onConfirm;
|
final void Function(RewriteRulesData) onConfirm;
|
||||||
|
final bool dialog;
|
||||||
|
|
||||||
const AddDnsRewriteModal({
|
const AddDnsRewriteModal({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.onConfirm
|
required this.onConfirm,
|
||||||
|
required this.dialog
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -50,45 +52,45 @@ class _AddDnsRewriteModalState extends State<AddDnsRewriteModal> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Padding(
|
Widget content() {
|
||||||
padding: MediaQuery.of(context).viewInsets,
|
return Column(
|
||||||
child: Container(
|
mainAxisSize: MainAxisSize.min,
|
||||||
height: Platform.isIOS ? 416 : 400,
|
children: [
|
||||||
decoration: BoxDecoration(
|
Flexible(
|
||||||
borderRadius: const BorderRadius.only(
|
child: SingleChildScrollView(
|
||||||
topLeft: Radius.circular(28),
|
child: Wrap(
|
||||||
topRight: Radius.circular(28)
|
|
||||||
),
|
|
||||||
color: Theme.of(context).dialogBackgroundColor,
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: ListView(
|
|
||||||
physics: (Platform.isIOS ? 426 : 410) < MediaQuery.of(context).size.height
|
|
||||||
? const NeverScrollableScrollPhysics()
|
|
||||||
: null,
|
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Row(
|
||||||
padding: const EdgeInsets.only(top: 24),
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
child: Icon(
|
children: [
|
||||||
Icons.add,
|
Column(
|
||||||
size: 24,
|
children: [
|
||||||
color: Theme.of(context).listTileTheme.iconColor
|
Padding(
|
||||||
),
|
padding: const EdgeInsets.only(top: 24),
|
||||||
|
child: Icon(
|
||||||
|
Icons.add,
|
||||||
|
size: 24,
|
||||||
|
color: Theme.of(context).listTileTheme.iconColor
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context)!.addDnsRewrite,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 24,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
|
||||||
Text(
|
|
||||||
AppLocalizations.of(context)!.addDnsRewrite,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 24,
|
|
||||||
color: Theme.of(context).colorScheme.onSurface
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
padding: const EdgeInsets.only(
|
||||||
|
left: 24, right: 24, bottom: 12
|
||||||
|
),
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
controller: domainController,
|
controller: domainController,
|
||||||
onChanged: validateDomain,
|
onChanged: validateDomain,
|
||||||
|
@ -104,9 +106,10 @@ class _AddDnsRewriteModalState extends State<AddDnsRewriteModal> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 30),
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
padding: const EdgeInsets.only(
|
||||||
|
left: 24, right: 24, top: 12
|
||||||
|
),
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
controller: answerController,
|
controller: answerController,
|
||||||
onChanged: (_) => checkValidValues(),
|
onChanged: (_) => checkValidValues(),
|
||||||
|
@ -124,44 +127,70 @@ class _AddDnsRewriteModalState extends State<AddDnsRewriteModal> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
),
|
||||||
padding: const EdgeInsets.all(24),
|
Padding(
|
||||||
child: Row(
|
padding: const EdgeInsets.all(24),
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
child: Row(
|
||||||
children: [
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
TextButton(
|
children: [
|
||||||
onPressed: () => Navigator.pop(context),
|
TextButton(
|
||||||
child: Text(AppLocalizations.of(context)!.cancel),
|
onPressed: () => Navigator.pop(context),
|
||||||
),
|
child: Text(AppLocalizations.of(context)!.cancel),
|
||||||
const SizedBox(width: 20),
|
),
|
||||||
TextButton(
|
const SizedBox(width: 20),
|
||||||
onPressed: validData == true
|
TextButton(
|
||||||
? () {
|
onPressed: validData == true
|
||||||
Navigator.pop(context);
|
? () {
|
||||||
widget.onConfirm(
|
Navigator.pop(context);
|
||||||
RewriteRulesData(
|
widget.onConfirm(
|
||||||
domain: domainController.text,
|
RewriteRulesData(
|
||||||
answer: answerController.text
|
domain: domainController.text,
|
||||||
)
|
answer: answerController.text
|
||||||
);
|
)
|
||||||
}
|
);
|
||||||
: null,
|
}
|
||||||
child: Text(
|
: null,
|
||||||
AppLocalizations.of(context)!.confirm,
|
child: Text(
|
||||||
style: TextStyle(
|
AppLocalizations.of(context)!.confirm,
|
||||||
color: validData == true
|
style: TextStyle(
|
||||||
? Theme.of(context).colorScheme.primary
|
color: validData == true
|
||||||
: Theme.of(context).colorScheme.onSurface.withOpacity(0.38)
|
? Theme.of(context).colorScheme.primary
|
||||||
),
|
: Theme.of(context).colorScheme.onSurface.withOpacity(0.38)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
if (Platform.isIOS) const SizedBox(height: 16)
|
),
|
||||||
],
|
if (Platform.isIOS) const SizedBox(height: 16)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (widget.dialog == true) {
|
||||||
|
return Dialog(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
maxWidth: 400
|
||||||
|
),
|
||||||
|
child: content()
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
}
|
||||||
|
else {
|
||||||
|
return Padding(
|
||||||
|
padding: MediaQuery.of(context).viewInsets,
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: const BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(28),
|
||||||
|
topRight: Radius.circular(28)
|
||||||
|
),
|
||||||
|
color: Theme.of(context).dialogBackgroundColor,
|
||||||
|
),
|
||||||
|
child: content()
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
// 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: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 +72,8 @@ class _DnsRewritesWidgetState extends State<DnsRewritesWidget> {
|
||||||
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 width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
void deleteDnsRewrite(RewriteRulesData rule) async {
|
void deleteDnsRewrite(RewriteRulesData rule) async {
|
||||||
ProcessModal processModal = ProcessModal(context: context);
|
ProcessModal processModal = ProcessModal(context: context);
|
||||||
processModal.open(AppLocalizations.of(context)!.deleting);
|
processModal.open(AppLocalizations.of(context)!.deleting);
|
||||||
|
@ -288,14 +292,26 @@ class _DnsRewritesWidgetState extends State<DnsRewritesWidget> {
|
||||||
body: generateBody(),
|
body: generateBody(),
|
||||||
floatingActionButton: FloatingActionButton(
|
floatingActionButton: FloatingActionButton(
|
||||||
onPressed: () => {
|
onPressed: () => {
|
||||||
showModalBottomSheet(
|
if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) {
|
||||||
context: context,
|
showDialog(
|
||||||
builder: (context) => AddDnsRewriteModal(
|
context: context,
|
||||||
onConfirm: addDnsRewrite,
|
builder: (context) => AddDnsRewriteModal(
|
||||||
),
|
onConfirm: addDnsRewrite,
|
||||||
backgroundColor: Colors.transparent,
|
dialog: true,
|
||||||
isScrollControlled: true
|
),
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AddDnsRewriteModal(
|
||||||
|
onConfirm: addDnsRewrite,
|
||||||
|
dialog: false,
|
||||||
|
),
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
isScrollControlled: true
|
||||||
|
)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
child: const Icon(Icons.add),
|
child: const Icon(Icons.add),
|
||||||
),
|
),
|
||||||
|
|
|
@ -26,8 +26,12 @@ class EncryptionTextField extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
padding: width > 900
|
||||||
|
? const EdgeInsets.symmetric(horizontal: 8)
|
||||||
|
: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
constraints: const BoxConstraints(
|
constraints: const BoxConstraints(
|
||||||
maxHeight: 200
|
maxHeight: 200
|
||||||
|
|
|
@ -234,6 +234,8 @@ class _EncryptionSettingsWidgetState extends State<EncryptionSettingsWidget> {
|
||||||
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 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);
|
||||||
|
@ -343,43 +345,59 @@ class _EncryptionSettingsWidgetState extends State<EncryptionSettingsWidget> {
|
||||||
disabled: !enabled,
|
disabled: !enabled,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
EncryptionTextField(
|
Wrap(
|
||||||
enabled: enabled,
|
children: [
|
||||||
controller: httpsPortController,
|
FractionallySizedBox(
|
||||||
icon: Icons.numbers_rounded,
|
widthFactor: width > 900 ? 0.33 : 1,
|
||||||
onChanged: (value) {
|
child: EncryptionTextField(
|
||||||
setState(() => httpsPortError = validatePort(context, value));
|
enabled: enabled,
|
||||||
onEditValidate();
|
controller: httpsPortController,
|
||||||
},
|
icon: Icons.numbers_rounded,
|
||||||
errorText: httpsPortError,
|
onChanged: (value) {
|
||||||
label: AppLocalizations.of(context)!.httpsPort,
|
setState(() => httpsPortError = validatePort(context, value));
|
||||||
keyboardType: TextInputType.number,
|
onEditValidate();
|
||||||
),
|
},
|
||||||
const SizedBox(height: 30),
|
errorText: httpsPortError,
|
||||||
EncryptionTextField(
|
label: AppLocalizations.of(context)!.httpsPort,
|
||||||
enabled: enabled,
|
keyboardType: TextInputType.number,
|
||||||
controller: tlsPortController,
|
),
|
||||||
icon: Icons.numbers_rounded,
|
),
|
||||||
onChanged: (value) {
|
Padding(
|
||||||
setState(() => tlsPortError = validatePort(context, value));
|
padding: width <= 900
|
||||||
onEditValidate();
|
? const EdgeInsets.symmetric(vertical: 24)
|
||||||
},
|
: const EdgeInsets.all(0),
|
||||||
errorText: tlsPortError,
|
child: FractionallySizedBox(
|
||||||
label: AppLocalizations.of(context)!.tlsPort,
|
widthFactor: width > 900 ? 0.33 : 1,
|
||||||
keyboardType: TextInputType.number,
|
child: EncryptionTextField(
|
||||||
),
|
enabled: enabled,
|
||||||
const SizedBox(height: 30),
|
controller: tlsPortController,
|
||||||
EncryptionTextField(
|
icon: Icons.numbers_rounded,
|
||||||
enabled: enabled,
|
onChanged: (value) {
|
||||||
controller: dnsOverQuicPortController,
|
setState(() => tlsPortError = validatePort(context, value));
|
||||||
icon: Icons.numbers_rounded,
|
onEditValidate();
|
||||||
onChanged: (value) {
|
},
|
||||||
setState(() => dnsOverQuicPortError = validatePort(context, value));
|
errorText: tlsPortError,
|
||||||
onEditValidate();
|
label: AppLocalizations.of(context)!.tlsPort,
|
||||||
},
|
keyboardType: TextInputType.number,
|
||||||
errorText: dnsOverQuicPortError,
|
),
|
||||||
label: AppLocalizations.of(context)!.dnsOverQuicPort,
|
),
|
||||||
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(
|
SectionLabel(
|
||||||
label: AppLocalizations.of(context)!.certificates,
|
label: AppLocalizations.of(context)!.certificates,
|
||||||
|
|
|
@ -52,24 +52,26 @@ class _SafeSearchSettingsScreenWidgetState extends State<SafeSearchSettingsScree
|
||||||
bool youtubeEnabled = false;
|
bool youtubeEnabled = false;
|
||||||
|
|
||||||
Future requestSafeSearchSettings() async {
|
Future requestSafeSearchSettings() async {
|
||||||
final result = await getServerStatus(widget.serversProvider.selectedServer!);
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
if (result['result'] == 'success') {
|
final result = await getServerStatus(widget.serversProvider.selectedServer!);
|
||||||
widget.serversProvider.setServerStatusData(result['data']);
|
if (mounted) {
|
||||||
widget.serversProvider.setServerStatusLoad(1);
|
if (result['result'] == 'success') {
|
||||||
setState(() {
|
widget.serversProvider.setServerStatusData(result['data']);
|
||||||
generalEnabled = result['data'].safeSearchEnabled;
|
widget.serversProvider.setServerStatusLoad(1);
|
||||||
bingEnabled = result['data'].safeSeachBing;
|
setState(() {
|
||||||
duckduckgoEnabled = result['data'].safeSearchDuckduckgo;
|
generalEnabled = result['data'].safeSearchEnabled;
|
||||||
googleEnabled = result['data'].safeSearchGoogle;
|
bingEnabled = result['data'].safeSeachBing;
|
||||||
pixabayEnabled = result['data'].safeSearchPixabay;
|
duckduckgoEnabled = result['data'].safeSearchDuckduckgo;
|
||||||
yandexEnabled = result['data'].safeSearchYandex;
|
googleEnabled = result['data'].safeSearchGoogle;
|
||||||
youtubeEnabled = result['data'].safeSearchYoutube;
|
pixabayEnabled = result['data'].safeSearchPixabay;
|
||||||
});
|
yandexEnabled = result['data'].safeSearchYandex;
|
||||||
}
|
youtubeEnabled = result['data'].safeSearchYoutube;
|
||||||
else {
|
});
|
||||||
widget.appConfigProvider.addLog(result['log']);
|
}
|
||||||
widget.serversProvider.setServerStatusLoad(2);
|
else {
|
||||||
|
widget.appConfigProvider.addLog(result['log']);
|
||||||
|
widget.serversProvider.setServerStatusLoad(2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
import 'package:flutter_split_view/flutter_split_view.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/settings/server_info/server_info.dart';
|
import 'package:adguard_home_manager/screens/settings/server_info/server_info.dart';
|
||||||
|
@ -9,7 +12,6 @@ import 'package:adguard_home_manager/screens/settings/access_settings/access_set
|
||||||
import 'package:adguard_home_manager/screens/settings/customization/customization.dart';
|
import 'package:adguard_home_manager/screens/settings/customization/customization.dart';
|
||||||
import 'package:adguard_home_manager/screens/settings/dhcp/dhcp.dart';
|
import 'package:adguard_home_manager/screens/settings/dhcp/dhcp.dart';
|
||||||
import 'package:adguard_home_manager/screens/settings/safe_search_settings.dart';
|
import 'package:adguard_home_manager/screens/settings/safe_search_settings.dart';
|
||||||
import 'package:adguard_home_manager/widgets/section_label.dart';
|
|
||||||
import 'package:adguard_home_manager/screens/settings/update_server/update.dart';
|
import 'package:adguard_home_manager/screens/settings/update_server/update.dart';
|
||||||
import 'package:adguard_home_manager/screens/settings/dns/dns.dart';
|
import 'package:adguard_home_manager/screens/settings/dns/dns.dart';
|
||||||
import 'package:adguard_home_manager/screens/settings/dns_rewrites/dns_rewrites.dart';
|
import 'package:adguard_home_manager/screens/settings/dns_rewrites/dns_rewrites.dart';
|
||||||
|
@ -17,6 +19,8 @@ import 'package:adguard_home_manager/screens/servers/servers.dart';
|
||||||
import 'package:adguard_home_manager/screens/settings/advanced_setings.dart';
|
import 'package:adguard_home_manager/screens/settings/advanced_setings.dart';
|
||||||
import 'package:adguard_home_manager/screens/settings/general_settings.dart';
|
import 'package:adguard_home_manager/screens/settings/general_settings.dart';
|
||||||
|
|
||||||
|
import 'package:adguard_home_manager/widgets/custom_settings_tile.dart';
|
||||||
|
import 'package:adguard_home_manager/widgets/section_label.dart';
|
||||||
import 'package:adguard_home_manager/widgets/custom_list_tile.dart';
|
import 'package:adguard_home_manager/widgets/custom_list_tile.dart';
|
||||||
|
|
||||||
import 'package:adguard_home_manager/constants/strings.dart';
|
import 'package:adguard_home_manager/constants/strings.dart';
|
||||||
|
@ -29,18 +33,85 @@ import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
||||||
class Settings extends StatelessWidget {
|
class Settings extends StatelessWidget {
|
||||||
const Settings({Key? key}) : super(key: key);
|
const Settings({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
|
if (width > 900) {
|
||||||
|
return SplitView.material(
|
||||||
|
hideDivider: true,
|
||||||
|
flexWidth: const FlexWidth(mainViewFlexWidth: 1, secondaryViewFlexWidth: 2),
|
||||||
|
placeholder: Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(24),
|
||||||
|
child: Text(
|
||||||
|
AppLocalizations.of(context)!.selectOptionLeftColumn,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 24,
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: const SettingsWidget(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return const SettingsWidget();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class SettingsWidget extends StatelessWidget {
|
||||||
|
const SettingsWidget({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 serversProvider = Provider.of<ServersProvider>(context);
|
final serversProvider = Provider.of<ServersProvider>(context);
|
||||||
|
|
||||||
void navigateServers() {
|
final width = MediaQuery.of(context).size.width;
|
||||||
Future.delayed(const Duration(milliseconds: 0), (() {
|
|
||||||
Navigator.of(context).push(
|
if (width <= 900 && appConfigProvider.selectedSettingsScreen != null) {
|
||||||
MaterialPageRoute(builder: (context) => const Servers())
|
appConfigProvider.setSelectedSettingsScreen(screen: null);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget settingsTile({
|
||||||
|
required String title,
|
||||||
|
required String subtitle,
|
||||||
|
required IconData icon,
|
||||||
|
Widget? trailing,
|
||||||
|
required Widget screenToNavigate,
|
||||||
|
required int thisItem
|
||||||
|
}) {
|
||||||
|
if (width > 900) {
|
||||||
|
return CustomSettingsTile(
|
||||||
|
title: title,
|
||||||
|
subtitle: subtitle,
|
||||||
|
icon: icon,
|
||||||
|
trailing: trailing,
|
||||||
|
thisItem: thisItem,
|
||||||
|
selectedItem: appConfigProvider.selectedSettingsScreen,
|
||||||
|
onTap: () {
|
||||||
|
appConfigProvider.setSelectedSettingsScreen(screen: thisItem, notify: true);
|
||||||
|
SplitView.of(context).setSecondary(screenToNavigate);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}));
|
}
|
||||||
}
|
else {
|
||||||
|
return CustomListTile(
|
||||||
|
title: title,
|
||||||
|
subtitle: subtitle,
|
||||||
|
icon: icon,
|
||||||
|
trailing: trailing,
|
||||||
|
onTap: () {
|
||||||
|
Navigator.of(context).push(
|
||||||
|
MaterialPageRoute(builder: (context) => screenToNavigate)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
|
@ -49,85 +120,55 @@ class Settings extends StatelessWidget {
|
||||||
),
|
),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
children: [
|
children: [
|
||||||
if (serversProvider.selectedServer != null) ...[
|
if (serversProvider.selectedServer != null && serversProvider.serverStatus.data != null) ...[
|
||||||
SectionLabel(label: AppLocalizations.of(context)!.serverSettings),
|
SectionLabel(label: AppLocalizations.of(context)!.serverSettings),
|
||||||
if (serverVersionIsAhead(
|
if (serverVersionIsAhead(
|
||||||
currentVersion: serversProvider.serverStatus.data!.serverVersion,
|
currentVersion: serversProvider.serverStatus.data!.serverVersion,
|
||||||
referenceVersion: 'v0.107.28',
|
referenceVersion: 'v0.107.28',
|
||||||
referenceVersionBeta: 'v0.108.0-b.33'
|
referenceVersionBeta: 'v0.108.0-b.33'
|
||||||
) == true) CustomListTile(
|
) == true) settingsTile(
|
||||||
icon: Icons.search_rounded,
|
icon: Icons.search_rounded,
|
||||||
title: AppLocalizations.of(context)!.safeSearch,
|
title: AppLocalizations.of(context)!.safeSearch,
|
||||||
subtitle: AppLocalizations.of(context)!.safeSearchSettings,
|
subtitle: AppLocalizations.of(context)!.safeSearchSettings,
|
||||||
onTap: () => {
|
thisItem: 0,
|
||||||
Navigator.of(context).push(
|
screenToNavigate: const SafeSearchSettingsScreen(),
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => const SafeSearchSettingsScreen()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
CustomListTile(
|
settingsTile(
|
||||||
icon: Icons.lock_rounded,
|
icon: Icons.lock_rounded,
|
||||||
title: AppLocalizations.of(context)!.accessSettings,
|
title: AppLocalizations.of(context)!.accessSettings,
|
||||||
subtitle: AppLocalizations.of(context)!.accessSettingsDescription,
|
subtitle: AppLocalizations.of(context)!.accessSettingsDescription,
|
||||||
onTap: () => {
|
thisItem: 1,
|
||||||
Navigator.of(context).push(
|
screenToNavigate: const AccessSettings(),
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => const AccessSettings()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
CustomListTile(
|
settingsTile(
|
||||||
icon: Icons.install_desktop_rounded,
|
icon: Icons.install_desktop_rounded,
|
||||||
title: AppLocalizations.of(context)!.dhcpSettings,
|
title: AppLocalizations.of(context)!.dhcpSettings,
|
||||||
subtitle: AppLocalizations.of(context)!.dhcpSettingsDescription,
|
subtitle: AppLocalizations.of(context)!.dhcpSettingsDescription,
|
||||||
onTap: () => {
|
thisItem: 2,
|
||||||
Navigator.of(context).push(
|
screenToNavigate: const Dhcp(),
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => const Dhcp()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
CustomListTile(
|
settingsTile(
|
||||||
icon: Icons.dns_rounded,
|
icon: Icons.dns_rounded,
|
||||||
title: AppLocalizations.of(context)!.dnsSettings,
|
title: AppLocalizations.of(context)!.dnsSettings,
|
||||||
subtitle: AppLocalizations.of(context)!.dnsSettingsDescription,
|
subtitle: AppLocalizations.of(context)!.dnsSettingsDescription,
|
||||||
onTap: () => {
|
thisItem: 3,
|
||||||
Navigator.of(context).push(
|
screenToNavigate: const DnsSettings(),
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => const DnsSettings()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
CustomListTile(
|
settingsTile(
|
||||||
icon: Icons.security_rounded,
|
icon: Icons.security_rounded,
|
||||||
title: AppLocalizations.of(context)!.encryptionSettings,
|
title: AppLocalizations.of(context)!.encryptionSettings,
|
||||||
subtitle: AppLocalizations.of(context)!.encryptionSettingsDescription,
|
subtitle: AppLocalizations.of(context)!.encryptionSettingsDescription,
|
||||||
onTap: () => {
|
thisItem: 4,
|
||||||
Navigator.of(context).push(
|
screenToNavigate: const EncryptionSettings(),
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => const EncryptionSettings()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
CustomListTile(
|
settingsTile(
|
||||||
icon: Icons.route_rounded,
|
icon: Icons.route_rounded,
|
||||||
title: AppLocalizations.of(context)!.dnsRewrites,
|
title: AppLocalizations.of(context)!.dnsRewrites,
|
||||||
subtitle: AppLocalizations.of(context)!.dnsRewritesDescription,
|
subtitle: AppLocalizations.of(context)!.dnsRewritesDescription,
|
||||||
onTap: () => {
|
thisItem: 5,
|
||||||
Navigator.of(context).push(
|
screenToNavigate: const DnsRewrites(),
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => const DnsRewrites()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
if (serversProvider.updateAvailable.data != null) CustomListTile(
|
if (serversProvider.updateAvailable.data != null) settingsTile(
|
||||||
icon: Icons.system_update_rounded,
|
icon: Icons.system_update_rounded,
|
||||||
title: AppLocalizations.of(context)!.updates,
|
title: AppLocalizations.of(context)!.updates,
|
||||||
subtitle: AppLocalizations.of(context)!.updatesDescription,
|
subtitle: AppLocalizations.of(context)!.updatesDescription,
|
||||||
|
@ -144,37 +185,26 @@ class Settings extends StatelessWidget {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
onTap: () => {
|
thisItem: 6,
|
||||||
Navigator.of(context).push(
|
screenToNavigate: const UpdateScreen(),
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => const UpdateScreen()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
CustomListTile(
|
settingsTile(
|
||||||
icon: Icons.info_rounded,
|
icon: Icons.info_rounded,
|
||||||
title: AppLocalizations.of(context)!.serverInformation,
|
title: AppLocalizations.of(context)!.serverInformation,
|
||||||
subtitle: AppLocalizations.of(context)!.serverInformationDescription,
|
subtitle: AppLocalizations.of(context)!.serverInformationDescription,
|
||||||
onTap: () => {
|
thisItem: 7,
|
||||||
Navigator.of(context).push(
|
screenToNavigate: const ServerInformation(),
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => const ServerInformation()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
SectionLabel(label: AppLocalizations.of(context)!.appSettings),
|
SectionLabel(label: AppLocalizations.of(context)!.appSettings),
|
||||||
CustomListTile(
|
settingsTile(
|
||||||
icon: Icons.palette_rounded,
|
icon: Icons.palette_rounded,
|
||||||
title: AppLocalizations.of(context)!.customization,
|
title: AppLocalizations.of(context)!.customization,
|
||||||
subtitle: AppLocalizations.of(context)!.customizationDescription,
|
subtitle: AppLocalizations.of(context)!.customizationDescription,
|
||||||
onTap: () => Navigator.push(context, MaterialPageRoute(
|
thisItem: 8,
|
||||||
builder: (context) => const Customization()
|
screenToNavigate: const Customization(),
|
||||||
))
|
|
||||||
),
|
),
|
||||||
CustomListTile(
|
settingsTile(
|
||||||
icon: Icons.storage_rounded,
|
icon: Icons.storage_rounded,
|
||||||
title: AppLocalizations.of(context)!.servers,
|
title: AppLocalizations.of(context)!.servers,
|
||||||
subtitle: serversProvider.selectedServer != null
|
subtitle: serversProvider.selectedServer != null
|
||||||
|
@ -182,31 +212,22 @@ class Settings extends StatelessWidget {
|
||||||
? "${AppLocalizations.of(context)!.connectedTo} ${serversProvider.selectedServer!.name}"
|
? "${AppLocalizations.of(context)!.connectedTo} ${serversProvider.selectedServer!.name}"
|
||||||
: "${AppLocalizations.of(context)!.selectedServer} ${serversProvider.selectedServer!.name}"
|
: "${AppLocalizations.of(context)!.selectedServer} ${serversProvider.selectedServer!.name}"
|
||||||
: AppLocalizations.of(context)!.noServerSelected,
|
: AppLocalizations.of(context)!.noServerSelected,
|
||||||
onTap: navigateServers,
|
thisItem: 9,
|
||||||
|
screenToNavigate: const Servers(),
|
||||||
),
|
),
|
||||||
CustomListTile(
|
settingsTile(
|
||||||
icon: Icons.settings,
|
icon: Icons.settings,
|
||||||
title: AppLocalizations.of(context)!.generalSettings,
|
title: AppLocalizations.of(context)!.generalSettings,
|
||||||
subtitle: AppLocalizations.of(context)!.generalSettingsDescription,
|
subtitle: AppLocalizations.of(context)!.generalSettingsDescription,
|
||||||
onTap: () => {
|
thisItem: 10,
|
||||||
Navigator.of(context).push(
|
screenToNavigate: const GeneralSettings(),
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => const GeneralSettings()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
CustomListTile(
|
settingsTile(
|
||||||
icon: Icons.build_outlined,
|
icon: Icons.build_outlined,
|
||||||
title: AppLocalizations.of(context)!.advancedSettings,
|
title: AppLocalizations.of(context)!.advancedSettings,
|
||||||
subtitle: AppLocalizations.of(context)!.advancedSetupDescription,
|
subtitle: AppLocalizations.of(context)!.advancedSetupDescription,
|
||||||
onTap: () => {
|
thisItem: 11,
|
||||||
Navigator.of(context).push(
|
screenToNavigate: const AdvancedSettings(),
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => const AdvancedSettings()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
SectionLabel(label: AppLocalizations.of(context)!.aboutApp),
|
SectionLabel(label: AppLocalizations.of(context)!.aboutApp),
|
||||||
CustomListTile(
|
CustomListTile(
|
||||||
|
@ -222,7 +243,7 @@ class Settings extends StatelessWidget {
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
if (Platform.isAndroid) IconButton(
|
||||||
onPressed: () => openUrl(Urls.playStore),
|
onPressed: () => openUrl(Urls.playStore),
|
||||||
icon: SvgPicture.asset(
|
icon: SvgPicture.asset(
|
||||||
'assets/resources/google-play.svg',
|
'assets/resources/google-play.svg',
|
||||||
|
|
|
@ -65,16 +65,18 @@ class UpdateScreen extends StatelessWidget {
|
||||||
Widget headerPortrait() {
|
Widget headerPortrait() {
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
|
const SizedBox(height: 8),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
if (Navigator.canPop(context)) IconButton(
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Icons.arrow_back,
|
Icons.arrow_back,
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
),
|
),
|
||||||
|
if (!Navigator.canPop(context)) const SizedBox(),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Icons.refresh_rounded,
|
Icons.refresh_rounded,
|
||||||
|
@ -170,120 +172,6 @@ class UpdateScreen extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget headerLandscape() {
|
|
||||||
return Column(
|
|
||||||
mainAxisSize: MainAxisSize.max,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(
|
|
||||||
Icons.arrow_back,
|
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
|
||||||
),
|
|
||||||
onPressed: () => Navigator.pop(context),
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(
|
|
||||||
Icons.refresh_rounded,
|
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
|
||||||
),
|
|
||||||
tooltip: AppLocalizations.of(context)!.checkUpdates,
|
|
||||||
onPressed: () => serversProvider.checkServerUpdatesAvailable(serversProvider.selectedServer!)
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.only(
|
|
||||||
top: 8, bottom: 16, left: 16, right: 16
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
serversProvider.updateAvailable.loadStatus == LoadStatus.loading
|
|
||||||
? Column(
|
|
||||||
children: const [
|
|
||||||
CircularProgressIndicator(),
|
|
||||||
SizedBox(height: 4)
|
|
||||||
],
|
|
||||||
)
|
|
||||||
: Icon(
|
|
||||||
serversProvider.updateAvailable.data!.updateAvailable != null
|
|
||||||
? serversProvider.updateAvailable.data!.updateAvailable == true
|
|
||||||
? Icons.system_update_rounded
|
|
||||||
: Icons.system_security_update_good_rounded
|
|
||||||
: Icons.system_security_update_warning_rounded,
|
|
||||||
size: 40,
|
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Text(
|
|
||||||
serversProvider.updateAvailable.loadStatus == LoadStatus.loading
|
|
||||||
? AppLocalizations.of(context)!.checkingUpdates
|
|
||||||
: serversProvider.updateAvailable.data!.updateAvailable != null
|
|
||||||
? serversProvider.updateAvailable.data!.updateAvailable == true
|
|
||||||
? AppLocalizations.of(context)!.updateAvailable
|
|
||||||
: AppLocalizations.of(context)!.serverUpdated
|
|
||||||
: AppLocalizations.of(context)!.unknownStatus,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 24,
|
|
||||||
fontWeight: FontWeight.w400
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 40),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
if (serversProvider.updateAvailable.loadStatus == LoadStatus.loaded) Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
serversProvider.updateAvailable.data!.updateAvailable != null && serversProvider.updateAvailable.data!.updateAvailable == true
|
|
||||||
? AppLocalizations.of(context)!.newVersion
|
|
||||||
: AppLocalizations.of(context)!.currentVersion,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text(
|
|
||||||
serversProvider.updateAvailable.data!.updateAvailable != null
|
|
||||||
? serversProvider.updateAvailable.data!.updateAvailable == true
|
|
||||||
? serversProvider.updateAvailable.data!.newVersion ?? 'N/A'
|
|
||||||
: serversProvider.updateAvailable.data!.currentVersion
|
|
||||||
: "N/A",
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if (serversProvider.updateAvailable.loadStatus != LoadStatus.loaded) const SizedBox(),
|
|
||||||
FilledButton.icon(
|
|
||||||
icon: const Icon(Icons.download_rounded),
|
|
||||||
label: Text(AppLocalizations.of(context)!.updateNow),
|
|
||||||
onPressed: serversProvider.updateAvailable.data!.updateAvailable != null && serversProvider.updateAvailable.data!.updateAvailable == true
|
|
||||||
? serversProvider.updateAvailable.data!.canAutoupdate == true
|
|
||||||
? () => update()
|
|
||||||
: () => showAutoUpdateUnavailableModal()
|
|
||||||
: null
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final changelog = serversProvider.updateAvailable.loadStatus == LoadStatus.loaded && serversProvider.updateAvailable.data!.changelog != null
|
final changelog = serversProvider.updateAvailable.loadStatus == LoadStatus.loaded && serversProvider.updateAvailable.data!.changelog != null
|
||||||
? ListView(
|
? ListView(
|
||||||
children: [
|
children: [
|
||||||
|
@ -313,51 +201,20 @@ class UpdateScreen extends StatelessWidget {
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: MediaQuery.of(context).size.width > 700
|
body: Column(
|
||||||
? Row(
|
children: [
|
||||||
children: [
|
Container(
|
||||||
Expanded(
|
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||||
flex: 2,
|
child: SafeArea(
|
||||||
child: Container(
|
child: headerPortrait()
|
||||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
)
|
||||||
child: Column(
|
),
|
||||||
mainAxisSize: MainAxisSize.max,
|
const SizedBox(height: 16),
|
||||||
children: [
|
changelog != null
|
||||||
Container(
|
? Expanded(child: changelog)
|
||||||
height: MediaQuery.of(context).size.height,
|
: const SizedBox(),
|
||||||
padding: EdgeInsets.only(
|
]
|
||||||
top: MediaQuery.of(context).viewPadding.top
|
)
|
||||||
),
|
|
||||||
child: headerLandscape(),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
flex: 3,
|
|
||||||
child: SafeArea(
|
|
||||||
child: SizedBox(
|
|
||||||
width: MediaQuery.of(context).size.width*0.6,
|
|
||||||
child: changelog ?? const SizedBox(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
|
||||||
: Column(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
|
||||||
child: SafeArea(
|
|
||||||
child: headerPortrait()
|
|
||||||
)
|
|
||||||
),
|
|
||||||
changelog != null
|
|
||||||
? Expanded(child: changelog)
|
|
||||||
: const SizedBox(),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
276
lib/screens/top_items/top_items_modal.dart
Normal file
276
lib/screens/top_items/top_items_modal.dart
Normal file
|
@ -0,0 +1,276 @@
|
||||||
|
// ignore_for_file: use_build_context_synchronously
|
||||||
|
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:percent_indicator/percent_indicator.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
import 'package:adguard_home_manager/screens/home/top_items_options_modal.dart';
|
||||||
|
import 'package:adguard_home_manager/widgets/custom_list_tile.dart';
|
||||||
|
|
||||||
|
import 'package:adguard_home_manager/models/applied_filters.dart';
|
||||||
|
import 'package:adguard_home_manager/functions/copy_clipboard.dart';
|
||||||
|
import 'package:adguard_home_manager/providers/logs_provider.dart';
|
||||||
|
import 'package:adguard_home_manager/functions/snackbar.dart';
|
||||||
|
import 'package:adguard_home_manager/functions/number_format.dart';
|
||||||
|
import 'package:adguard_home_manager/functions/block_unblock_domain.dart';
|
||||||
|
import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
||||||
|
import 'package:adguard_home_manager/providers/servers_provider.dart';
|
||||||
|
|
||||||
|
class TopItemsModal extends StatefulWidget {
|
||||||
|
final String type;
|
||||||
|
final String title;
|
||||||
|
final bool? isClient;
|
||||||
|
final List<Map<String, dynamic>> data;
|
||||||
|
|
||||||
|
const TopItemsModal({
|
||||||
|
Key? key,
|
||||||
|
required this.type,
|
||||||
|
required this.title,
|
||||||
|
this.isClient,
|
||||||
|
required this.data,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<TopItemsModal> createState() => _TopItemsModalState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TopItemsModalState extends State<TopItemsModal> {
|
||||||
|
bool searchActive = false;
|
||||||
|
final TextEditingController searchController = TextEditingController();
|
||||||
|
|
||||||
|
List<Map<String, dynamic>> data = [];
|
||||||
|
List<Map<String, dynamic>> screenData = [];
|
||||||
|
|
||||||
|
void search(String value) {
|
||||||
|
List<Map<String, dynamic>> newValues = widget.data.where((item) => item.keys.toList()[0].contains(value)).toList();
|
||||||
|
setState(() => screenData = newValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
data = widget.data;
|
||||||
|
screenData = widget.data;
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final serversProvider = Provider.of<ServersProvider>(context);
|
||||||
|
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||||
|
final logsProvider = Provider.of<LogsProvider>(context);
|
||||||
|
|
||||||
|
int total = 0;
|
||||||
|
for (var element in data) {
|
||||||
|
total = total + int.parse(element.values.toList()[0].toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool? getIsBlocked() {
|
||||||
|
if (widget.type == 'topBlockedDomains') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (widget.type == 'topQueriedDomains') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void changeBlockStatus(String status, String domain) async {
|
||||||
|
final result = await blockUnblock(context, domain, status);
|
||||||
|
showSnacbkar(
|
||||||
|
context: context,
|
||||||
|
appConfigProvider: appConfigProvider,
|
||||||
|
label: result['message'],
|
||||||
|
color: result['success'] == true ? Colors.green : Colors.red
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void openOptionsModal(String domain, String type) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => TopItemsOptionsModal(
|
||||||
|
isBlocked: getIsBlocked(),
|
||||||
|
changeStatus: (String status) => changeBlockStatus(status, domain),
|
||||||
|
copyToClipboard: () => copyToClipboard(
|
||||||
|
context: context,
|
||||||
|
value: domain,
|
||||||
|
successMessage: AppLocalizations.of(context)!.domainCopiedClipboard
|
||||||
|
),
|
||||||
|
type: type,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Dialog(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
maxWidth: 500
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 1,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
icon: const Icon(Icons.clear_rounded),
|
||||||
|
tooltip: AppLocalizations.of(context)!.close,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
flex: 1,
|
||||||
|
child: TextField(
|
||||||
|
controller: searchController,
|
||||||
|
onChanged: search,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
filled: true,
|
||||||
|
fillColor: Theme.of(context).colorScheme.primary.withOpacity(0.1),
|
||||||
|
hintText: AppLocalizations.of(context)!.search,
|
||||||
|
prefixIcon: const Icon(Icons.search_rounded),
|
||||||
|
contentPadding: const EdgeInsets.only(left: 14, bottom: 9, top: 11),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderSide: const BorderSide(color: Colors.transparent),
|
||||||
|
borderRadius: BorderRadius.circular(25.7),
|
||||||
|
),
|
||||||
|
enabledBorder: UnderlineInputBorder(
|
||||||
|
borderSide: const BorderSide(color: Colors.transparent),
|
||||||
|
borderRadius: BorderRadius.circular(25.7),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (screenData.isNotEmpty) Flexible(
|
||||||
|
child: ListView.builder(
|
||||||
|
padding: const EdgeInsets.only(top: 0),
|
||||||
|
itemCount: screenData.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
String? name;
|
||||||
|
if (widget.isClient != null && widget.isClient == true) {
|
||||||
|
try {
|
||||||
|
name = serversProvider.serverStatus.data!.clients.firstWhere((c) => c.ids.contains(screenData[index].keys.toList()[0])).name;
|
||||||
|
} catch (e) {
|
||||||
|
// ---- //
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return CustomListTile(
|
||||||
|
onTap: () {
|
||||||
|
if (widget.type == 'topQueriedDomains' || widget.type == 'topBlockedDomains') {
|
||||||
|
logsProvider.setSearchText(screenData[index].keys.toList()[0]);
|
||||||
|
logsProvider.setSelectedClients(null);
|
||||||
|
logsProvider.setAppliedFilters(
|
||||||
|
AppliedFiters(
|
||||||
|
selectedResultStatus: 'all',
|
||||||
|
searchText: screenData[index].keys.toList()[0],
|
||||||
|
clients: null
|
||||||
|
)
|
||||||
|
);
|
||||||
|
appConfigProvider.setSelectedScreen(2);
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
else if (widget.type == 'topClients') {
|
||||||
|
logsProvider.setSearchText(null);
|
||||||
|
logsProvider.setSelectedClients([screenData[index].keys.toList()[0]]);
|
||||||
|
logsProvider.setAppliedFilters(
|
||||||
|
AppliedFiters(
|
||||||
|
selectedResultStatus: 'all',
|
||||||
|
searchText: null,
|
||||||
|
clients: [screenData[index].keys.toList()[0]]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
appConfigProvider.setSelectedScreen(2);
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLongPress: () => openOptionsModal(
|
||||||
|
screenData[index].keys.toList()[0],
|
||||||
|
widget.type
|
||||||
|
),
|
||||||
|
title: screenData[index].keys.toList()[0],
|
||||||
|
trailing: Text(
|
||||||
|
screenData[index].values.toList()[0].toString(),
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant
|
||||||
|
),
|
||||||
|
),
|
||||||
|
subtitleWidget: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
if (name != null) ...[
|
||||||
|
Text(
|
||||||
|
name,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 5),
|
||||||
|
],
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 50,
|
||||||
|
child: Text(
|
||||||
|
"${doubleFormat((screenData[index].values.toList()[0]/total*100), Platform.localeName)}%",
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).listTileTheme.textColor
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Flexible(
|
||||||
|
child: LinearPercentIndicator(
|
||||||
|
animation: true,
|
||||||
|
lineHeight: 4,
|
||||||
|
animationDuration: 500,
|
||||||
|
curve: Curves.easeOut,
|
||||||
|
percent: screenData[index].values.toList()[0]/total,
|
||||||
|
barRadius: const Radius.circular(5),
|
||||||
|
progressColor: Theme.of(context).colorScheme.primary,
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.surfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (screenData.isEmpty) Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
|
child: Text(
|
||||||
|
AppLocalizations.of(context)!.noItemsSearch,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 22,
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
114
lib/services/db/queries.dart
Normal file
114
lib/services/db/queries.dart
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
import 'package:sqflite/sqflite.dart';
|
||||||
|
|
||||||
|
import 'package:adguard_home_manager/functions/conversions.dart';
|
||||||
|
import 'package:adguard_home_manager/models/server.dart';
|
||||||
|
|
||||||
|
Future<dynamic> saveServerQuery(Database db, Server server) async {
|
||||||
|
try {
|
||||||
|
return await db.transaction((txn) async {
|
||||||
|
await txn.insert(
|
||||||
|
'servers',
|
||||||
|
{
|
||||||
|
'id': server.id,
|
||||||
|
'name': server.name,
|
||||||
|
'connectionMethod': server.connectionMethod,
|
||||||
|
'domain': server.domain,
|
||||||
|
'path': server.path,
|
||||||
|
'port': server.port,
|
||||||
|
'user': server.user,
|
||||||
|
'password': server.password,
|
||||||
|
'defaultServer': convertFromBoolToInt(server.defaultServer),
|
||||||
|
'authToken': server.authToken,
|
||||||
|
'runningOnHa': convertFromBoolToInt(server.runningOnHa)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<dynamic> editServerQuery(Database db, Server server) async {
|
||||||
|
try {
|
||||||
|
return await db.transaction((txn) async {
|
||||||
|
await txn.update(
|
||||||
|
'servers',
|
||||||
|
{
|
||||||
|
'id': server.id,
|
||||||
|
'name': server.name,
|
||||||
|
'connectionMethod': server.connectionMethod,
|
||||||
|
'domain': server.domain,
|
||||||
|
'path': server.path,
|
||||||
|
'port': server.port,
|
||||||
|
'user': server.user,
|
||||||
|
'password': server.password,
|
||||||
|
'defaultServer': server.defaultServer,
|
||||||
|
'authToken': server.authToken,
|
||||||
|
'runningOnHa': convertFromBoolToInt(server.runningOnHa)
|
||||||
|
},
|
||||||
|
where: 'id = ?',
|
||||||
|
whereArgs: [server.id]
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Future<bool> removeServerQuery(Database db, String id) async {
|
||||||
|
try {
|
||||||
|
return await db.transaction((txn) async {
|
||||||
|
await txn.delete(
|
||||||
|
'servers',
|
||||||
|
where: 'id = ?',
|
||||||
|
whereArgs: [id]
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<dynamic> setDefaultServerQuery(Database db, String id) async {
|
||||||
|
try {
|
||||||
|
return await db.transaction((txn) async {
|
||||||
|
await txn.update(
|
||||||
|
'servers',
|
||||||
|
{'defaultServer': '0'},
|
||||||
|
where: 'defaultServer = ?',
|
||||||
|
whereArgs: [1]
|
||||||
|
);
|
||||||
|
await txn.update(
|
||||||
|
'servers',
|
||||||
|
{'defaultServer': '1'},
|
||||||
|
where: 'id = ?',
|
||||||
|
whereArgs: [id]
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> updateConfigQuery({
|
||||||
|
required Database db,
|
||||||
|
required String column,
|
||||||
|
required dynamic value
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
return await db.transaction((txn) async {
|
||||||
|
await txn.update(
|
||||||
|
'appConfig',
|
||||||
|
{ column: value },
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,10 +16,12 @@ enum ConnectionType { http, https}
|
||||||
|
|
||||||
class AddServerModal extends StatefulWidget {
|
class AddServerModal extends StatefulWidget {
|
||||||
final Server? server;
|
final Server? server;
|
||||||
|
final bool window;
|
||||||
|
|
||||||
const AddServerModal({
|
const AddServerModal({
|
||||||
Key? key,
|
Key? key,
|
||||||
this.server,
|
this.server,
|
||||||
|
required this.window
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -429,15 +431,227 @@ class _AddServerModalState extends State<AddServerModal> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Stack(
|
List<Widget> form() {
|
||||||
children: [
|
return [
|
||||||
Scaffold(
|
Container(
|
||||||
appBar: AppBar(
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||||
title: Text(AppLocalizations.of(context)!.createConnection),
|
margin: const EdgeInsets.only(
|
||||||
actions: [
|
top: 24,
|
||||||
|
left: 24,
|
||||||
|
right: 24
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).colorScheme.primary.withOpacity(0.05),
|
||||||
|
borderRadius: BorderRadius.circular(30),
|
||||||
|
border: Border.all(
|
||||||
|
color: Theme.of(context).colorScheme.primary
|
||||||
|
)
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
"${connectionType.name}://${ipDomainController.text}${portController.text != '' ? ':${portController.text}' : ""}${pathController.text}",
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
fontWeight: FontWeight.w500
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
sectionLabel(AppLocalizations.of(context)!.general),
|
||||||
|
textField(
|
||||||
|
label: AppLocalizations.of(context)!.name,
|
||||||
|
controller: nameController,
|
||||||
|
icon: Icons.badge_rounded,
|
||||||
|
error: nameError,
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value != '') {
|
||||||
|
setState(() => nameError = null);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setState(() => nameError = AppLocalizations.of(context)!.nameNotEmpty);
|
||||||
|
}
|
||||||
|
checkDataValid();
|
||||||
|
}
|
||||||
|
),
|
||||||
|
sectionLabel(AppLocalizations.of(context)!.connection),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
child: SegmentedButton<ConnectionType>(
|
||||||
|
segments: const [
|
||||||
|
ButtonSegment(
|
||||||
|
value: ConnectionType.http,
|
||||||
|
label: Text("HTTP")
|
||||||
|
),
|
||||||
|
ButtonSegment(
|
||||||
|
value: ConnectionType.https,
|
||||||
|
label: Text("HTTPS")
|
||||||
|
),
|
||||||
|
],
|
||||||
|
selected: <ConnectionType>{connectionType},
|
||||||
|
onSelectionChanged: (value) => setState(() => connectionType = value.first),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 30),
|
||||||
|
textField(
|
||||||
|
label: AppLocalizations.of(context)!.ipDomain,
|
||||||
|
controller: ipDomainController,
|
||||||
|
icon: Icons.link_rounded,
|
||||||
|
error: ipDomainError,
|
||||||
|
keyboardType: TextInputType.url,
|
||||||
|
onChanged: validateAddress
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
textField(
|
||||||
|
label: AppLocalizations.of(context)!.path,
|
||||||
|
controller: pathController,
|
||||||
|
icon: Icons.route_rounded,
|
||||||
|
error: pathError,
|
||||||
|
onChanged: validateSubroute,
|
||||||
|
hintText: AppLocalizations.of(context)!.examplePath,
|
||||||
|
helperText: AppLocalizations.of(context)!.helperPath,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
textField(
|
||||||
|
label: AppLocalizations.of(context)!.port,
|
||||||
|
controller: portController,
|
||||||
|
icon: Icons.numbers_rounded,
|
||||||
|
error: portError,
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
onChanged: validatePort
|
||||||
|
),
|
||||||
|
sectionLabel(AppLocalizations.of(context)!.authentication),
|
||||||
|
textField(
|
||||||
|
label: AppLocalizations.of(context)!.username,
|
||||||
|
controller: userController,
|
||||||
|
icon: Icons.person_rounded,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
textField(
|
||||||
|
label: AppLocalizations.of(context)!.password,
|
||||||
|
controller: passwordController,
|
||||||
|
icon: Icons.lock_rounded,
|
||||||
|
keyboardType: TextInputType.visiblePassword,
|
||||||
|
obscureText: true
|
||||||
|
),
|
||||||
|
sectionLabel(AppLocalizations.of(context)!.other),
|
||||||
|
Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: widget.server == null
|
||||||
|
? () => setState(() => defaultServer = !defaultServer)
|
||||||
|
: null,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context)!.defaultServer,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 15,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Switch(
|
||||||
|
value: defaultServer,
|
||||||
|
onChanged: widget.server == null
|
||||||
|
? (value) => setState(() => defaultServer = value)
|
||||||
|
: null,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () => setState(() => homeAssistant = !homeAssistant),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context)!.runningHomeAssistant,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 15,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Switch(
|
||||||
|
value: homeAssistant,
|
||||||
|
onChanged: (value) => setState(() => homeAssistant = value),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (widget.window == true) {
|
||||||
|
return Dialog(
|
||||||
|
child: SizedBox(
|
||||||
|
width: 400,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(right: 10),
|
padding: const EdgeInsets.all(16),
|
||||||
child: IconButton(
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
icon: const Icon(Icons.clear_rounded)
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context)!.createConnection,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 20
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
tooltip: widget.server == null
|
||||||
|
? AppLocalizations.of(context)!.connect
|
||||||
|
: AppLocalizations.of(context)!.save,
|
||||||
|
onPressed: allDataValid == true
|
||||||
|
? widget.server == null
|
||||||
|
? () => connect()
|
||||||
|
: () => edit()
|
||||||
|
: null,
|
||||||
|
icon: Icon(
|
||||||
|
widget.server == null
|
||||||
|
? Icons.login_rounded
|
||||||
|
: Icons.save_rounded
|
||||||
|
)
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: ListView(
|
||||||
|
children: form()
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(AppLocalizations.of(context)!.createConnection),
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
tooltip: widget.server == null
|
tooltip: widget.server == null
|
||||||
? AppLocalizations.of(context)!.connect
|
? AppLocalizations.of(context)!.connect
|
||||||
: AppLocalizations.of(context)!.save,
|
: AppLocalizations.of(context)!.save,
|
||||||
|
@ -452,203 +666,49 @@ class _AddServerModalState extends State<AddServerModal> {
|
||||||
: Icons.save_rounded
|
: Icons.save_rounded
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(width: 10)
|
||||||
],
|
],
|
||||||
toolbarHeight: 70,
|
toolbarHeight: 70,
|
||||||
|
),
|
||||||
|
body: ListView(
|
||||||
|
children: form(),
|
||||||
|
)
|
||||||
),
|
),
|
||||||
body: ListView(
|
AnimatedOpacity(
|
||||||
children: [
|
opacity: isConnecting == true ? 1 : 0,
|
||||||
Container(
|
duration: const Duration(milliseconds: 250),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
curve: Curves.easeInOut,
|
||||||
margin: const EdgeInsets.only(
|
child: IgnorePointer(
|
||||||
top: 24,
|
ignoring: isConnecting == true ? false : true,
|
||||||
left: 24,
|
child: Scaffold(
|
||||||
right: 24
|
backgroundColor: Colors.transparent,
|
||||||
),
|
body: Container(
|
||||||
decoration: BoxDecoration(
|
width: mediaQuery.size.width,
|
||||||
color: Theme.of(context).colorScheme.primary.withOpacity(0.05),
|
height: mediaQuery.size.height,
|
||||||
borderRadius: BorderRadius.circular(30),
|
color: const Color.fromRGBO(0, 0, 0, 0.7),
|
||||||
border: Border.all(
|
child: Column(
|
||||||
color: Theme.of(context).colorScheme.primary
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
)
|
children: [
|
||||||
),
|
const CircularProgressIndicator(
|
||||||
child: Text(
|
|
||||||
"${connectionType.name}://${ipDomainController.text}${portController.text != '' ? ':${portController.text}' : ""}${pathController.text}",
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
fontWeight: FontWeight.w500
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
sectionLabel(AppLocalizations.of(context)!.general),
|
|
||||||
textField(
|
|
||||||
label: AppLocalizations.of(context)!.name,
|
|
||||||
controller: nameController,
|
|
||||||
icon: Icons.badge_rounded,
|
|
||||||
error: nameError,
|
|
||||||
onChanged: (value) {
|
|
||||||
if (value != '') {
|
|
||||||
setState(() => nameError = null);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
setState(() => nameError = AppLocalizations.of(context)!.nameNotEmpty);
|
|
||||||
}
|
|
||||||
checkDataValid();
|
|
||||||
}
|
|
||||||
),
|
|
||||||
sectionLabel(AppLocalizations.of(context)!.connection),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
|
||||||
child: SegmentedButton<ConnectionType>(
|
|
||||||
segments: const [
|
|
||||||
ButtonSegment(
|
|
||||||
value: ConnectionType.http,
|
|
||||||
label: Text("HTTP")
|
|
||||||
),
|
|
||||||
ButtonSegment(
|
|
||||||
value: ConnectionType.https,
|
|
||||||
label: Text("HTTPS")
|
|
||||||
),
|
|
||||||
],
|
|
||||||
selected: <ConnectionType>{connectionType},
|
|
||||||
onSelectionChanged: (value) => setState(() => connectionType = value.first),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 30),
|
|
||||||
textField(
|
|
||||||
label: AppLocalizations.of(context)!.ipDomain,
|
|
||||||
controller: ipDomainController,
|
|
||||||
icon: Icons.link_rounded,
|
|
||||||
error: ipDomainError,
|
|
||||||
keyboardType: TextInputType.url,
|
|
||||||
onChanged: validateAddress
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
textField(
|
|
||||||
label: AppLocalizations.of(context)!.path,
|
|
||||||
controller: pathController,
|
|
||||||
icon: Icons.route_rounded,
|
|
||||||
error: pathError,
|
|
||||||
onChanged: validateSubroute,
|
|
||||||
hintText: AppLocalizations.of(context)!.examplePath,
|
|
||||||
helperText: AppLocalizations.of(context)!.helperPath,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
textField(
|
|
||||||
label: AppLocalizations.of(context)!.port,
|
|
||||||
controller: portController,
|
|
||||||
icon: Icons.numbers_rounded,
|
|
||||||
error: portError,
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
onChanged: validatePort
|
|
||||||
),
|
|
||||||
sectionLabel(AppLocalizations.of(context)!.authentication),
|
|
||||||
textField(
|
|
||||||
label: AppLocalizations.of(context)!.username,
|
|
||||||
controller: userController,
|
|
||||||
icon: Icons.person_rounded,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
textField(
|
|
||||||
label: AppLocalizations.of(context)!.password,
|
|
||||||
controller: passwordController,
|
|
||||||
icon: Icons.lock_rounded,
|
|
||||||
keyboardType: TextInputType.visiblePassword,
|
|
||||||
obscureText: true
|
|
||||||
),
|
|
||||||
sectionLabel(AppLocalizations.of(context)!.other),
|
|
||||||
Material(
|
|
||||||
color: Colors.transparent,
|
|
||||||
child: InkWell(
|
|
||||||
onTap: widget.server == null
|
|
||||||
? () => setState(() => defaultServer = !defaultServer)
|
|
||||||
: null,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
AppLocalizations.of(context)!.defaultServer,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 15,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Switch(
|
|
||||||
value: defaultServer,
|
|
||||||
onChanged: widget.server == null
|
|
||||||
? (value) => setState(() => defaultServer = value)
|
|
||||||
: null,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
Material(
|
|
||||||
color: Colors.transparent,
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () => setState(() => homeAssistant = !homeAssistant),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
AppLocalizations.of(context)!.runningHomeAssistant,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 15,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Switch(
|
|
||||||
value: homeAssistant,
|
|
||||||
onChanged: (value) => setState(() => homeAssistant = value),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
AnimatedOpacity(
|
|
||||||
opacity: isConnecting == true ? 1 : 0,
|
|
||||||
duration: const Duration(milliseconds: 250),
|
|
||||||
curve: Curves.easeInOut,
|
|
||||||
child: IgnorePointer(
|
|
||||||
ignoring: isConnecting == true ? false : true,
|
|
||||||
child: Scaffold(
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
body: Container(
|
|
||||||
width: mediaQuery.size.width,
|
|
||||||
height: mediaQuery.size.height,
|
|
||||||
color: const Color.fromRGBO(0, 0, 0, 0.7),
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
const CircularProgressIndicator(
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 30),
|
|
||||||
Text(
|
|
||||||
AppLocalizations.of(context)!.connecting,
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
fontSize: 26
|
|
||||||
),
|
),
|
||||||
)
|
const SizedBox(height: 30),
|
||||||
],
|
Text(
|
||||||
|
AppLocalizations.of(context)!.connecting,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
fontSize: 26
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
)
|
],
|
||||||
],
|
);
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -89,6 +89,10 @@ class BottomNavBar extends StatelessWidget {
|
||||||
if (value != 2) {
|
if (value != 2) {
|
||||||
logsProvider.resetFilters();
|
logsProvider.resetFilters();
|
||||||
}
|
}
|
||||||
|
// Reset settings selected screen
|
||||||
|
if (value != screens.length-1) {
|
||||||
|
appConfigProvider.setSelectedSettingsScreen(screen: null);
|
||||||
|
}
|
||||||
appConfigProvider.setSelectedScreen(value);
|
appConfigProvider.setSelectedScreen(value);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -10,6 +10,7 @@ class CustomListTile extends StatelessWidget {
|
||||||
final EdgeInsets? padding;
|
final EdgeInsets? padding;
|
||||||
final void Function()? onLongPress;
|
final void Function()? onLongPress;
|
||||||
final bool? disabled;
|
final bool? disabled;
|
||||||
|
final void Function(bool)? onHover;
|
||||||
|
|
||||||
const CustomListTile({
|
const CustomListTile({
|
||||||
Key? key,
|
Key? key,
|
||||||
|
@ -21,7 +22,8 @@ class CustomListTile extends StatelessWidget {
|
||||||
this.trailing,
|
this.trailing,
|
||||||
this.padding,
|
this.padding,
|
||||||
this.onLongPress,
|
this.onLongPress,
|
||||||
this.disabled
|
this.disabled,
|
||||||
|
this.onHover,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -30,6 +32,7 @@ class CustomListTile extends StatelessWidget {
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
|
onHover: onHover,
|
||||||
onLongPress: onLongPress,
|
onLongPress: onLongPress,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: padding ?? const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
padding: padding ?? const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
|
|
|
@ -41,33 +41,35 @@ class CustomRadioListTile extends StatelessWidget {
|
||||||
backgroundColor: radioBackgroundColor,
|
backgroundColor: radioBackgroundColor,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 24),
|
const SizedBox(width: 24),
|
||||||
Column(
|
Expanded(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
child: Column(
|
||||||
children: [
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
SizedBox(
|
children: [
|
||||||
width: MediaQuery.of(context).size.width-110,
|
|
||||||
child: Text(
|
|
||||||
title,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
color: Theme.of(context).colorScheme.onSurface
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (subtitle != null) ...[
|
|
||||||
const SizedBox(height: 5),
|
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: MediaQuery.of(context).size.width-110,
|
width: MediaQuery.of(context).size.width-110,
|
||||||
child: Text(
|
child: Text(
|
||||||
subtitle!,
|
title,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(context).listTileTheme.textColor,
|
fontSize: 16,
|
||||||
fontSize: 14
|
color: Theme.of(context).colorScheme.onSurface
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
]
|
if (subtitle != null) ...[
|
||||||
],
|
const SizedBox(height: 5),
|
||||||
|
SizedBox(
|
||||||
|
width: MediaQuery.of(context).size.width-110,
|
||||||
|
child: Text(
|
||||||
|
subtitle!,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).listTileTheme.textColor,
|
||||||
|
fontSize: 14
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
104
lib/widgets/custom_settings_tile.dart
Normal file
104
lib/widgets/custom_settings_tile.dart
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class CustomSettingsTile extends StatelessWidget {
|
||||||
|
final String title;
|
||||||
|
final String? subtitle;
|
||||||
|
final Widget? subtitleWidget;
|
||||||
|
final void Function()? onTap;
|
||||||
|
final IconData? icon;
|
||||||
|
final Widget? trailing;
|
||||||
|
final EdgeInsets? padding;
|
||||||
|
final int thisItem;
|
||||||
|
final int? selectedItem;
|
||||||
|
|
||||||
|
const CustomSettingsTile({
|
||||||
|
Key? key,
|
||||||
|
required this.title,
|
||||||
|
this.subtitle,
|
||||||
|
this.subtitleWidget,
|
||||||
|
this.onTap,
|
||||||
|
this.icon,
|
||||||
|
this.trailing,
|
||||||
|
this.padding,
|
||||||
|
required this.thisItem,
|
||||||
|
required this.selectedItem,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
Widget tileBody = Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
if (icon != null) ...[
|
||||||
|
Icon(
|
||||||
|
icon,
|
||||||
|
size: 24,
|
||||||
|
color: Theme.of(context).listTileTheme.iconColor,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
],
|
||||||
|
Flexible(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (subtitle != null || subtitleWidget != null) ...[
|
||||||
|
const SizedBox(height: 5),
|
||||||
|
if (subtitle == null && subtitleWidget != null) subtitleWidget!,
|
||||||
|
if (subtitle != null && subtitleWidget == null) Text(
|
||||||
|
subtitle!,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).listTileTheme.textColor,
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w400
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (trailing != null) ...[
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
trailing!
|
||||||
|
]
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
|
child: Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
borderRadius: BorderRadius.circular(28),
|
||||||
|
child: InkWell(
|
||||||
|
borderRadius: BorderRadius.circular(28),
|
||||||
|
onTap: onTap,
|
||||||
|
child: Container(
|
||||||
|
width: double.maxFinite,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(28),
|
||||||
|
color: thisItem == selectedItem
|
||||||
|
? Theme.of(context).colorScheme.primaryContainer
|
||||||
|
: null
|
||||||
|
),
|
||||||
|
child: tileBody
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
92
lib/widgets/menu_bar.dart
Normal file
92
lib/widgets/menu_bar.dart
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
import 'package:adguard_home_manager/config/app_screens.dart';
|
||||||
|
import 'package:adguard_home_manager/models/app_screen.dart';
|
||||||
|
import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
||||||
|
import 'package:adguard_home_manager/providers/servers_provider.dart';
|
||||||
|
|
||||||
|
class CustomMenuBar extends StatelessWidget {
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
const CustomMenuBar({
|
||||||
|
Key? key,
|
||||||
|
required this.child
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final serversProvider = Provider.of<ServersProvider>(context);
|
||||||
|
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||||
|
|
||||||
|
List<AppScreen> screens = serversProvider.selectedServer != null
|
||||||
|
? screensServerConnected
|
||||||
|
: screensSelectServer;
|
||||||
|
|
||||||
|
String translatedName(String key) {
|
||||||
|
switch (key) {
|
||||||
|
case 'connect':
|
||||||
|
return AppLocalizations.of(context)!.connect;
|
||||||
|
|
||||||
|
case 'home':
|
||||||
|
return AppLocalizations.of(context)!.home;
|
||||||
|
|
||||||
|
case 'settings':
|
||||||
|
return AppLocalizations.of(context)!.settings;
|
||||||
|
|
||||||
|
case 'clients':
|
||||||
|
return AppLocalizations.of(context)!.clients;
|
||||||
|
|
||||||
|
case 'logs':
|
||||||
|
return AppLocalizations.of(context)!.logs;
|
||||||
|
|
||||||
|
case 'filters':
|
||||||
|
return AppLocalizations.of(context)!.filters;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return PlatformMenuBar(
|
||||||
|
menus: [
|
||||||
|
PlatformMenu(
|
||||||
|
label: 'AdGuard Home Manager',
|
||||||
|
menus: <PlatformMenuItem>[
|
||||||
|
if (
|
||||||
|
PlatformProvidedMenuItem.hasMenu(PlatformProvidedMenuItemType.about)
|
||||||
|
) const PlatformMenuItemGroup(
|
||||||
|
members: [
|
||||||
|
PlatformProvidedMenuItem(
|
||||||
|
type: PlatformProvidedMenuItemType.about,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
if (
|
||||||
|
PlatformProvidedMenuItem.hasMenu(PlatformProvidedMenuItemType.quit)
|
||||||
|
) const PlatformMenuItemGroup(
|
||||||
|
members: [
|
||||||
|
PlatformProvidedMenuItem(
|
||||||
|
type: PlatformProvidedMenuItemType.quit,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
PlatformMenu(
|
||||||
|
label: AppLocalizations.of(context)!.screens,
|
||||||
|
menus: <PlatformMenuItem>[
|
||||||
|
PlatformMenuItemGroup(
|
||||||
|
members: screens.asMap().entries.map((e) => PlatformMenuItem(
|
||||||
|
label: "${appConfigProvider.selectedScreen == e.key ? '✔' : ''} ${translatedName(e.value.name)}",
|
||||||
|
onSelected: () => appConfigProvider.setSelectedScreen(e.key),
|
||||||
|
)).toList()
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
81
lib/widgets/navigation_rail.dart
Normal file
81
lib/widgets/navigation_rail.dart
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
import 'package:adguard_home_manager/providers/logs_provider.dart';
|
||||||
|
import 'package:adguard_home_manager/config/app_screens.dart';
|
||||||
|
import 'package:adguard_home_manager/models/app_screen.dart';
|
||||||
|
import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
||||||
|
import 'package:adguard_home_manager/providers/servers_provider.dart';
|
||||||
|
|
||||||
|
class SideNavigationRail extends StatelessWidget {
|
||||||
|
const SideNavigationRail({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final serversProvider = Provider.of<ServersProvider>(context);
|
||||||
|
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||||
|
final logsProvider = Provider.of<LogsProvider>(context);
|
||||||
|
|
||||||
|
List<AppScreen> screens = serversProvider.selectedServer != null
|
||||||
|
? screensServerConnected
|
||||||
|
: screensSelectServer;
|
||||||
|
|
||||||
|
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 '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NavigationRail(
|
||||||
|
selectedIndex: appConfigProvider.selectedScreen,
|
||||||
|
destinations: screens.map((screen) => NavigationRailDestination(
|
||||||
|
icon: Icon(
|
||||||
|
screen.icon,
|
||||||
|
color: screens[appConfigProvider.selectedScreen] == screen
|
||||||
|
? Theme.of(context).colorScheme.onSecondaryContainer
|
||||||
|
: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
label: Text(translatedName(screen.name))
|
||||||
|
)).toList(),
|
||||||
|
onDestinationSelected: (value) {
|
||||||
|
// Reset clients tab to 0 when changing screen
|
||||||
|
if (value != 1) {
|
||||||
|
appConfigProvider.setSelectedClientsTab(0);
|
||||||
|
}
|
||||||
|
// Reset logs filters when changing screen
|
||||||
|
if (value != 2) {
|
||||||
|
logsProvider.resetFilters();
|
||||||
|
}
|
||||||
|
// Reset settings selected screen
|
||||||
|
if (value != screens.length-1) {
|
||||||
|
appConfigProvider.setSelectedSettingsScreen(screen: null);
|
||||||
|
}
|
||||||
|
appConfigProvider.setSelectedScreen(value);
|
||||||
|
},
|
||||||
|
labelType: NavigationRailLabelType.all,
|
||||||
|
useIndicator: true,
|
||||||
|
groupAlignment: 0,
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.primary.withOpacity(0.05),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,6 +20,7 @@ class ProcessDialog extends StatelessWidget {
|
||||||
horizontal: 30
|
horizontal: 30
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
const CircularProgressIndicator(),
|
const CircularProgressIndicator(),
|
||||||
const SizedBox(width: 40),
|
const SizedBox(width: 40),
|
||||||
|
|
|
@ -23,9 +23,17 @@ class DeleteModal extends StatelessWidget {
|
||||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||||
|
|
||||||
void removeServer() async {
|
void removeServer() async {
|
||||||
|
final previouslySelectedServer = serversProvider.selectedServer;
|
||||||
|
|
||||||
final deleted = await serversProvider.removeServer(serverToDelete);
|
final deleted = await serversProvider.removeServer(serverToDelete);
|
||||||
|
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
|
|
||||||
if (deleted == true) {
|
if (deleted == true) {
|
||||||
|
if (previouslySelectedServer != null && previouslySelectedServer.id == serverToDelete.id) {
|
||||||
|
appConfigProvider.setSelectedScreen(0);
|
||||||
|
}
|
||||||
|
|
||||||
showSnacbkar(
|
showSnacbkar(
|
||||||
context: context,
|
context: context,
|
||||||
appConfigProvider: appConfigProvider,
|
appConfigProvider: appConfigProvider,
|
||||||
|
|
|
@ -5,6 +5,7 @@ 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/widgets/servers_list/servers_list_item.dart';
|
import 'package:adguard_home_manager/widgets/servers_list/servers_list_item.dart';
|
||||||
|
import 'package:adguard_home_manager/widgets/servers_list/servers_tile_item.dart';
|
||||||
|
|
||||||
import 'package:adguard_home_manager/providers/servers_provider.dart';
|
import 'package:adguard_home_manager/providers/servers_provider.dart';
|
||||||
|
|
||||||
|
@ -13,6 +14,7 @@ class ServersList extends StatelessWidget {
|
||||||
final List<ExpandableController> controllers;
|
final List<ExpandableController> controllers;
|
||||||
final Function(int) onChange;
|
final Function(int) onChange;
|
||||||
final ScrollController scrollController;
|
final ScrollController scrollController;
|
||||||
|
final double breakingWidth;
|
||||||
|
|
||||||
const ServersList({
|
const ServersList({
|
||||||
Key? key,
|
Key? key,
|
||||||
|
@ -20,23 +22,44 @@ class ServersList extends StatelessWidget {
|
||||||
required this.controllers,
|
required this.controllers,
|
||||||
required this.onChange,
|
required this.onChange,
|
||||||
required this.scrollController,
|
required this.scrollController,
|
||||||
|
required this.breakingWidth
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final serversProvider = Provider.of<ServersProvider>(context);
|
final serversProvider = Provider.of<ServersProvider>(context);
|
||||||
|
|
||||||
|
final width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
if (serversProvider.serversList.isNotEmpty) {
|
if (serversProvider.serversList.isNotEmpty) {
|
||||||
return ListView.builder(
|
if (width > breakingWidth) {
|
||||||
controller: scrollController,
|
return ListView(
|
||||||
itemCount: serversProvider.serversList.length,
|
children: [
|
||||||
itemBuilder: (context, index) => ServersListItem(
|
Wrap(
|
||||||
expandableController: controllers[index],
|
children: serversProvider.serversList.asMap().entries.map(
|
||||||
server: serversProvider.serversList[index],
|
(s) => ServersTileItem(
|
||||||
index: index,
|
server: serversProvider.serversList[s.key],
|
||||||
onChange: onChange
|
index: s.key,
|
||||||
)
|
onChange: onChange
|
||||||
);
|
)
|
||||||
|
).toList(),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return ListView.builder(
|
||||||
|
controller: scrollController,
|
||||||
|
itemCount: serversProvider.serversList.length,
|
||||||
|
itemBuilder: (context, index) => ServersListItem(
|
||||||
|
expandableController: controllers[index],
|
||||||
|
server: serversProvider.serversList[index],
|
||||||
|
index: index,
|
||||||
|
onChange: onChange
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
|
|
|
@ -71,6 +71,8 @@ class _ServersListItemState extends State<ServersListItem> with SingleTickerProv
|
||||||
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 width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
void showDeleteModal(Server server) async {
|
void showDeleteModal(Server server) async {
|
||||||
await Future.delayed(const Duration(seconds: 0), () => {
|
await Future.delayed(const Duration(seconds: 0), () => {
|
||||||
showDialog(
|
showDialog(
|
||||||
|
@ -85,10 +87,25 @@ class _ServersListItemState extends State<ServersListItem> with SingleTickerProv
|
||||||
|
|
||||||
void openAddServerBottomSheet({Server? server}) async {
|
void openAddServerBottomSheet({Server? server}) async {
|
||||||
await Future.delayed(const Duration(seconds: 0), (() => {
|
await Future.delayed(const Duration(seconds: 0), (() => {
|
||||||
Navigator.push(context, MaterialPageRoute(
|
if (width > 700) {
|
||||||
fullscreenDialog: true,
|
showDialog(
|
||||||
builder: (BuildContext context) => AddServerModal(server: server)
|
context: context,
|
||||||
))
|
barrierDismissible: false,
|
||||||
|
builder: (context) => AddServerModal(
|
||||||
|
server: server,
|
||||||
|
window: true,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Navigator.push(context, MaterialPageRoute(
|
||||||
|
fullscreenDialog: true,
|
||||||
|
builder: (BuildContext context) => AddServerModal(
|
||||||
|
server: server,
|
||||||
|
window: false,
|
||||||
|
)
|
||||||
|
))
|
||||||
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -356,7 +373,6 @@ class _ServersListItemState extends State<ServersListItem> with SingleTickerProv
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border(
|
border: Border(
|
||||||
|
|
381
lib/widgets/servers_list/servers_tile_item.dart
Normal file
381
lib/widgets/servers_list/servers_tile_item.dart
Normal file
|
@ -0,0 +1,381 @@
|
||||||
|
// ignore_for_file: use_build_context_synchronously
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
import 'package:adguard_home_manager/widgets/add_server_modal.dart';
|
||||||
|
import 'package:adguard_home_manager/widgets/servers_list/delete_modal.dart';
|
||||||
|
|
||||||
|
import 'package:adguard_home_manager/classes/process_modal.dart';
|
||||||
|
import 'package:adguard_home_manager/functions/snackbar.dart';
|
||||||
|
import 'package:adguard_home_manager/models/app_log.dart';
|
||||||
|
import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
||||||
|
import 'package:adguard_home_manager/services/http_requests.dart';
|
||||||
|
import 'package:adguard_home_manager/models/server.dart';
|
||||||
|
import 'package:adguard_home_manager/providers/servers_provider.dart';
|
||||||
|
|
||||||
|
class ServersTileItem extends StatefulWidget {
|
||||||
|
final Server server;
|
||||||
|
final int index;
|
||||||
|
final void Function(int) onChange;
|
||||||
|
|
||||||
|
const ServersTileItem({
|
||||||
|
Key? key,
|
||||||
|
required this.server,
|
||||||
|
required this.index,
|
||||||
|
required this.onChange
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ServersTileItem> createState() => _ServersTileItemState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ServersTileItemState extends State<ServersTileItem> with SingleTickerProviderStateMixin {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final serversProvider = Provider.of<ServersProvider>(context);
|
||||||
|
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||||
|
|
||||||
|
final width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
|
void showDeleteModal(Server server) async {
|
||||||
|
await Future.delayed(const Duration(seconds: 0), () => {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => DeleteModal(
|
||||||
|
serverToDelete: server,
|
||||||
|
),
|
||||||
|
barrierDismissible: false
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void openAddServerBottomSheet({Server? server}) async {
|
||||||
|
await Future.delayed(const Duration(seconds: 0), (() => {
|
||||||
|
if (width > 700) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
|
builder: (context) => AddServerModal(
|
||||||
|
server: server,
|
||||||
|
window: true,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Navigator.push(context, MaterialPageRoute(
|
||||||
|
fullscreenDialog: true,
|
||||||
|
builder: (BuildContext context) => AddServerModal(
|
||||||
|
server: server,
|
||||||
|
window: false,
|
||||||
|
)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
void connectToServer(Server server) async {
|
||||||
|
final ProcessModal process = ProcessModal(context: context);
|
||||||
|
process.open(AppLocalizations.of(context)!.connecting);
|
||||||
|
|
||||||
|
final result = server.runningOnHa == true
|
||||||
|
? await loginHA(server)
|
||||||
|
: await login(server);
|
||||||
|
|
||||||
|
if (result['result'] == 'success') {
|
||||||
|
serversProvider.setSelectedServer(server);
|
||||||
|
|
||||||
|
serversProvider.setServerStatusLoad(0);
|
||||||
|
final serverStatus = await getServerStatus(server);
|
||||||
|
if (serverStatus['result'] == 'success') {
|
||||||
|
serversProvider.setServerStatusData(serverStatus['data']);
|
||||||
|
serversProvider.checkServerUpdatesAvailable(server);
|
||||||
|
serversProvider.setServerStatusLoad(1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
appConfigProvider.addLog(serverStatus['log']);
|
||||||
|
serversProvider.setServerStatusLoad(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
process.close();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
process.close();
|
||||||
|
appConfigProvider.addLog(result['log']);
|
||||||
|
showSnacbkar(
|
||||||
|
context: context,
|
||||||
|
appConfigProvider: appConfigProvider,
|
||||||
|
label: AppLocalizations.of(context)!.cannotConnect,
|
||||||
|
color: Colors.red
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setDefaultServer(Server server) async {
|
||||||
|
final result = await serversProvider.setDefaultServer(server);
|
||||||
|
if (result == null) {
|
||||||
|
showSnacbkar(
|
||||||
|
context: context,
|
||||||
|
appConfigProvider: appConfigProvider,
|
||||||
|
label: AppLocalizations.of(context)!.connectionDefaultSuccessfully,
|
||||||
|
color: Colors.green
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
appConfigProvider.addLog(
|
||||||
|
AppLog(
|
||||||
|
type: 'set_default_server',
|
||||||
|
dateTime: DateTime.now(),
|
||||||
|
message: result.toString()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
showSnacbkar(
|
||||||
|
context: context,
|
||||||
|
appConfigProvider: appConfigProvider,
|
||||||
|
label: AppLocalizations.of(context)!.connectionDefaultFailed,
|
||||||
|
color: Colors.red
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget leadingIcon(Server server) {
|
||||||
|
if (server.defaultServer == true) {
|
||||||
|
return Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.storage_rounded,
|
||||||
|
color: serversProvider.selectedServer != null && serversProvider.selectedServer?.id == server.id
|
||||||
|
? serversProvider.serverStatus.data != null
|
||||||
|
? Colors.green
|
||||||
|
: Colors.orange
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: 25,
|
||||||
|
height: 25,
|
||||||
|
child: Stack(
|
||||||
|
alignment: Alignment.bottomRight,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(1),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).colorScheme.primaryContainer,
|
||||||
|
borderRadius: BorderRadius.circular(20)
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
Icons.star,
|
||||||
|
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||||
|
size: 10,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return Icon(
|
||||||
|
Icons.storage_rounded,
|
||||||
|
color: serversProvider.selectedServer != null && serversProvider.selectedServer?.id == server.id
|
||||||
|
? serversProvider.serverStatus.data != null
|
||||||
|
? Colors.green
|
||||||
|
: Colors.orange
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget topRow(Server server, int index) {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
margin: const EdgeInsets.only(right: 16),
|
||||||
|
child: leadingIcon(server),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"${server.connectionMethod}://${server.domain}${server.path ?? ""}${server.port != null ? ':${server.port}' : ""}",
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 3),
|
||||||
|
Text(
|
||||||
|
server.name,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget bottomRow(Server server, int index) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
PopupMenuButton(
|
||||||
|
// color: Theme.of(context).dialogBackgroundColor,
|
||||||
|
itemBuilder: (context) => [
|
||||||
|
PopupMenuItem(
|
||||||
|
enabled: server.defaultServer == false
|
||||||
|
? true
|
||||||
|
: false,
|
||||||
|
onTap: server.defaultServer == false
|
||||||
|
? (() => setDefaultServer(server))
|
||||||
|
: null,
|
||||||
|
child: SizedBox(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.star),
|
||||||
|
const SizedBox(width: 15),
|
||||||
|
Text(
|
||||||
|
server.defaultServer == true
|
||||||
|
? AppLocalizations.of(context)!.defaultConnection
|
||||||
|
: AppLocalizations.of(context)!.setDefault,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
PopupMenuItem(
|
||||||
|
onTap: (() => openAddServerBottomSheet(server: server)),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.edit),
|
||||||
|
const SizedBox(width: 15),
|
||||||
|
Text(AppLocalizations.of(context)!.edit)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
),
|
||||||
|
PopupMenuItem(
|
||||||
|
onTap: (() => showDeleteModal(server)),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.delete),
|
||||||
|
const SizedBox(width: 15),
|
||||||
|
Text(AppLocalizations.of(context)!.delete)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
child: serversProvider.selectedServer != null &&
|
||||||
|
serversProvider.selectedServer != null && serversProvider.selectedServer?.id == server.id && serversProvider.serverStatus.data != null &&
|
||||||
|
serversProvider.selectedServer?.id == server.id
|
||||||
|
? Container(
|
||||||
|
margin: const EdgeInsets.only(right: 12),
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 10),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: serversProvider.selectedServer != null && serversProvider.selectedServer?.id == server.id && serversProvider.serverStatus.data != null
|
||||||
|
? Colors.green
|
||||||
|
: Colors.orange,
|
||||||
|
borderRadius: BorderRadius.circular(30)
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
serversProvider.selectedServer != null && serversProvider.selectedServer?.id == server.id && serversProvider.serverStatus.data != null
|
||||||
|
? Icons.check
|
||||||
|
: Icons.warning,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Text(
|
||||||
|
serversProvider.selectedServer != null && serversProvider.selectedServer?.id == server.id && serversProvider.serverStatus.data != null
|
||||||
|
? AppLocalizations.of(context)!.connected
|
||||||
|
: AppLocalizations.of(context)!.selectedDisconnected,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.w500
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: Container(
|
||||||
|
margin: const EdgeInsets.only(right: 10),
|
||||||
|
child: TextButton(
|
||||||
|
onPressed: () => connectToServer(server),
|
||||||
|
child: Text(AppLocalizations.of(context)!.connect),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
EdgeInsets generateMargins(int index) {
|
||||||
|
if (index == 0) {
|
||||||
|
return const EdgeInsets.only(top: 16, left: 16, right: 8, bottom: 8);
|
||||||
|
}
|
||||||
|
if (index == 1) {
|
||||||
|
return const EdgeInsets.only(top: 16, left: 8, right: 16, bottom: 8);
|
||||||
|
}
|
||||||
|
else if (index == serversProvider.serversList.length-1 && (index+1)%2 == 0) {
|
||||||
|
return const EdgeInsets.only(top: 8, left: 8, right: 16, bottom: 16);
|
||||||
|
}
|
||||||
|
else if (index == serversProvider.serversList.length-1 && (index+1)%2 == 1) {
|
||||||
|
return const EdgeInsets.only(top: 8, left: 16, right: 8, bottom: 16);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if ((index+1)%2 == 0) {
|
||||||
|
return const EdgeInsets.only(top: 8, left: 8, right: 16, bottom: 8);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return const EdgeInsets.only(top: 8, left: 16, right: 8, bottom: 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return FractionallySizedBox(
|
||||||
|
widthFactor: 0.5,
|
||||||
|
child: Card(
|
||||||
|
margin: generateMargins(widget.index),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
topRow(widget.server, widget.index),
|
||||||
|
bottomRow(widget.server, widget.index)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,6 +15,8 @@ class CustomTabContentList extends StatelessWidget {
|
||||||
final double? refreshIndicatorOffset;
|
final double? refreshIndicatorOffset;
|
||||||
final Widget? fab;
|
final Widget? fab;
|
||||||
final bool? fabVisible;
|
final bool? fabVisible;
|
||||||
|
final bool? noSliver;
|
||||||
|
final EdgeInsets? listPadding;
|
||||||
|
|
||||||
const CustomTabContentList({
|
const CustomTabContentList({
|
||||||
Key? key,
|
Key? key,
|
||||||
|
@ -27,7 +29,9 @@ class CustomTabContentList extends StatelessWidget {
|
||||||
required this.onRefresh,
|
required this.onRefresh,
|
||||||
this.refreshIndicatorOffset,
|
this.refreshIndicatorOffset,
|
||||||
this.fab,
|
this.fab,
|
||||||
this.fabVisible
|
this.fabVisible,
|
||||||
|
this.noSliver,
|
||||||
|
this.listPadding
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -36,95 +40,157 @@ class CustomTabContentList extends StatelessWidget {
|
||||||
|
|
||||||
switch (loadStatus) {
|
switch (loadStatus) {
|
||||||
case LoadStatus.loading:
|
case LoadStatus.loading:
|
||||||
return SafeArea(
|
if (noSliver == true) {
|
||||||
top: false,
|
return Padding(
|
||||||
bottom: false,
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
child: Builder(
|
child: loadingGenerator()
|
||||||
builder: (BuildContext context) => CustomScrollView(
|
);
|
||||||
slivers: [
|
}
|
||||||
SliverOverlapInjector(
|
else {
|
||||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
return SafeArea(
|
||||||
),
|
top: false,
|
||||||
SliverFillRemaining(
|
bottom: false,
|
||||||
child: Padding(
|
child: Builder(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
builder: (BuildContext context) => CustomScrollView(
|
||||||
child: loadingGenerator()
|
slivers: [
|
||||||
|
SliverOverlapInjector(
|
||||||
|
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||||
),
|
),
|
||||||
)
|
SliverFillRemaining(
|
||||||
],
|
child: Padding(
|
||||||
),
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
)
|
child: loadingGenerator()
|
||||||
);
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
case LoadStatus.loaded:
|
case LoadStatus.loaded:
|
||||||
return Stack(
|
if (noSliver == true) {
|
||||||
children: [
|
if (itemsCount > 0) {
|
||||||
SafeArea(
|
return Stack(
|
||||||
top: false,
|
children: [
|
||||||
bottom: false,
|
ListView.builder(
|
||||||
child: Builder(
|
padding: listPadding,
|
||||||
builder: (BuildContext context) {
|
itemCount: itemsCount,
|
||||||
return RefreshIndicator(
|
itemBuilder: (context, index) => contentWidget(index),
|
||||||
onRefresh: onRefresh,
|
),
|
||||||
edgeOffset: refreshIndicatorOffset ?? 95,
|
if (fab != null) AnimatedPositioned(
|
||||||
child: CustomScrollView(
|
duration: const Duration(milliseconds: 100),
|
||||||
slivers: <Widget>[
|
curve: Curves.easeInOut,
|
||||||
SliverOverlapInjector(
|
bottom: fabVisible != null && fabVisible == true ?
|
||||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
appConfigProvider.showingSnackbar
|
||||||
),
|
? 70 : 20
|
||||||
if (itemsCount > 0) SliverList(
|
: -70,
|
||||||
delegate: SliverChildBuilderDelegate(
|
right: 20,
|
||||||
(context, index) => contentWidget(index),
|
child: fab!
|
||||||
childCount: itemsCount
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
noData,
|
||||||
|
if (fab != null) AnimatedPositioned(
|
||||||
|
duration: const Duration(milliseconds: 100),
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
bottom: fabVisible != null && fabVisible == true ?
|
||||||
|
appConfigProvider.showingSnackbar
|
||||||
|
? 70 : 20
|
||||||
|
: -70,
|
||||||
|
right: 20,
|
||||||
|
child: fab!
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
SafeArea(
|
||||||
|
top: false,
|
||||||
|
bottom: false,
|
||||||
|
child: Builder(
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return RefreshIndicator(
|
||||||
|
onRefresh: onRefresh,
|
||||||
|
edgeOffset: refreshIndicatorOffset ?? 70,
|
||||||
|
child: CustomScrollView(
|
||||||
|
slivers: <Widget>[
|
||||||
|
SliverOverlapInjector(
|
||||||
|
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||||
),
|
),
|
||||||
),
|
if (itemsCount > 0) SliverList(
|
||||||
if (itemsCount == 0) SliverFillRemaining(
|
delegate: SliverChildBuilderDelegate(
|
||||||
child: noData,
|
(context, index) => contentWidget(index),
|
||||||
)
|
childCount: itemsCount
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
);
|
if (itemsCount == 0) SliverFillRemaining(
|
||||||
},
|
child: noData,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
if (fab != null) AnimatedPositioned(
|
||||||
if (fab != null) AnimatedPositioned(
|
duration: const Duration(milliseconds: 100),
|
||||||
duration: const Duration(milliseconds: 100),
|
curve: Curves.easeInOut,
|
||||||
curve: Curves.easeInOut,
|
bottom: fabVisible != null && fabVisible == true ?
|
||||||
bottom: fabVisible != null && fabVisible == true ?
|
appConfigProvider.showingSnackbar
|
||||||
appConfigProvider.showingSnackbar
|
? 70 : 20
|
||||||
? 70 : 20
|
: -70,
|
||||||
: -70,
|
right: 20,
|
||||||
right: 20,
|
child: fab!
|
||||||
child: fab!
|
),
|
||||||
),
|
],
|
||||||
],
|
);
|
||||||
);
|
}
|
||||||
|
|
||||||
case LoadStatus.error:
|
case LoadStatus.error:
|
||||||
return SafeArea(
|
if (noSliver == true) {
|
||||||
top: false,
|
return Padding(
|
||||||
bottom: false,
|
padding: const EdgeInsets.only(
|
||||||
child: Builder(
|
top: 95,
|
||||||
builder: (BuildContext context) => CustomScrollView(
|
left: 16,
|
||||||
slivers: [
|
right: 16
|
||||||
SliverOverlapInjector(
|
|
||||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
|
||||||
),
|
|
||||||
SliverFillRemaining(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.only(
|
|
||||||
top: 95,
|
|
||||||
left: 16,
|
|
||||||
right: 16
|
|
||||||
),
|
|
||||||
child: errorGenerator()
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
)
|
child: errorGenerator()
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return SafeArea(
|
||||||
|
top: false,
|
||||||
|
bottom: false,
|
||||||
|
child: Builder(
|
||||||
|
builder: (BuildContext context) => CustomScrollView(
|
||||||
|
slivers: [
|
||||||
|
SliverOverlapInjector(
|
||||||
|
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||||
|
),
|
||||||
|
SliverFillRemaining(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
top: 95,
|
||||||
|
left: 16,
|
||||||
|
right: 16
|
||||||
|
),
|
||||||
|
child: errorGenerator()
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return const SizedBox();
|
return const SizedBox();
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
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';
|
||||||
|
@ -22,14 +24,30 @@ class UpdateModal extends StatefulWidget {
|
||||||
class _UpdateModalState extends State<UpdateModal> {
|
class _UpdateModalState extends State<UpdateModal> {
|
||||||
bool doNotRemember = false;
|
bool doNotRemember = false;
|
||||||
|
|
||||||
String getDownloadLink() {
|
String? getDownloadLink() {
|
||||||
return widget.gitHubRelease.assets.firstWhere((item) => item.browserDownloadUrl.contains('apk')).browserDownloadUrl;
|
if (Platform.isAndroid) {
|
||||||
|
return widget.gitHubRelease.assets.firstWhere((item) => item.browserDownloadUrl.contains('apk')).browserDownloadUrl;
|
||||||
|
}
|
||||||
|
else if (Platform.isMacOS) {
|
||||||
|
return widget.gitHubRelease.assets.firstWhere((item) => item.browserDownloadUrl.contains('macOS')).browserDownloadUrl; // macOS package is a zip
|
||||||
|
}
|
||||||
|
else if (Platform.isWindows) {
|
||||||
|
return widget.gitHubRelease.assets.firstWhere((item) => item.browserDownloadUrl.contains('exe')).browserDownloadUrl;
|
||||||
|
}
|
||||||
|
else if (Platform.isLinux) {
|
||||||
|
return widget.gitHubRelease.assets.firstWhere((item) => item.browserDownloadUrl.contains('deb')).browserDownloadUrl;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||||
|
|
||||||
|
final downloadLink = getDownloadLink();
|
||||||
|
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
scrollable: true,
|
scrollable: true,
|
||||||
title: Column(
|
title: Column(
|
||||||
|
@ -104,10 +122,10 @@ class _UpdateModalState extends State<UpdateModal> {
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
TextButton(
|
if (downloadLink != null) TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
widget.onDownload(getDownloadLink(), widget.gitHubRelease.tagName);
|
widget.onDownload(downloadLink, widget.gitHubRelease.tagName);
|
||||||
},
|
},
|
||||||
child: Text(AppLocalizations.of(context)!.download)
|
child: Text(AppLocalizations.of(context)!.download)
|
||||||
),
|
),
|
||||||
|
|
1
linux/.gitignore
vendored
Normal file
1
linux/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
flutter/ephemeral
|
138
linux/CMakeLists.txt
Normal file
138
linux/CMakeLists.txt
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
# Project-level configuration.
|
||||||
|
cmake_minimum_required(VERSION 3.10)
|
||||||
|
project(runner LANGUAGES CXX)
|
||||||
|
|
||||||
|
# The name of the executable created for the application. Change this to change
|
||||||
|
# the on-disk name of your application.
|
||||||
|
set(BINARY_NAME "adguard_home_manager")
|
||||||
|
# The unique GTK application identifier for this application. See:
|
||||||
|
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
|
||||||
|
set(APPLICATION_ID "com.jgeek00.adguard_home_manager")
|
||||||
|
|
||||||
|
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
|
||||||
|
# versions of CMake.
|
||||||
|
cmake_policy(SET CMP0063 NEW)
|
||||||
|
|
||||||
|
# Load bundled libraries from the lib/ directory relative to the binary.
|
||||||
|
set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
|
||||||
|
|
||||||
|
# Root filesystem for cross-building.
|
||||||
|
if(FLUTTER_TARGET_PLATFORM_SYSROOT)
|
||||||
|
set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
|
||||||
|
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Define build configuration options.
|
||||||
|
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||||
|
set(CMAKE_BUILD_TYPE "Debug" CACHE
|
||||||
|
STRING "Flutter build mode" FORCE)
|
||||||
|
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
|
||||||
|
"Debug" "Profile" "Release")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Compilation settings that should be applied to most targets.
|
||||||
|
#
|
||||||
|
# Be cautious about adding new options here, as plugins use this function by
|
||||||
|
# default. In most cases, you should add new options to specific targets instead
|
||||||
|
# of modifying this function.
|
||||||
|
function(APPLY_STANDARD_SETTINGS TARGET)
|
||||||
|
target_compile_features(${TARGET} PUBLIC cxx_std_14)
|
||||||
|
target_compile_options(${TARGET} PRIVATE -Wall -Werror)
|
||||||
|
target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
|
||||||
|
target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
# Flutter library and tool build rules.
|
||||||
|
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
|
||||||
|
add_subdirectory(${FLUTTER_MANAGED_DIR})
|
||||||
|
|
||||||
|
# System-level dependencies.
|
||||||
|
find_package(PkgConfig REQUIRED)
|
||||||
|
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
|
||||||
|
|
||||||
|
add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
|
||||||
|
|
||||||
|
# Define the application target. To change its name, change BINARY_NAME above,
|
||||||
|
# not the value here, or `flutter run` will no longer work.
|
||||||
|
#
|
||||||
|
# Any new source files that you add to the application should be added here.
|
||||||
|
add_executable(${BINARY_NAME}
|
||||||
|
"main.cc"
|
||||||
|
"my_application.cc"
|
||||||
|
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Apply the standard set of build settings. This can be removed for applications
|
||||||
|
# that need different build settings.
|
||||||
|
apply_standard_settings(${BINARY_NAME})
|
||||||
|
|
||||||
|
# Add dependency libraries. Add any application-specific dependencies here.
|
||||||
|
target_link_libraries(${BINARY_NAME} PRIVATE flutter)
|
||||||
|
target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
|
||||||
|
|
||||||
|
# Run the Flutter tool portions of the build. This must not be removed.
|
||||||
|
add_dependencies(${BINARY_NAME} flutter_assemble)
|
||||||
|
|
||||||
|
# Only the install-generated bundle's copy of the executable will launch
|
||||||
|
# correctly, since the resources must in the right relative locations. To avoid
|
||||||
|
# people trying to run the unbundled copy, put it in a subdirectory instead of
|
||||||
|
# the default top-level location.
|
||||||
|
set_target_properties(${BINARY_NAME}
|
||||||
|
PROPERTIES
|
||||||
|
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Generated plugin build rules, which manage building the plugins and adding
|
||||||
|
# them to the application.
|
||||||
|
include(flutter/generated_plugins.cmake)
|
||||||
|
|
||||||
|
|
||||||
|
# === Installation ===
|
||||||
|
# By default, "installing" just makes a relocatable bundle in the build
|
||||||
|
# directory.
|
||||||
|
set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
|
||||||
|
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
|
||||||
|
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Start with a clean build bundle directory every time.
|
||||||
|
install(CODE "
|
||||||
|
file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
|
||||||
|
" COMPONENT Runtime)
|
||||||
|
|
||||||
|
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
|
||||||
|
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
|
||||||
|
|
||||||
|
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
|
||||||
|
COMPONENT Runtime)
|
||||||
|
|
||||||
|
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
|
||||||
|
COMPONENT Runtime)
|
||||||
|
|
||||||
|
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||||
|
COMPONENT Runtime)
|
||||||
|
|
||||||
|
foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
|
||||||
|
install(FILES "${bundled_library}"
|
||||||
|
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||||
|
COMPONENT Runtime)
|
||||||
|
endforeach(bundled_library)
|
||||||
|
|
||||||
|
# Fully re-copy the assets directory on each build to avoid having stale files
|
||||||
|
# from a previous install.
|
||||||
|
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
|
||||||
|
install(CODE "
|
||||||
|
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
|
||||||
|
" COMPONENT Runtime)
|
||||||
|
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
|
||||||
|
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
|
||||||
|
|
||||||
|
# Install the AOT library on non-Debug builds only.
|
||||||
|
if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
|
||||||
|
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||||
|
COMPONENT Runtime)
|
||||||
|
endif()
|
88
linux/flutter/CMakeLists.txt
Normal file
88
linux/flutter/CMakeLists.txt
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
# This file controls Flutter-level build steps. It should not be edited.
|
||||||
|
cmake_minimum_required(VERSION 3.10)
|
||||||
|
|
||||||
|
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
|
||||||
|
|
||||||
|
# Configuration provided via flutter tool.
|
||||||
|
include(${EPHEMERAL_DIR}/generated_config.cmake)
|
||||||
|
|
||||||
|
# TODO: Move the rest of this into files in ephemeral. See
|
||||||
|
# https://github.com/flutter/flutter/issues/57146.
|
||||||
|
|
||||||
|
# Serves the same purpose as list(TRANSFORM ... PREPEND ...),
|
||||||
|
# which isn't available in 3.10.
|
||||||
|
function(list_prepend LIST_NAME PREFIX)
|
||||||
|
set(NEW_LIST "")
|
||||||
|
foreach(element ${${LIST_NAME}})
|
||||||
|
list(APPEND NEW_LIST "${PREFIX}${element}")
|
||||||
|
endforeach(element)
|
||||||
|
set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
# === Flutter Library ===
|
||||||
|
# System-level dependencies.
|
||||||
|
find_package(PkgConfig REQUIRED)
|
||||||
|
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
|
||||||
|
pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
|
||||||
|
pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
|
||||||
|
|
||||||
|
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
|
||||||
|
|
||||||
|
# Published to parent scope for install step.
|
||||||
|
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
|
||||||
|
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
|
||||||
|
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
|
||||||
|
set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
|
||||||
|
|
||||||
|
list(APPEND FLUTTER_LIBRARY_HEADERS
|
||||||
|
"fl_basic_message_channel.h"
|
||||||
|
"fl_binary_codec.h"
|
||||||
|
"fl_binary_messenger.h"
|
||||||
|
"fl_dart_project.h"
|
||||||
|
"fl_engine.h"
|
||||||
|
"fl_json_message_codec.h"
|
||||||
|
"fl_json_method_codec.h"
|
||||||
|
"fl_message_codec.h"
|
||||||
|
"fl_method_call.h"
|
||||||
|
"fl_method_channel.h"
|
||||||
|
"fl_method_codec.h"
|
||||||
|
"fl_method_response.h"
|
||||||
|
"fl_plugin_registrar.h"
|
||||||
|
"fl_plugin_registry.h"
|
||||||
|
"fl_standard_message_codec.h"
|
||||||
|
"fl_standard_method_codec.h"
|
||||||
|
"fl_string_codec.h"
|
||||||
|
"fl_value.h"
|
||||||
|
"fl_view.h"
|
||||||
|
"flutter_linux.h"
|
||||||
|
)
|
||||||
|
list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
|
||||||
|
add_library(flutter INTERFACE)
|
||||||
|
target_include_directories(flutter INTERFACE
|
||||||
|
"${EPHEMERAL_DIR}"
|
||||||
|
)
|
||||||
|
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
|
||||||
|
target_link_libraries(flutter INTERFACE
|
||||||
|
PkgConfig::GTK
|
||||||
|
PkgConfig::GLIB
|
||||||
|
PkgConfig::GIO
|
||||||
|
)
|
||||||
|
add_dependencies(flutter flutter_assemble)
|
||||||
|
|
||||||
|
# === Flutter tool backend ===
|
||||||
|
# _phony_ is a non-existent file to force this command to run every time,
|
||||||
|
# since currently there's no way to get a full input/output list from the
|
||||||
|
# flutter tool.
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/_phony_
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E env
|
||||||
|
${FLUTTER_TOOL_ENVIRONMENT}
|
||||||
|
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
|
||||||
|
${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
add_custom_target(flutter_assemble DEPENDS
|
||||||
|
"${FLUTTER_LIBRARY}"
|
||||||
|
${FLUTTER_LIBRARY_HEADERS}
|
||||||
|
)
|
27
linux/flutter/generated_plugin_registrant.cc
Normal file
27
linux/flutter/generated_plugin_registrant.cc
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
//
|
||||||
|
// Generated file. Do not edit.
|
||||||
|
//
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
|
||||||
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
|
#include <dynamic_color/dynamic_color_plugin.h>
|
||||||
|
#include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h>
|
||||||
|
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||||
|
#include <window_size/window_size_plugin.h>
|
||||||
|
|
||||||
|
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||||
|
g_autoptr(FlPluginRegistrar) dynamic_color_registrar =
|
||||||
|
fl_plugin_registry_get_registrar_for_plugin(registry, "DynamicColorPlugin");
|
||||||
|
dynamic_color_plugin_register_with_registrar(dynamic_color_registrar);
|
||||||
|
g_autoptr(FlPluginRegistrar) sqlite3_flutter_libs_registrar =
|
||||||
|
fl_plugin_registry_get_registrar_for_plugin(registry, "Sqlite3FlutterLibsPlugin");
|
||||||
|
sqlite3_flutter_libs_plugin_register_with_registrar(sqlite3_flutter_libs_registrar);
|
||||||
|
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
||||||
|
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
||||||
|
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
||||||
|
g_autoptr(FlPluginRegistrar) window_size_registrar =
|
||||||
|
fl_plugin_registry_get_registrar_for_plugin(registry, "WindowSizePlugin");
|
||||||
|
window_size_plugin_register_with_registrar(window_size_registrar);
|
||||||
|
}
|
15
linux/flutter/generated_plugin_registrant.h
Normal file
15
linux/flutter/generated_plugin_registrant.h
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
//
|
||||||
|
// Generated file. Do not edit.
|
||||||
|
//
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
|
||||||
|
#ifndef GENERATED_PLUGIN_REGISTRANT_
|
||||||
|
#define GENERATED_PLUGIN_REGISTRANT_
|
||||||
|
|
||||||
|
#include <flutter_linux/flutter_linux.h>
|
||||||
|
|
||||||
|
// Registers Flutter plugins.
|
||||||
|
void fl_register_plugins(FlPluginRegistry* registry);
|
||||||
|
|
||||||
|
#endif // GENERATED_PLUGIN_REGISTRANT_
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue