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.
|
||||
|
||||
version:
|
||||
revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
|
||||
revision: f72efea43c3013323d1b95cff571f3c1caa37583
|
||||
channel: stable
|
||||
|
||||
project_type: app
|
||||
|
@ -13,26 +13,11 @@ project_type: app
|
|||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
|
||||
base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
|
||||
- platform: android
|
||||
create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
|
||||
base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
|
||||
- platform: ios
|
||||
create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
|
||||
base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
|
||||
create_revision: f72efea43c3013323d1b95cff571f3c1caa37583
|
||||
base_revision: f72efea43c3013323d1b95cff571f3c1caa37583
|
||||
- platform: linux
|
||||
create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
|
||||
base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
|
||||
- platform: macos
|
||||
create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
|
||||
base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
|
||||
- platform: web
|
||||
create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
|
||||
base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
|
||||
- platform: windows
|
||||
create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
|
||||
base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
|
||||
create_revision: f72efea43c3013323d1b95cff571f3c1caa37583
|
||||
base_revision: f72efea43c3013323d1b95cff571f3c1caa37583
|
||||
|
||||
# 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)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.adguardHomeManager;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.jgeek00.adguard_home_manager;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
|
@ -494,7 +494,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.adguardHomeManager;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.jgeek00.adguard_home_manager;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
|
@ -517,7 +517,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.adguardHomeManager;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.jgeek00.adguard_home_manager;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// ignore_for_file: use_build_context_synchronously, depend_on_referenced_packages
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:animations/animations.dart';
|
||||
|
@ -10,7 +11,9 @@ import 'package:store_checker/store_checker.dart';
|
|||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:adguard_home_manager/widgets/bottom_nav_bar.dart';
|
||||
import 'package:adguard_home_manager/widgets/menu_bar.dart';
|
||||
import 'package:adguard_home_manager/widgets/update_modal.dart';
|
||||
import 'package:adguard_home_manager/widgets/navigation_rail.dart';
|
||||
|
||||
import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
||||
import 'package:adguard_home_manager/models/github_release.dart';
|
||||
|
@ -53,11 +56,23 @@ class _BaseState extends State<Base> with WidgetsBindingObserver {
|
|||
}
|
||||
|
||||
Future<GitHubRelease?> checkInstallationSource() async {
|
||||
Source installationSource = await StoreChecker.getSource;
|
||||
if (installationSource != Source.IS_INSTALLED_FROM_PLAY_STORE) {
|
||||
final result = await checkAppUpdatesGitHub();
|
||||
if (result['result'] == 'success') {
|
||||
if (updateExists(widget.appConfigProvider.getAppInfo!.version, result['body'].tagName)) {
|
||||
final result = await checkAppUpdatesGitHub();
|
||||
if (result['result'] == 'success') {
|
||||
final update = updateExists(widget.appConfigProvider.getAppInfo!.version, result['body'].tagName);
|
||||
if (update == true) {
|
||||
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'];
|
||||
}
|
||||
}
|
||||
|
@ -107,38 +122,51 @@ class _BaseState extends State<Base> with WidgetsBindingObserver {
|
|||
final serversProvider = Provider.of<ServersProvider>(context);
|
||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
|
||||
List<AppScreen> screens = serversProvider.selectedServer != null
|
||||
? screensServerConnected
|
||||
: screensSelectServer;
|
||||
|
||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: SystemUiOverlayStyle(
|
||||
statusBarColor: Colors.transparent,
|
||||
statusBarBrightness: Theme.of(context).brightness == Brightness.light
|
||||
? Brightness.light
|
||||
: Brightness.dark,
|
||||
statusBarIconBrightness: Theme.of(context).brightness == Brightness.light
|
||||
? Brightness.dark
|
||||
: Brightness.light,
|
||||
systemNavigationBarColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
systemNavigationBarIconBrightness: Theme.of(context).brightness == Brightness.light
|
||||
? Brightness.dark
|
||||
: Brightness.light,
|
||||
),
|
||||
child: Scaffold(
|
||||
body: PageTransitionSwitcher(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
transitionBuilder: (
|
||||
(child, primaryAnimation, secondaryAnimation) => FadeThroughTransition(
|
||||
animation: primaryAnimation,
|
||||
secondaryAnimation: secondaryAnimation,
|
||||
child: child,
|
||||
)
|
||||
),
|
||||
child: screens[appConfigProvider.selectedScreen].body,
|
||||
return CustomMenuBar(
|
||||
child: AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: SystemUiOverlayStyle(
|
||||
statusBarColor: Colors.transparent,
|
||||
statusBarBrightness: Theme.of(context).brightness == Brightness.light
|
||||
? Brightness.light
|
||||
: Brightness.dark,
|
||||
statusBarIconBrightness: Theme.of(context).brightness == Brightness.light
|
||||
? Brightness.dark
|
||||
: Brightness.light,
|
||||
systemNavigationBarColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
systemNavigationBarIconBrightness: Theme.of(context).brightness == Brightness.light
|
||||
? Brightness.dark
|
||||
: Brightness.light,
|
||||
),
|
||||
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) {
|
||||
FlutterWebBrowser.openWebPage(
|
||||
url: url,
|
||||
customTabsOptions: const CustomTabsOptions(
|
||||
instantAppsEnabled: true,
|
||||
showTitle: true,
|
||||
urlBarHidingEnabled: false,
|
||||
),
|
||||
safariVCOptions: const SafariViewControllerOptions(
|
||||
barCollapsingEnabled: true,
|
||||
dismissButtonStyle: SafariViewControllerDismissButtonStyle.close,
|
||||
modalPresentationCapturesStatusBarAppearance: true,
|
||||
)
|
||||
);
|
||||
import 'package:flutter_web_browser/flutter_web_browser.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
void openUrl(String url) async {
|
||||
if (Platform.isAndroid || Platform.isIOS) {
|
||||
FlutterWebBrowser.openWebPage(
|
||||
url: url,
|
||||
customTabsOptions: const CustomTabsOptions(
|
||||
instantAppsEnabled: true,
|
||||
showTitle: true,
|
||||
urlBarHidingEnabled: false,
|
||||
),
|
||||
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",
|
||||
"serverStatus": "Server status",
|
||||
"connectionNotUpdated": "Connection not updated",
|
||||
"ruleFilteringWidget": "Rule\nfiltering",
|
||||
"safeBrowsingWidget": "Safe\nbrowsing",
|
||||
"parentalFilteringWidget": "Parental\nfiltering",
|
||||
"safeSearchWidget": "Safe\nsearch",
|
||||
"ruleFilteringWidget": "Rule filtering",
|
||||
"safeBrowsingWidget": "Safe browsing",
|
||||
"parentalFilteringWidget": "Parental filtering",
|
||||
"safeSearchWidget": "Safe search",
|
||||
"ruleFiltering": "Rule filtering",
|
||||
"safeBrowsing": "Safe browsing",
|
||||
"parentalFiltering": "Parental filtering",
|
||||
|
@ -606,5 +606,11 @@
|
|||
"remainingTime": "Remaining time",
|
||||
"safeSearchSettings": "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",
|
||||
"connectionNotUpdated": "Conexión no actualizada",
|
||||
"serverStatus": "Estado del servidor",
|
||||
"ruleFilteringWidget": "Bloqueo por\nfiltros",
|
||||
"safeBrowsingWidget": "Navegación\nsegura",
|
||||
"parentalFilteringWidget": "Control\nparental",
|
||||
"safeSearchWidget": "Búsqueda\nsegura",
|
||||
"ruleFilteringWidget": "Bloqueo por filtros",
|
||||
"safeBrowsingWidget": "Navegación segura",
|
||||
"parentalFilteringWidget": "Control parental",
|
||||
"safeSearchWidget": "Búsqueda segura",
|
||||
"ruleFiltering": "Bloqueo por filtros",
|
||||
"safeBrowsing": "Navegación segura",
|
||||
"parentalFiltering": "Control parental",
|
||||
|
@ -606,5 +606,11 @@
|
|||
"remainingTime": "Tiempo restante",
|
||||
"safeSearchSettings": "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:device_info_plus/device_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_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/base.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/providers/logs_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/providers/servers_provider.dart';
|
||||
|
||||
|
||||
void main() async {
|
||||
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();
|
||||
ServersProvider serversProvider = ServersProvider();
|
||||
|
|
|
@ -4,6 +4,7 @@ import 'package:flutter/scheduler.dart';
|
|||
import 'package:package_info_plus/package_info_plus.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/models/app_log.dart';
|
||||
|
||||
|
@ -16,6 +17,8 @@ class AppConfigProvider with ChangeNotifier {
|
|||
|
||||
int _selectedScreen = 0;
|
||||
|
||||
int? _selectedSettingsScreen;
|
||||
|
||||
bool _showingSnackbar = false;
|
||||
|
||||
int _selectedTheme = 0;
|
||||
|
@ -118,6 +121,10 @@ class AppConfigProvider with ChangeNotifier {
|
|||
return _doNotRememberVersion;
|
||||
}
|
||||
|
||||
int? get selectedSettingsScreen {
|
||||
return _selectedSettingsScreen;
|
||||
}
|
||||
|
||||
void setDbInstance(Database db) {
|
||||
_dbInstance = db;
|
||||
}
|
||||
|
@ -159,8 +166,19 @@ class AppConfigProvider with ChangeNotifier {
|
|||
notifyListeners();
|
||||
}
|
||||
|
||||
void setSelectedSettingsScreen({required int? screen, bool? notify}) {
|
||||
_selectedSettingsScreen = screen;
|
||||
if (notify == true) {
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
_overrideSslCheck = status == true ? 1 : 0;
|
||||
notifyListeners();
|
||||
|
@ -172,7 +190,11 @@ class AppConfigProvider with ChangeNotifier {
|
|||
}
|
||||
|
||||
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) {
|
||||
_hideZeroValues = status == true ? 1 : 0;
|
||||
notifyListeners();
|
||||
|
@ -184,7 +206,11 @@ class AppConfigProvider with ChangeNotifier {
|
|||
}
|
||||
|
||||
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) {
|
||||
_showNameTimeLogs = status == true ? 1 : 0;
|
||||
notifyListeners();
|
||||
|
@ -196,7 +222,11 @@ class AppConfigProvider with ChangeNotifier {
|
|||
}
|
||||
|
||||
Future<bool> setSelectedTheme(int value) async {
|
||||
final updated = await _updateThemeDb(value);
|
||||
final updated = await updateConfigQuery(
|
||||
db: _dbInstance!,
|
||||
column: 'theme',
|
||||
value: value
|
||||
);
|
||||
if (updated == true) {
|
||||
_selectedTheme = value;
|
||||
notifyListeners();
|
||||
|
@ -208,7 +238,11 @@ class AppConfigProvider with ChangeNotifier {
|
|||
}
|
||||
|
||||
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) {
|
||||
_useDynamicColor = value;
|
||||
notifyListeners();
|
||||
|
@ -220,7 +254,11 @@ class AppConfigProvider with ChangeNotifier {
|
|||
}
|
||||
|
||||
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) {
|
||||
_useThemeColorForStatus = value;
|
||||
notifyListeners();
|
||||
|
@ -232,7 +270,11 @@ class AppConfigProvider with ChangeNotifier {
|
|||
}
|
||||
|
||||
Future<bool> setStaticColor(int value) async {
|
||||
final updated = await _updateStaticColorDb(value);
|
||||
final updated = await updateConfigQuery(
|
||||
db: _dbInstance!,
|
||||
column: 'staticColor',
|
||||
value: value
|
||||
);
|
||||
if (updated == true) {
|
||||
_staticColor = value;
|
||||
notifyListeners();
|
||||
|
@ -244,109 +286,12 @@ class AppConfigProvider with ChangeNotifier {
|
|||
}
|
||||
|
||||
Future<bool> setDoNotRememberVersion(String value) async {
|
||||
try {
|
||||
return await _dbInstance!.transaction((txn) async {
|
||||
await txn.rawUpdate(
|
||||
'UPDATE appConfig SET doNotRememberVersion = "$value"',
|
||||
);
|
||||
_doNotRememberVersion = value;
|
||||
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;
|
||||
}
|
||||
final updated = await updateConfigQuery(
|
||||
db: _dbInstance!,
|
||||
column: 'doNotRememberVersion',
|
||||
value: value
|
||||
);
|
||||
return updated;
|
||||
}
|
||||
|
||||
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/filtering_status.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/clients.dart';
|
||||
import 'package:adguard_home_manager/models/server_status.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/functions/time_server_disabled.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/constants/enums.dart';
|
||||
|
||||
|
@ -33,6 +34,9 @@ class ServersProvider with ChangeNotifier {
|
|||
loadStatus: LoadStatus.loading,
|
||||
data: null
|
||||
);
|
||||
String? _searchTermClients;
|
||||
List<AutoClient> _filteredActiveClients = [];
|
||||
List<Client> _filteredAddedClients = [];
|
||||
|
||||
final Filtering _filtering = Filtering(
|
||||
loadStatus: LoadStatus.loading,
|
||||
|
@ -86,6 +90,18 @@ class ServersProvider with ChangeNotifier {
|
|||
return _clients;
|
||||
}
|
||||
|
||||
String? get searchTermClients {
|
||||
return _searchTermClients;
|
||||
}
|
||||
|
||||
List<AutoClient> get filteredActiveClients {
|
||||
return _filteredActiveClients;
|
||||
}
|
||||
|
||||
List<Client> get filteredAddedClients {
|
||||
return _filteredAddedClients;
|
||||
}
|
||||
|
||||
FilteringStatus? get filteringStatus {
|
||||
return _filteringStatus;
|
||||
}
|
||||
|
@ -147,6 +163,43 @@ class ServersProvider with ChangeNotifier {
|
|||
|
||||
void setClientsData(ClientsData 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();
|
||||
}
|
||||
|
||||
|
@ -249,7 +302,7 @@ class ServersProvider with ChangeNotifier {
|
|||
}
|
||||
|
||||
Future<dynamic> createServer(Server server) async {
|
||||
final saved = await saveServerIntoDb(server);
|
||||
final saved = await saveServerQuery(_dbInstance!, server);
|
||||
if (saved == null) {
|
||||
if (server.defaultServer == true) {
|
||||
final defaultServer = await setDefaultServer(server);
|
||||
|
@ -274,7 +327,7 @@ class ServersProvider with ChangeNotifier {
|
|||
}
|
||||
|
||||
Future<dynamic> setDefaultServer(Server server) async {
|
||||
final updated = await setDefaultServerDb(server.id);
|
||||
final updated = await setDefaultServerQuery(_dbInstance!, server.id);
|
||||
if (updated == null) {
|
||||
List<Server> newServers = _serversList.map((s) {
|
||||
if (s.id == server.id) {
|
||||
|
@ -296,7 +349,7 @@ class ServersProvider with ChangeNotifier {
|
|||
}
|
||||
|
||||
Future<dynamic> editServer(Server server) async {
|
||||
final result = await editServerDb(server);
|
||||
final result = await editServerQuery(_dbInstance!, server);
|
||||
if (result == null) {
|
||||
List<Server> newServers = _serversList.map((s) {
|
||||
if (s.id == server.id) {
|
||||
|
@ -316,7 +369,7 @@ class ServersProvider with ChangeNotifier {
|
|||
}
|
||||
|
||||
Future<bool> removeServer(Server server) async {
|
||||
final result = await removeFromDb(server.id);
|
||||
final result = await removeServerQuery(_dbInstance!, server.id);
|
||||
if (result == true) {
|
||||
_selectedServer = null;
|
||||
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 {
|
||||
setUpdateAvailableLoadStatus(LoadStatus.loading, true);
|
||||
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
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:animations/animations.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_split_view/flutter_split_view.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/screens/clients/client_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/fab.dart';
|
||||
import 'package:adguard_home_manager/screens/clients/options_modal.dart';
|
||||
|
@ -27,13 +31,19 @@ class AddedList extends StatefulWidget {
|
|||
final LoadStatus loadStatus;
|
||||
final List<Client> data;
|
||||
final Future Function() fetchClients;
|
||||
final void Function(Client) onClientSelected;
|
||||
final Client? selectedClient;
|
||||
final bool splitView;
|
||||
|
||||
const AddedList({
|
||||
Key? key,
|
||||
required this.scrollController,
|
||||
required this.loadStatus,
|
||||
required this.data,
|
||||
required this.fetchClients
|
||||
required this.fetchClients,
|
||||
required this.onClientSelected,
|
||||
this.selectedClient,
|
||||
required this.splitView
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
@ -69,6 +79,8 @@ class _AddedListState extends State<AddedList> {
|
|||
final serversProvider = Provider.of<ServersProvider>(context);
|
||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
|
||||
void confirmEditClient(Client client) async {
|
||||
ProcessModal processModal = ProcessModal(context: context);
|
||||
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();
|
||||
serversProvider.setClientsData(clientsData);
|
||||
|
||||
if (widget.splitView == true) {
|
||||
SplitView.of(context).popUntil(0);
|
||||
}
|
||||
|
||||
showSnacbkar(
|
||||
context: context,
|
||||
appConfigProvider: appConfigProvider,
|
||||
|
@ -150,15 +166,31 @@ class _AddedListState extends State<AddedList> {
|
|||
}
|
||||
|
||||
void openClientModal(Client client) {
|
||||
Navigator.push(context, MaterialPageRoute(
|
||||
fullscreenDialog: true,
|
||||
builder: (BuildContext context) => ClientScreen(
|
||||
onConfirm: confirmEditClient,
|
||||
serverVersion: serversProvider.serverStatus.data!.serverVersion,
|
||||
onDelete: deleteClient,
|
||||
client: client,
|
||||
)
|
||||
));
|
||||
if (width > 900 || !(Platform.isAndroid | Platform.isIOS)) {
|
||||
showDialog(
|
||||
barrierDismissible: false,
|
||||
context: context,
|
||||
builder: (BuildContext context) => ClientScreen(
|
||||
onConfirm: confirmEditClient,
|
||||
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) {
|
||||
|
@ -181,9 +213,12 @@ class _AddedListState extends State<AddedList> {
|
|||
}
|
||||
|
||||
return CustomTabContentList(
|
||||
noSliver: !(Platform.isAndroid || Platform.isIOS),
|
||||
listPadding: widget.splitView == true
|
||||
? const EdgeInsets.only(top: 8)
|
||||
: null,
|
||||
loadingGenerator: () => SizedBox(
|
||||
width: double.maxFinite,
|
||||
height: MediaQuery.of(context).size.height-171,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
|
@ -202,109 +237,28 @@ class _AddedListState extends State<AddedList> {
|
|||
),
|
||||
),
|
||||
itemsCount: widget.data.length,
|
||||
contentWidget: (index) => ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
||||
isThreeLine: true,
|
||||
onLongPress: () => openOptionsModal(widget.data[index]),
|
||||
onTap: () => openClientModal(widget.data[index]),
|
||||
title: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 5),
|
||||
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,
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
contentWidget: (index) => AddedClientTile(
|
||||
selectedClient: widget.selectedClient,
|
||||
client: widget.data[index],
|
||||
onTap: widget.onClientSelected,
|
||||
onLongPress: openOptionsModal,
|
||||
onEdit: openClientModal,
|
||||
splitView: widget.splitView,
|
||||
),
|
||||
noData: SizedBox(
|
||||
width: double.maxFinite,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context)!.noClientsList,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.noClientsList,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
|
@ -318,7 +272,6 @@ class _AddedListState extends State<AddedList> {
|
|||
),
|
||||
errorGenerator: () => SizedBox(
|
||||
width: double.maxFinite,
|
||||
height: MediaQuery.of(context).size.height-171,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
|
|
|
@ -19,6 +19,7 @@ class ClientScreen extends StatefulWidget {
|
|||
final String serverVersion;
|
||||
final void Function(Client) onConfirm;
|
||||
final void Function(Client)? onDelete;
|
||||
final bool dialog;
|
||||
|
||||
const ClientScreen({
|
||||
Key? key,
|
||||
|
@ -26,6 +27,7 @@ class ClientScreen extends StatefulWidget {
|
|||
required this.serverVersion,
|
||||
required this.onConfirm,
|
||||
this.onDelete,
|
||||
required this.dialog
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
@ -301,50 +303,12 @@ class _ClientScreenState extends State<ClientScreen> {
|
|||
);
|
||||
}
|
||||
|
||||
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: ListView(
|
||||
Widget content(bool withPaddingTop) {
|
||||
return ListView(
|
||||
padding: const EdgeInsets.only(top: 0),
|
||||
children: [
|
||||
const SizedBox(height: 24),
|
||||
if (withPaddingTop == true) const SizedBox(height: 24),
|
||||
if (withPaddingTop == false) const SizedBox(height: 6),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: TextFormField(
|
||||
|
@ -693,10 +657,7 @@ class _ClientScreenState extends State<ClientScreen> {
|
|||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: editMode == true
|
||||
? MediaQuery.of(context).size.width - 108
|
||||
: MediaQuery.of(context).size.width - 40,
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
enabled: editMode,
|
||||
controller: controller['controller'],
|
||||
|
@ -751,7 +712,125 @@ class _ClientScreenState extends State<ClientScreen> {
|
|||
),
|
||||
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_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/clients_list.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/models/app_log.dart';
|
||||
|
@ -56,6 +61,9 @@ class _ClientsWidgetState extends State<ClientsWidget> with TickerProviderStateM
|
|||
late TabController tabController;
|
||||
final ScrollController scrollController = ScrollController();
|
||||
|
||||
bool searchMode = false;
|
||||
final TextEditingController searchController = TextEditingController();
|
||||
|
||||
Future fetchClients() async {
|
||||
widget.setLoadingStatus(LoadStatus.loading, false);
|
||||
final result = await getClients(widget.server);
|
||||
|
@ -90,83 +98,207 @@ class _ClientsWidgetState extends State<ClientsWidget> with TickerProviderStateM
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final serversProvider = Provider.of<ServersProvider>(context);
|
||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||
|
||||
return DefaultTabController(
|
||||
length: 2,
|
||||
child: NestedScrollView(
|
||||
controller: scrollController,
|
||||
headerSliverBuilder: ((context, innerBoxIsScrolled) {
|
||||
return [
|
||||
SliverOverlapAbsorber(
|
||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||
sliver: SliverAppBar(
|
||||
title: Text(AppLocalizations.of(context)!.clients),
|
||||
pinned: true,
|
||||
floating: true,
|
||||
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)
|
||||
)
|
||||
)
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
|
||||
PreferredSizeWidget tabBar() {
|
||||
return TabBar(
|
||||
controller: tabController,
|
||||
unselectedLabelColor: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
tabs: [
|
||||
Tab(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.devices),
|
||||
const SizedBox(width: 8),
|
||||
Text(AppLocalizations.of(context)!.activeClients)
|
||||
],
|
||||
),
|
||||
),
|
||||
child: TabBarView(
|
||||
controller: tabController,
|
||||
children: [
|
||||
ClientsList(
|
||||
scrollController: scrollController,
|
||||
loadStatus: serversProvider.clients.loadStatus,
|
||||
data: serversProvider.clients.loadStatus == LoadStatus.loaded
|
||||
? serversProvider.clients.data!.autoClientsData : [],
|
||||
fetchClients: fetchClients,
|
||||
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: 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:provider/provider.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/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/providers/logs_provider.dart';
|
||||
|
||||
class ClientsList extends StatelessWidget {
|
||||
final ScrollController scrollController;
|
||||
final LoadStatus loadStatus;
|
||||
final List<AutoClient> data;
|
||||
final Future Function() fetchClients;
|
||||
final void Function(AutoClient) onClientSelected;
|
||||
final AutoClient? selectedClient;
|
||||
final bool splitView;
|
||||
final bool sliver;
|
||||
|
||||
const ClientsList({
|
||||
Key? key,
|
||||
required this.scrollController,
|
||||
required this.loadStatus,
|
||||
required this.data,
|
||||
required this.fetchClients
|
||||
required this.fetchClients,
|
||||
required this.onClientSelected,
|
||||
this.selectedClient,
|
||||
required this.splitView,
|
||||
required this.sliver
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||
final logsProvider = Provider.of<LogsProvider>(context);
|
||||
|
||||
return CustomTabContentList(
|
||||
listPadding: splitView == true
|
||||
? const EdgeInsets.only(top: 8)
|
||||
: null,
|
||||
noSliver: !sliver,
|
||||
loadingGenerator: () => SizedBox(
|
||||
width: double.maxFinite,
|
||||
height: MediaQuery.of(context).size.height-171,
|
||||
|
@ -52,31 +60,11 @@ class ClientsList extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
itemsCount: data.length,
|
||||
contentWidget: (index) => CustomListTile(
|
||||
title: data[index].name != ''
|
||||
? data[index].name!
|
||||
: data[index].ip,
|
||||
subtitle: data[index].name != ''
|
||||
? 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);
|
||||
},
|
||||
contentWidget: (index) => ActiveClientTile(
|
||||
client: data[index],
|
||||
onTap: onClientSelected,
|
||||
splitView: splitView,
|
||||
selectedClient: selectedClient,
|
||||
),
|
||||
noData: SizedBox(
|
||||
width: double.maxFinite,
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
// ignore_for_file: use_build_context_synchronously
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
@ -23,6 +25,8 @@ class ClientsFab extends StatelessWidget {
|
|||
final serversProvider = Provider.of<ServersProvider>(context);
|
||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
|
||||
void confirmAddClient(Client client) async {
|
||||
ProcessModal processModal = ProcessModal(context: context);
|
||||
processModal.open(AppLocalizations.of(context)!.addingClient);
|
||||
|
@ -65,13 +69,27 @@ class ClientsFab extends StatelessWidget {
|
|||
}
|
||||
|
||||
void openAddClient() {
|
||||
Navigator.push(context, MaterialPageRoute(
|
||||
fullscreenDialog: true,
|
||||
builder: (BuildContext context) => ClientScreen(
|
||||
onConfirm: confirmAddClient,
|
||||
serverVersion: serversProvider.serverStatus.data!.serverVersion,
|
||||
)
|
||||
));
|
||||
if (width > 900 || !(Platform.isAndroid | Platform.isIOS)) {
|
||||
showDialog(
|
||||
barrierDismissible: false,
|
||||
context: context,
|
||||
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(
|
||||
|
|
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(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Material(
|
||||
color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
child: InkWell(
|
||||
onTap: widget.disabled == true
|
||||
? null
|
||||
: () => setState(() => generalEnabled = !generalEnabled),
|
||||
content: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 400
|
||||
),
|
||||
child: Wrap(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Material(
|
||||
color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
vertical: 5
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context)!.enable,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: widget.disabled == true
|
||||
? Colors.grey
|
||||
: Theme.of(context).colorScheme.onSurface
|
||||
child: InkWell(
|
||||
onTap: widget.disabled == true
|
||||
? null
|
||||
: () => setState(() => generalEnabled = !generalEnabled),
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
vertical: 5
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context)!.enable,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: widget.disabled == true
|
||||
? Colors.grey
|
||||
: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
),
|
||||
),
|
||||
Switch(
|
||||
value: generalEnabled,
|
||||
onChanged: widget.disabled == true
|
||||
? null
|
||||
: (value) => setState(() => generalEnabled = value),
|
||||
)
|
||||
],
|
||||
Switch(
|
||||
value: generalEnabled,
|
||||
onChanged: widget.disabled == true
|
||||
? null
|
||||
: (value) => setState(() => generalEnabled = value),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4, width: double.maxFinite),
|
||||
CustomCheckboxListTile(
|
||||
value: bingEnabled,
|
||||
onChanged: (value) => setState(() => bingEnabled = value),
|
||||
title: "Bing",
|
||||
disabled: widget.disabled || !generalEnabled,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 36,
|
||||
vertical: 4
|
||||
const SizedBox(height: 4, width: double.maxFinite),
|
||||
CustomCheckboxListTile(
|
||||
value: bingEnabled,
|
||||
onChanged: (value) => setState(() => bingEnabled = value),
|
||||
title: "Bing",
|
||||
disabled: widget.disabled || !generalEnabled,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 36,
|
||||
vertical: 4
|
||||
),
|
||||
),
|
||||
),
|
||||
CustomCheckboxListTile(
|
||||
value: duckduckgoEnabled,
|
||||
onChanged: (value) => setState(() => duckduckgoEnabled = value),
|
||||
title: "DuckDuckGo",
|
||||
disabled: widget.disabled || !generalEnabled,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 36,
|
||||
vertical: 4
|
||||
CustomCheckboxListTile(
|
||||
value: duckduckgoEnabled,
|
||||
onChanged: (value) => setState(() => duckduckgoEnabled = value),
|
||||
title: "DuckDuckGo",
|
||||
disabled: widget.disabled || !generalEnabled,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 36,
|
||||
vertical: 4
|
||||
),
|
||||
),
|
||||
),
|
||||
CustomCheckboxListTile(
|
||||
value: googleEnabled,
|
||||
onChanged: (value) => setState(() => googleEnabled = value),
|
||||
title: "Google",
|
||||
disabled: widget.disabled || !generalEnabled,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 36,
|
||||
vertical: 4
|
||||
CustomCheckboxListTile(
|
||||
value: googleEnabled,
|
||||
onChanged: (value) => setState(() => googleEnabled = value),
|
||||
title: "Google",
|
||||
disabled: widget.disabled || !generalEnabled,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 36,
|
||||
vertical: 4
|
||||
),
|
||||
),
|
||||
),
|
||||
CustomCheckboxListTile(
|
||||
value: pixabayEnabled,
|
||||
onChanged: (value) => setState(() => pixabayEnabled = value),
|
||||
title: "Pixabay",
|
||||
disabled: widget.disabled || !generalEnabled,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 36,
|
||||
vertical: 4
|
||||
CustomCheckboxListTile(
|
||||
value: pixabayEnabled,
|
||||
onChanged: (value) => setState(() => pixabayEnabled = value),
|
||||
title: "Pixabay",
|
||||
disabled: widget.disabled || !generalEnabled,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 36,
|
||||
vertical: 4
|
||||
),
|
||||
),
|
||||
),
|
||||
CustomCheckboxListTile(
|
||||
value: yandexEnabled,
|
||||
onChanged: (value) => setState(() => yandexEnabled = value),
|
||||
title: "Yandex",
|
||||
disabled: widget.disabled || !generalEnabled,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 36,
|
||||
vertical: 4
|
||||
CustomCheckboxListTile(
|
||||
value: yandexEnabled,
|
||||
onChanged: (value) => setState(() => yandexEnabled = value),
|
||||
title: "Yandex",
|
||||
disabled: widget.disabled || !generalEnabled,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 36,
|
||||
vertical: 4
|
||||
),
|
||||
),
|
||||
),
|
||||
CustomCheckboxListTile(
|
||||
value: youtubeEnabled,
|
||||
onChanged: (value) => setState(() => youtubeEnabled = value),
|
||||
title: "YouTube",
|
||||
disabled: widget.disabled || !generalEnabled,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 36,
|
||||
vertical: 4
|
||||
CustomCheckboxListTile(
|
||||
value: youtubeEnabled,
|
||||
onChanged: (value) => setState(() => youtubeEnabled = value),
|
||||
title: "YouTube",
|
||||
disabled: widget.disabled || !generalEnabled,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 36,
|
||||
vertical: 4
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
// ignore_for_file: use_build_context_synchronously
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:animations/animations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
@ -99,6 +101,8 @@ class _SearchClientsWidgetState extends State<SearchClientsWidget> {
|
|||
final serversProvider = Provider.of<ServersProvider>(context);
|
||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
|
||||
void deleteClient(Client client) async {
|
||||
ProcessModal processModal = ProcessModal(context: context);
|
||||
processModal.open(AppLocalizations.of(context)!.removingClient);
|
||||
|
@ -183,15 +187,31 @@ class _SearchClientsWidgetState extends State<SearchClientsWidget> {
|
|||
}
|
||||
|
||||
void openClientModal(Client client) {
|
||||
Navigator.push(context, MaterialPageRoute(
|
||||
fullscreenDialog: true,
|
||||
builder: (BuildContext context) => ClientScreen(
|
||||
onConfirm: confirmEditClient,
|
||||
onDelete: deleteClient,
|
||||
client: client,
|
||||
serverVersion: serversProvider.serverStatus.data!.serverVersion,
|
||||
)
|
||||
));
|
||||
if (width > 900 || !(Platform.isAndroid | Platform.isIOS)) {
|
||||
showDialog(
|
||||
barrierDismissible: false,
|
||||
context: context,
|
||||
builder: (BuildContext context) => ClientScreen(
|
||||
onConfirm: confirmEditClient,
|
||||
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) {
|
||||
|
|
|
@ -66,6 +66,7 @@ class _ConnectState extends State<Connect> {
|
|||
controllers: expandableControllerList,
|
||||
onChange: expandOrContract,
|
||||
scrollController: scrollController,
|
||||
breakingWidth: 700,
|
||||
),
|
||||
AnimatedPositioned(
|
||||
duration: const Duration(milliseconds: 100),
|
||||
|
|
|
@ -6,12 +6,27 @@ class FabConnect extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
|
||||
void openAddServerModal() async {
|
||||
await Future.delayed(const Duration(seconds: 0), (() => {
|
||||
Navigator.push(context, MaterialPageRoute(
|
||||
fullscreenDialog: true,
|
||||
builder: (BuildContext context) => const AddServerModal()
|
||||
))
|
||||
if (width > 700) {
|
||||
showDialog(
|
||||
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
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.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/providers/servers_provider.dart';
|
||||
|
||||
class FiltersFab extends StatelessWidget {
|
||||
class AddFiltersButton extends StatelessWidget {
|
||||
final String type;
|
||||
final Widget Function(void Function()) widget;
|
||||
|
||||
const FiltersFab({
|
||||
const AddFiltersButton({
|
||||
Key? key,
|
||||
required this.type,
|
||||
required this.widget
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
@ -28,6 +32,8 @@ class FiltersFab extends StatelessWidget {
|
|||
final serversProvider = Provider.of<ServersProvider>(context);
|
||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
|
||||
void confirmAddRule(String rule) async {
|
||||
ProcessModal processModal = ProcessModal(context: context);
|
||||
processModal.open(AppLocalizations.of(context)!.addingRule);
|
||||
|
@ -64,14 +70,27 @@ class FiltersFab extends StatelessWidget {
|
|||
}
|
||||
|
||||
void openAddCustomRule() {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
fullscreenDialog: true,
|
||||
if (width > 700 || !(Platform.isAndroid || Platform.isIOS)) {
|
||||
showDialog(
|
||||
context: context,
|
||||
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 {
|
||||
|
@ -154,22 +173,34 @@ class FiltersFab extends StatelessWidget {
|
|||
}
|
||||
|
||||
void openAddWhitelistBlacklist() {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (ctx) => AddListModal(
|
||||
type: type,
|
||||
onConfirm: confirmAddList,
|
||||
),
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent
|
||||
);
|
||||
if (width > 700 || !(Platform.isAndroid || Platform.isIOS)) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => AddListModal(
|
||||
type: type,
|
||||
onConfirm: confirmAddList,
|
||||
dialog: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
else {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (ctx) => AddListModal(
|
||||
type: type,
|
||||
onConfirm: confirmAddList,
|
||||
dialog: false,
|
||||
),
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return FloatingActionButton(
|
||||
onPressed: type == 'blacklist' || type == 'whitelist'
|
||||
return widget(
|
||||
type == 'blacklist' || type == 'whitelist'
|
||||
? () => openAddWhitelistBlacklist()
|
||||
: () => openAddCustomRule(),
|
||||
child: const Icon(Icons.add),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,17 +1,17 @@
|
|||
import 'package:flutter/material.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/providers/app_config_provider.dart';
|
||||
|
||||
class AddCustomRule extends StatefulWidget {
|
||||
final void Function(String) onConfirm;
|
||||
final bool dialog;
|
||||
|
||||
const AddCustomRule({
|
||||
Key? key,
|
||||
required this.onConfirm
|
||||
required this.onConfirm,
|
||||
required this.dialog
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
@ -73,292 +73,337 @@ class _AddCustomRuleState extends State<AddCustomRule> {
|
|||
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
|
||||
Widget build(BuildContext context) {
|
||||
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: [
|
||||
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
|
||||
),
|
||||
|
||||
List<Widget> content() {
|
||||
return [
|
||||
const SizedBox(height: 24),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
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
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(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)
|
||||
)
|
||||
child: Text(
|
||||
buildRule(),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
fontWeight: FontWeight.w500
|
||||
),
|
||||
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(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: SegmentedButton(
|
||||
segments: [
|
||||
ButtonSegment(
|
||||
value: BlockingPresets.block,
|
||||
label: Text(AppLocalizations.of(context)!.block)
|
||||
),
|
||||
ButtonSegment(
|
||||
value: BlockingPresets.unblock,
|
||||
label: Text(AppLocalizations.of(context)!.unblock)
|
||||
),
|
||||
ButtonSegment(
|
||||
value: BlockingPresets.custom,
|
||||
label: Text(AppLocalizations.of(context)!.custom)
|
||||
),
|
||||
],
|
||||
selected: <BlockingPresets>{preset},
|
||||
onSelectionChanged: (value) => setState(() => preset = value.first),
|
||||
),
|
||||
),
|
||||
Container(height: 30),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: SegmentedButton(
|
||||
segments: [
|
||||
ButtonSegment(
|
||||
value: BlockingPresets.block,
|
||||
label: Text(AppLocalizations.of(context)!.block)
|
||||
),
|
||||
ButtonSegment(
|
||||
value: BlockingPresets.unblock,
|
||||
label: Text(AppLocalizations.of(context)!.unblock)
|
||||
),
|
||||
ButtonSegment(
|
||||
value: BlockingPresets.custom,
|
||||
label: Text(AppLocalizations.of(context)!.custom)
|
||||
),
|
||||
],
|
||||
selected: <BlockingPresets>{preset},
|
||||
onSelectionChanged: (value) => setState(() => preset = value.first),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () => setState(() => addImportant = !addImportant),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 28),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.addImportant,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
),
|
||||
Container(height: 20),
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () => setState(() => addImportant = !addImportant),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 28),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.addImportant,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
),
|
||||
Switch(
|
||||
value: addImportant,
|
||||
onChanged: (value) => setState(() => addImportant = value),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Switch(
|
||||
value: addImportant,
|
||||
onChanged: (value) => setState(() => addImportant = value),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
),
|
||||
Container(height: 20),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Card(
|
||||
child: Padding(
|
||||
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: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.info,
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
IconButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
icon: const Icon(Icons.clear_rounded),
|
||||
tooltip: AppLocalizations.of(context)!.close,
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.examples,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
AppLocalizations.of(context)!.addCustomRule,
|
||||
style: const TextStyle(
|
||||
fontSize: 22
|
||||
),
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
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
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
IconButton(
|
||||
onPressed: checkValidValues() == true
|
||||
? () {
|
||||
Navigator.pop(context);
|
||||
widget.onConfirm(buildRule());
|
||||
}
|
||||
: null,
|
||||
icon: const Icon(Icons.check)
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
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
|
||||
),
|
||||
)
|
||||
],
|
||||
Flexible(
|
||||
child: SingleChildScrollView(
|
||||
child: Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
children: content(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
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 void Function({required String name, required String url, required String type})? onConfirm;
|
||||
final void Function({required Filter list, required String type})? onEdit;
|
||||
final bool dialog;
|
||||
|
||||
const AddListModal({
|
||||
Key? key,
|
||||
|
@ -17,6 +18,7 @@ class AddListModal extends StatefulWidget {
|
|||
this.list,
|
||||
this.onConfirm,
|
||||
this.onEdit,
|
||||
required this.dialog
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
@ -68,51 +70,49 @@ class _AddListModalState extends State<AddListModal> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: MediaQuery.of(context).viewInsets,
|
||||
child: Container(
|
||||
height: Platform.isIOS ? 386 : 370,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(28),
|
||||
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,
|
||||
Widget content() {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(
|
||||
child: SingleChildScrollView(
|
||||
child: Wrap(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 24),
|
||||
child: Icon(
|
||||
widget.type == 'whitelist'
|
||||
? Icons.verified_user_rounded
|
||||
: Icons.gpp_bad_rounded,
|
||||
size: 24,
|
||||
color: Theme.of(context).listTileTheme.iconColor
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 24),
|
||||
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: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: TextFormField(
|
||||
|
@ -129,7 +129,7 @@ class _AddListModalState extends State<AddListModal> {
|
|||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
Container(height: 30),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: TextFormField(
|
||||
|
@ -151,54 +151,80 @@ class _AddListModalState extends State<AddListModal> {
|
|||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(AppLocalizations.of(context)!.cancel)
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
if (widget.list != null) {
|
||||
final Filter newList = Filter(
|
||||
url: urlController.text,
|
||||
name: nameController.text,
|
||||
lastUpdated: widget.list!.lastUpdated,
|
||||
id: widget.list!.id,
|
||||
rulesCount: widget.list!.rulesCount,
|
||||
enabled: widget.list!.enabled
|
||||
);
|
||||
widget.onEdit!(
|
||||
list: newList,
|
||||
type: widget.type
|
||||
);
|
||||
}
|
||||
else {
|
||||
widget.onConfirm!(
|
||||
name: nameController.text,
|
||||
url: urlController.text,
|
||||
type: widget.type
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Text(
|
||||
widget.list != null
|
||||
? AppLocalizations.of(context)!.save
|
||||
: AppLocalizations.of(context)!.confirm
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(AppLocalizations.of(context)!.cancel)
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
if (widget.list != null) {
|
||||
final Filter newList = Filter(
|
||||
url: urlController.text,
|
||||
name: nameController.text,
|
||||
lastUpdated: widget.list!.lastUpdated,
|
||||
id: widget.list!.id,
|
||||
rulesCount: widget.list!.rulesCount,
|
||||
enabled: widget.list!.enabled
|
||||
);
|
||||
widget.onEdit!(
|
||||
list: newList,
|
||||
type: widget.type
|
||||
);
|
||||
}
|
||||
else {
|
||||
widget.onConfirm!(
|
||||
name: nameController.text,
|
||||
url: urlController.text,
|
||||
type: widget.type
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Text(
|
||||
widget.list != null
|
||||
? 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';
|
||||
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -21,7 +26,8 @@ class BlockedServicesScreen extends StatelessWidget {
|
|||
|
||||
return BlockedServicesScreenWidget(
|
||||
serversProvider: serversProvider,
|
||||
appConfigProvider: appConfigProvider
|
||||
appConfigProvider: appConfigProvider,
|
||||
dialog: dialog,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -29,11 +35,13 @@ class BlockedServicesScreen extends StatelessWidget {
|
|||
class BlockedServicesScreenWidget extends StatefulWidget {
|
||||
final ServersProvider serversProvider;
|
||||
final AppConfigProvider appConfigProvider;
|
||||
final bool dialog;
|
||||
|
||||
const BlockedServicesScreenWidget({
|
||||
Key? key,
|
||||
required this.serversProvider,
|
||||
required this.appConfigProvider,
|
||||
required this.dialog
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
@ -209,24 +217,74 @@ class _BlockedServicesScreenStateWidget extends State<BlockedServicesScreenWidge
|
|||
}
|
||||
}
|
||||
|
||||
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,
|
||||
if (widget.dialog == true) {
|
||||
return Dialog(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 400
|
||||
),
|
||||
const SizedBox(width: 10)
|
||||
],
|
||||
),
|
||||
body: RefreshIndicator(
|
||||
onRefresh: loadBlockedServices,
|
||||
child: body()
|
||||
),
|
||||
);
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
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';
|
||||
|
||||
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
|
||||
State<CheckHostModal> createState() => _CheckHostModalState();
|
||||
|
@ -117,126 +122,141 @@ class _CheckHostModalState extends State<CheckHostModal> {
|
|||
}
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: MediaQuery.of(context).viewInsets,
|
||||
child: Container(
|
||||
height: 330,
|
||||
width: double.maxFinite,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(28),
|
||||
topRight: Radius.circular(28),
|
||||
),
|
||||
color: Theme.of(context).dialogBackgroundColor
|
||||
),
|
||||
child: Center(
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ListView(
|
||||
physics: 350 < MediaQuery.of(context).size.height
|
||||
? const NeverScrollableScrollPhysics()
|
||||
: 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)
|
||||
)
|
||||
Widget content() {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(
|
||||
child: SingleChildScrollView(
|
||||
child: Wrap(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 24),
|
||||
child: Icon(
|
||||
Icons.shield_rounded,
|
||||
size: 24,
|
||||
color: Theme.of(context).listTileTheme.iconColor
|
||||
),
|
||||
),
|
||||
errorText: domainError,
|
||||
labelText: AppLocalizations.of(context)!.domain,
|
||||
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,
|
||||
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
|
||||
),
|
||||
child: resultWidget,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
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),
|
||||
),
|
||||
if (resultWidget == null) Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 20,
|
||||
left: 20,
|
||||
right: 20
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.insertDomain,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
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
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
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/rendering.dart';
|
||||
import 'package:provider/provider.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/remove_custom_rule_modal.dart';
|
||||
import 'package:adguard_home_manager/screens/filters/add_button.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/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 {
|
||||
final LoadStatus loadStatus;
|
||||
final ScrollController scrollController;
|
||||
final List<String> data;
|
||||
final Future<void> Function() fetchData;
|
||||
final void Function(String) onRemoveCustomRule;
|
||||
|
||||
const CustomRulesList({
|
||||
Key? key,
|
||||
required this.loadStatus,
|
||||
required this.scrollController,
|
||||
required this.data,
|
||||
required this.fetchData
|
||||
required this.fetchData,
|
||||
required this.onRemoveCustomRule
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
@ -61,52 +55,6 @@ class _CustomRulesListState extends State<CustomRulesList> {
|
|||
|
||||
@override
|
||||
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) {
|
||||
final regex = RegExp(r'^(!|#).*$');
|
||||
if (regex.hasMatch(value)) {
|
||||
|
@ -184,7 +132,7 @@ class _CustomRulesListState extends State<CustomRulesList> {
|
|||
),
|
||||
subtitle: generateSubtitle(widget.data[index]),
|
||||
trailing: IconButton(
|
||||
onPressed: () => openRemoveCustomRuleModal(widget.data[index]),
|
||||
onPressed: () => widget.onRemoveCustomRule(widget.data[index]),
|
||||
icon: const Icon(Icons.delete)
|
||||
),
|
||||
),
|
||||
|
@ -239,8 +187,12 @@ class _CustomRulesListState extends State<CustomRulesList> {
|
|||
),
|
||||
loadStatus: widget.loadStatus,
|
||||
onRefresh: widget.fetchData,
|
||||
fab: const FiltersFab(
|
||||
fab: AddFiltersButton(
|
||||
type: 'custom_rule',
|
||||
widget: (fn) => FloatingActionButton(
|
||||
onPressed: fn,
|
||||
child: const Icon(Icons.add),
|
||||
),
|
||||
),
|
||||
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
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/screens/filters/filters_list.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/update_interval_lists_modal.dart';
|
||||
|
||||
import 'package:adguard_home_manager/functions/snackbar.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/models/filtering.dart';
|
||||
import 'package:adguard_home_manager/constants/enums.dart';
|
||||
import 'package:adguard_home_manager/services/http_requests.dart';
|
||||
import 'package:adguard_home_manager/models/clients.dart';
|
||||
|
@ -47,10 +52,7 @@ class FiltersWidget extends StatefulWidget {
|
|||
State<FiltersWidget> createState() => _FiltersWidgetState();
|
||||
}
|
||||
|
||||
class _FiltersWidgetState extends State<FiltersWidget> with TickerProviderStateMixin {
|
||||
late TabController tabController;
|
||||
final ScrollController scrollController = ScrollController();
|
||||
|
||||
class _FiltersWidgetState extends State<FiltersWidget> {
|
||||
Future fetchFilters() async {
|
||||
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
|
||||
void initState() {
|
||||
fetchFilters();
|
||||
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
|
||||
|
@ -89,6 +85,8 @@ class _FiltersWidgetState extends State<FiltersWidget> with TickerProviderStateM
|
|||
final serversProvider = Provider.of<ServersProvider>(context);
|
||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
|
||||
void fetchUpdateLists() async {
|
||||
ProcessModal processModal = ProcessModal(context: context);
|
||||
processModal.open(AppLocalizations.of(context)!.updatingLists);
|
||||
|
@ -139,12 +137,24 @@ class _FiltersWidgetState extends State<FiltersWidget> with TickerProviderStateM
|
|||
|
||||
void showCheckHostModal() {
|
||||
Future.delayed(const Duration(seconds: 0), () {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) => const CheckHostModal(),
|
||||
backgroundColor: Colors.transparent,
|
||||
isScrollControlled: true,
|
||||
);
|
||||
if (width > 700 || !(Platform.isAndroid || Platform.isIOS)) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => const CheckHostModal(
|
||||
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() {
|
||||
Future.delayed(const Duration(seconds: 0), () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const BlockedServicesScreen(),
|
||||
)
|
||||
);
|
||||
if (width > 700 || !(Platform.isAndroid || Platform.isIOS)) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => const BlockedServicesScreen(
|
||||
dialog: true,
|
||||
),
|
||||
barrierDismissible: false
|
||||
);
|
||||
}
|
||||
else {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const BlockedServicesScreen(
|
||||
dialog: false,
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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: serversProvider.filtering.loadStatus == LoadStatus.loaded ? [
|
||||
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
|
||||
),
|
||||
child: Icon(
|
||||
serversProvider.filtering.data!.enabled == true
|
||||
? Icons.check_circle_rounded
|
||||
: Icons.cancel,
|
||||
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
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) => UpdateIntervalListsModal(
|
||||
interval: serversProvider.filtering.data!.interval,
|
||||
onChange: setUpdateFrequency
|
||||
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),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void openListDetails(Filter filter, String type) {
|
||||
if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => ListDetailsScreen(
|
||||
list: filter,
|
||||
type: type,
|
||||
dialog: true,
|
||||
),
|
||||
barrierDismissible: false
|
||||
);
|
||||
}
|
||||
else {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => ListDetailsScreen(
|
||||
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,
|
||||
isScrollControlled: true
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.update_rounded)
|
||||
child: Icon(
|
||||
serversProvider.filtering.data!.enabled == true
|
||||
? Icons.check_circle_rounded
|
||||
: Icons.cancel,
|
||||
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(
|
||||
children: [
|
||||
const Icon(Icons.sync_rounded),
|
||||
const SizedBox(width: 10),
|
||||
Text(AppLocalizations.of(context)!.updateLists)
|
||||
],
|
||||
)
|
||||
),
|
||||
PopupMenuItem(
|
||||
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)
|
||||
],
|
||||
)
|
||||
),
|
||||
]
|
||||
)
|
||||
],
|
||||
)
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => UpdateIntervalListsModal(
|
||||
interval: serversProvider.filtering.data!.interval,
|
||||
onChange: setUpdateFrequency,
|
||||
dialog: true,
|
||||
),
|
||||
const SizedBox(width: 5),
|
||||
] : [],
|
||||
bottom: TabBar(
|
||||
controller: tabController,
|
||||
isScrollable: false,
|
||||
unselectedLabelColor: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
tabs: [
|
||||
Tab(
|
||||
icon: const Icon(Icons.verified_user_rounded),
|
||||
text: AppLocalizations.of(context)!.whitelists,
|
||||
),
|
||||
Tab(
|
||||
icon: const Icon(Icons.gpp_bad_rounded),
|
||||
text: AppLocalizations.of(context)!.blacklist,
|
||||
),
|
||||
Tab(
|
||||
icon: const Icon(Icons.shield_rounded),
|
||||
text: AppLocalizations.of(context)!.customRules,
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
else {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) => UpdateIntervalListsModal(
|
||||
interval: serversProvider.filtering.data!.interval,
|
||||
onChange: setUpdateFrequency,
|
||||
dialog: false,
|
||||
),
|
||||
backgroundColor: Colors.transparent,
|
||||
isScrollControlled: true
|
||||
);
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.update_rounded),
|
||||
tooltip: AppLocalizations.of(context)!.updateFrequency,
|
||||
),
|
||||
PopupMenuButton(
|
||||
itemBuilder: (context) => [
|
||||
PopupMenuItem(
|
||||
onTap: fetchUpdateLists,
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.sync_rounded),
|
||||
const SizedBox(width: 10),
|
||||
Text(AppLocalizations.of(context)!.updateLists)
|
||||
],
|
||||
)
|
||||
),
|
||||
)
|
||||
];
|
||||
}),
|
||||
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: fetchFilters,
|
||||
),
|
||||
FiltersList(
|
||||
loadStatus: serversProvider.filtering.loadStatus,
|
||||
scrollController: scrollController,
|
||||
type: 'blacklist',
|
||||
data: serversProvider.filtering.loadStatus == LoadStatus.loaded
|
||||
? serversProvider.filtering.data!.filters : [],
|
||||
fetchData: fetchFilters,
|
||||
),
|
||||
CustomRulesList(
|
||||
loadStatus: serversProvider.filtering.loadStatus,
|
||||
scrollController: scrollController,
|
||||
data: serversProvider.filtering.loadStatus == LoadStatus.loaded
|
||||
? serversProvider.filtering.data!.userRules : [],
|
||||
fetchData: fetchFilters,
|
||||
),
|
||||
]
|
||||
)
|
||||
)
|
||||
);
|
||||
PopupMenuItem(
|
||||
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),
|
||||
];
|
||||
}
|
||||
else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
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_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/screens/filters/fab.dart';
|
||||
import 'package:adguard_home_manager/screens/filters/list_details_screen.dart';
|
||||
import 'package:adguard_home_manager/screens/filters/add_button.dart';
|
||||
import 'package:adguard_home_manager/widgets/custom_list_tile.dart';
|
||||
import 'package:adguard_home_manager/widgets/tab_content_list.dart';
|
||||
|
||||
|
@ -23,6 +22,7 @@ class FiltersList extends StatefulWidget {
|
|||
final List<Filter> data;
|
||||
final Future<void> Function() fetchData;
|
||||
final String type;
|
||||
final void Function(Filter, String) onOpenDetailsScreen;
|
||||
|
||||
const FiltersList({
|
||||
Key? key,
|
||||
|
@ -31,6 +31,7 @@ class FiltersList extends StatefulWidget {
|
|||
required this.data,
|
||||
required this.fetchData,
|
||||
required this.type,
|
||||
required this.onOpenDetailsScreen
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
@ -65,17 +66,6 @@ class _FiltersListState extends State<FiltersList> {
|
|||
Widget build(BuildContext 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(
|
||||
loadingGenerator: () => SizedBox(
|
||||
width: double.maxFinite,
|
||||
|
@ -112,7 +102,7 @@ class _FiltersListState extends State<FiltersList> {
|
|||
? Colors.grey
|
||||
: Colors.red
|
||||
),
|
||||
onTap: () => openDetailsModal(widget.data[index]),
|
||||
onTap: () => widget.onOpenDetailsScreen(widget.data[index], widget.type),
|
||||
),
|
||||
noData: Container(
|
||||
width: double.maxFinite,
|
||||
|
@ -166,8 +156,12 @@ class _FiltersListState extends State<FiltersList> {
|
|||
),
|
||||
loadStatus: widget.loadStatus,
|
||||
onRefresh: widget.fetchData,
|
||||
fab: FiltersFab(
|
||||
fab: AddFiltersButton(
|
||||
type: widget.type,
|
||||
widget: (fn) => FloatingActionButton(
|
||||
onPressed: fn,
|
||||
child: const Icon(Icons.add),
|
||||
),
|
||||
),
|
||||
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: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/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/providers/app_config_provider.dart';
|
||||
|
@ -23,11 +23,13 @@ import 'package:adguard_home_manager/models/filtering.dart';
|
|||
class ListDetailsScreen extends StatefulWidget {
|
||||
final Filter list;
|
||||
final String type;
|
||||
final bool dialog;
|
||||
|
||||
const ListDetailsScreen({
|
||||
Key? key,
|
||||
required this.list,
|
||||
required this.type,
|
||||
required this.dialog
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
@ -68,6 +70,8 @@ class _ListDetailsScreenState extends State<ListDetailsScreen> {
|
|||
final serversProvider = Provider.of<ServersProvider>(context);
|
||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
|
||||
void enableDisableList(Filter list, bool newStatus) async {
|
||||
ProcessModal processModal = ProcessModal(context: context);
|
||||
processModal.open(
|
||||
|
@ -216,108 +220,234 @@ class _ListDetailsScreenState extends State<ListDetailsScreen> {
|
|||
}
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(AppLocalizations.of(context)!.listDetails),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () => {
|
||||
List<Widget> content() {
|
||||
return [
|
||||
CustomListTile(
|
||||
icon: Icons.shield_rounded,
|
||||
title: AppLocalizations.of(context)!.currentStatus,
|
||||
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(
|
||||
context: context,
|
||||
builder: (ctx) => AddListModal(
|
||||
list: widget.list,
|
||||
type: widget.type,
|
||||
onEdit: confirmEditList
|
||||
onEdit: confirmEditList,
|
||||
dialog: false,
|
||||
),
|
||||
isScrollControlled: true,
|
||||
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(
|
||||
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),
|
||||
],
|
||||
),
|
||||
body: Stack(
|
||||
children: [
|
||||
ListView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
FilterListTile(
|
||||
icon: Icons.shield_rounded,
|
||||
title: AppLocalizations.of(context)!.currentStatus,
|
||||
subtitle: enabled == true
|
||||
? AppLocalizations.of(context)!.enabled
|
||||
: AppLocalizations.of(context)!.disabled,
|
||||
color: enabled == true
|
||||
? appConfigProvider.useThemeColorForStatus == true
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Colors.green
|
||||
: appConfigProvider.useThemeColorForStatus == true
|
||||
? Colors.grey
|
||||
: Colors.red,
|
||||
bold: true,
|
||||
),
|
||||
FilterListTile(
|
||||
icon: Icons.badge_rounded,
|
||||
title: AppLocalizations.of(context)!.name,
|
||||
subtitle: name
|
||||
),
|
||||
FilterListTile(
|
||||
icon: Icons.link_rounded,
|
||||
title: "URL",
|
||||
subtitle: widget.list.url
|
||||
),
|
||||
FilterListTile(
|
||||
icon: Icons.list_rounded,
|
||||
title: AppLocalizations.of(context)!.rules,
|
||||
subtitle: widget.list.rulesCount.toString()
|
||||
),
|
||||
FilterListTile(
|
||||
icon: Icons.shield_rounded,
|
||||
title: AppLocalizations.of(context)!.listType,
|
||||
subtitle: widget.type == 'whitelist'
|
||||
? AppLocalizations.of(context)!.whitelist
|
||||
: 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'),
|
||||
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),
|
||||
tooltip: AppLocalizations.of(context)!.close,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.listDetails,
|
||||
style: const TextStyle(
|
||||
fontSize: 22
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () => enableDisableList(widget.list, !enabled),
|
||||
icon: Icon(
|
||||
enabled == true
|
||||
? Icons.gpp_bad_rounded
|
||||
: Icons.verified_user_rounded,
|
||||
),
|
||||
tooltip: enabled == true
|
||||
? AppLocalizations.of(context)!.disableList
|
||||
: AppLocalizations.of(context)!.enableList,
|
||||
),
|
||||
...actions()
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: SingleChildScrollView(
|
||||
child: Wrap(
|
||||
children: content(),
|
||||
),
|
||||
)
|
||||
)
|
||||
],
|
||||
),
|
||||
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,
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
else {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(AppLocalizations.of(context)!.listDetails),
|
||||
actions: actions(),
|
||||
),
|
||||
body: Stack(
|
||||
children: [
|
||||
ListView(
|
||||
children: content(),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
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 {
|
||||
final int interval;
|
||||
final void Function(int) onChange;
|
||||
final bool dialog;
|
||||
|
||||
const UpdateIntervalListsModal({
|
||||
Key? key,
|
||||
required this.interval,
|
||||
required this.onChange,
|
||||
required this.dialog
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
@ -39,270 +41,264 @@ class _UpdateIntervalListsModalState extends State<UpdateIntervalListsModal> {
|
|||
Widget build(BuildContext context) {
|
||||
final MediaQueryData mediaQueryData = MediaQuery.of(context);
|
||||
|
||||
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: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Expanded(
|
||||
child: ListView(
|
||||
physics: (Platform.isIOS ? 426 : 410) < MediaQuery.of(context).size.height
|
||||
? const NeverScrollableScrollPhysics()
|
||||
: null,
|
||||
Widget content() {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(
|
||||
child: SingleChildScrollView(
|
||||
child: Wrap(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 24),
|
||||
child: Icon(
|
||||
Icons.update_rounded,
|
||||
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(
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
Container(
|
||||
width: (mediaQueryData.size.width-70)/2,
|
||||
margin: const EdgeInsets.only(
|
||||
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),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 24),
|
||||
child: Icon(
|
||||
Icons.update_rounded,
|
||||
size: 24,
|
||||
color: Theme.of(context).listTileTheme.iconColor
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
Container(
|
||||
width: (mediaQueryData.size.width-70)/2,
|
||||
margin: const EdgeInsets.only(
|
||||
top: 5,
|
||||
right: 5,
|
||||
bottom: 5
|
||||
),
|
||||
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),
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 24,
|
||||
vertical: 16
|
||||
),
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.updateFrequency,
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
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),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(AppLocalizations.of(context)!.cancel),
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
TextButton(
|
||||
onPressed: selectedOption != null
|
||||
? () {
|
||||
Navigator.pop(context);
|
||||
widget.onChange(selectedOption!);
|
||||
}
|
||||
: null,
|
||||
style: ButtonStyle(
|
||||
overlayColor: MaterialStateProperty.all(
|
||||
Theme.of(context).colorScheme.primary.withOpacity(0.1)
|
||||
),
|
||||
foregroundColor: MaterialStateProperty.all(
|
||||
selectedOption != null
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Colors.grey,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(AppLocalizations.of(context)!.cancel),
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
TextButton(
|
||||
onPressed: selectedOption != null
|
||||
? () {
|
||||
Navigator.pop(context);
|
||||
widget.onChange(selectedOption!);
|
||||
}
|
||||
: null,
|
||||
style: ButtonStyle(
|
||||
overlayColor: MaterialStateProperty.all(
|
||||
Theme.of(context).colorScheme.primary.withOpacity(0.1)
|
||||
),
|
||||
foregroundColor: MaterialStateProperty.all(
|
||||
selectedOption != null
|
||||
? 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,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
Flexible(
|
||||
child: Text(
|
||||
label,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
),
|
||||
),
|
||||
!isEmpty
|
||||
|
|
|
@ -12,13 +12,27 @@ class HomeFab extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
final serversProvider = Provider.of<ServersProvider>(context);
|
||||
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
|
||||
void openManagementBottomSheet() {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
builder: (context) => const ManagementModal(),
|
||||
backgroundColor: Colors.transparent,
|
||||
);
|
||||
if (width > 700) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => const ManagementModal(
|
||||
dialog: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
else {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
builder: (context) => const ManagementModal(
|
||||
dialog: false,
|
||||
),
|
||||
backgroundColor: Colors.transparent,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return serversProvider.serverStatus.loadStatus == 1
|
||||
|
|
|
@ -55,6 +55,8 @@ class _HomeState extends State<Home> {
|
|||
final serversProvider = Provider.of<ServersProvider>(context);
|
||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
|
||||
Widget status() {
|
||||
switch (serversProvider.serverStatus.loadStatus) {
|
||||
case 0:
|
||||
|
@ -92,72 +94,138 @@ class _HomeState extends State<Home> {
|
|||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
HomeChart(
|
||||
data: serversProvider.serverStatus.data!.stats.dnsQueries,
|
||||
label: AppLocalizations.of(context)!.dnsQueries,
|
||||
primaryValue: intFormat(serversProvider.serverStatus.data!.stats.numDnsQueries, Platform.localeName),
|
||||
secondaryValue: "${doubleFormat(serversProvider.serverStatus.data!.stats.avgProcessingTime*1000, Platform.localeName)} ms",
|
||||
color: Colors.blue,
|
||||
Wrap(
|
||||
children: [
|
||||
FractionallySizedBox(
|
||||
widthFactor: width > 700 ? 0.5 : 1,
|
||||
child: HomeChart(
|
||||
data: serversProvider.serverStatus.data!.stats.dnsQueries,
|
||||
label: AppLocalizations.of(context)!.dnsQueries,
|
||||
primaryValue: intFormat(serversProvider.serverStatus.data!.stats.numDnsQueries, Platform.localeName),
|
||||
secondaryValue: "${doubleFormat(serversProvider.serverStatus.data!.stats.avgProcessingTime*1000, Platform.localeName)} ms",
|
||||
color: Colors.blue,
|
||||
),
|
||||
),
|
||||
FractionallySizedBox(
|
||||
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(
|
||||
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,
|
||||
),
|
||||
|
||||
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,
|
||||
),
|
||||
|
||||
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),
|
||||
if (width <= 700) ...[
|
||||
TopItems(
|
||||
label: AppLocalizations.of(context)!.topQueriedDomains,
|
||||
data: serversProvider.serverStatus.data!.stats.topQueriedDomains,
|
||||
type: 'topQueriedDomains',
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
TopItems(
|
||||
label: AppLocalizations.of(context)!.topBlockedDomains,
|
||||
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),
|
||||
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,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
TopItems(
|
||||
label: AppLocalizations.of(context)!.topBlockedDomains,
|
||||
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';
|
||||
|
||||
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
|
||||
State<ManagementModal> createState() => _ManagementModalState();
|
||||
|
@ -364,8 +369,112 @@ class _ManagementModalState extends State<ManagementModal> with SingleTickerProv
|
|||
);
|
||||
}
|
||||
|
||||
return SafeArea(
|
||||
child: Container(
|
||||
Widget header() {
|
||||
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(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: const BorderRadius.only(
|
||||
|
@ -373,66 +482,18 @@ class _ManagementModalState extends State<ManagementModal> with SingleTickerProv
|
|||
topRight: Radius.circular(28)
|
||||
)
|
||||
),
|
||||
child: Wrap(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
Flexible(
|
||||
child: SingleChildScrollView(
|
||||
child: Wrap(
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
header(),
|
||||
...toggles()
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
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: const EdgeInsets.all(24),
|
||||
|
@ -449,7 +510,7 @@ class _ManagementModalState extends State<ManagementModal> with SingleTickerProv
|
|||
if (Platform.isIOS) const SizedBox(height: 16)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,8 +15,12 @@ class ServerStatus extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
|
||||
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(
|
||||
children: [
|
||||
Text(
|
||||
|
@ -29,11 +33,11 @@ class ServerStatus extends StatelessWidget {
|
|||
),
|
||||
const SizedBox(height: 20),
|
||||
SizedBox(
|
||||
height: 140,
|
||||
height: width > 700 ? 70 : 140,
|
||||
child: GridView(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 2,
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: width > 700 ? 4 : 2,
|
||||
crossAxisSpacing: 10,
|
||||
mainAxisSpacing: 10,
|
||||
mainAxisExtent: 65
|
||||
|
|
|
@ -44,13 +44,15 @@ class StatusBox extends StatelessWidget {
|
|||
: Colors.grey.computeLuminance() > 0.5 ? Colors.black : Colors.white,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
color: appConfigProvider.useThemeColorForStatus == true
|
||||
? Theme.of(context).colorScheme.primary.computeLuminance() > 0.5 ? Colors.black : Colors.white
|
||||
: Colors.grey.computeLuminance() > 0.5 ? Colors.black : Colors.white,
|
||||
fontWeight: FontWeight.w500
|
||||
Flexible(
|
||||
child: Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
color: appConfigProvider.useThemeColorForStatus == true
|
||||
? Theme.of(context).colorScheme.primary.computeLuminance() > 0.5 ? Colors.black : Colors.white
|
||||
: Colors.grey.computeLuminance() > 0.5 ? Colors.black : Colors.white,
|
||||
fontWeight: FontWeight.w500
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
// 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/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
@ -35,6 +39,8 @@ class TopItems extends StatelessWidget {
|
|||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||
final logsProvider = Provider.of<LogsProvider>(context);
|
||||
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
|
||||
bool? getIsBlocked() {
|
||||
if (type == 'topBlockedDomains') {
|
||||
return true;
|
||||
|
@ -266,16 +272,32 @@ class TopItems extends StatelessWidget {
|
|||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => TopItemsScreen(
|
||||
type: type,
|
||||
title: label,
|
||||
isClient: clients,
|
||||
data: generateData(),
|
||||
onPressed: () => {
|
||||
if (width > 700 || !(Platform.isAndroid || Platform.isIOS)) {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (context) => TopItemsModal(
|
||||
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(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
|
|
|
@ -8,10 +8,12 @@ import 'package:adguard_home_manager/providers/logs_provider.dart';
|
|||
|
||||
class ClientsModal extends StatefulWidget {
|
||||
final List<String>? value;
|
||||
final bool dialog;
|
||||
|
||||
const ClientsModal({
|
||||
Key? key,
|
||||
required this.value
|
||||
required this.value,
|
||||
required this.dialog
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
@ -94,44 +96,36 @@ class _ClientsModalState extends State<ClientsModal> {
|
|||
});
|
||||
}
|
||||
|
||||
return Container(
|
||||
height: height >= (logsProvider.clients!.length*64) == true
|
||||
? logsProvider.clients!.length*64
|
||||
: height-50,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(28),
|
||||
topRight: Radius.circular(28)
|
||||
),
|
||||
color: Theme.of(context).dialogBackgroundColor
|
||||
),
|
||||
child: Column(
|
||||
Widget content() {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 24,
|
||||
bottom: 16,
|
||||
),
|
||||
child: Icon(
|
||||
Icons.smartphone_rounded,
|
||||
size: 24,
|
||||
color: Theme.of(context).listTileTheme.iconColor
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 24,
|
||||
bottom: 16,
|
||||
),
|
||||
child: Icon(
|
||||
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(
|
||||
AppLocalizations.of(context)!.clients,
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Expanded(
|
||||
Flexible(
|
||||
child: ListView.builder(
|
||||
physics: height >= (logsProvider.clients!.length*64) == true
|
||||
? const NeverScrollableScrollPhysics()
|
||||
: null,
|
||||
itemCount: logsProvider.clients!.length,
|
||||
itemBuilder: (context, index) => listItem(
|
||||
label: logsProvider.clients![index].ip,
|
||||
|
@ -150,7 +144,7 @@ class _ClientsModalState extends State<ClientsModal> {
|
|||
}
|
||||
}
|
||||
)
|
||||
),
|
||||
)
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
|
@ -176,7 +170,35 @@ class _ClientsModalState extends State<ClientsModal> {
|
|||
),
|
||||
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 {
|
||||
final String value;
|
||||
final bool dialog;
|
||||
|
||||
const FilterStatusModal({
|
||||
Key? key,
|
||||
required this.value
|
||||
required this.value,
|
||||
required this.dialog
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
@ -31,8 +33,6 @@ class _FilterStatusModalState extends State<FilterStatusModal> {
|
|||
Widget build(BuildContext context) {
|
||||
final logsProvider = Provider.of<LogsProvider>(context);
|
||||
|
||||
final height = MediaQuery.of(context).size.height;
|
||||
|
||||
void apply() async {
|
||||
logsProvider.setSelectedResultStatus(selectedResultStatus);
|
||||
|
||||
|
@ -83,95 +83,94 @@ class _FilterStatusModalState extends State<FilterStatusModal> {
|
|||
);
|
||||
}
|
||||
|
||||
return Container(
|
||||
height: height >= (Platform.isIOS ? 736 : 720) == true
|
||||
? (Platform.isIOS ? 736 : 720)
|
||||
: height-25,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(28),
|
||||
topRight: Radius.circular(28)
|
||||
),
|
||||
color: Theme.of(context).dialogBackgroundColor
|
||||
),
|
||||
child: Column(
|
||||
Widget content() {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 24,
|
||||
bottom: 16,
|
||||
),
|
||||
child: Icon(
|
||||
Icons.shield_rounded,
|
||||
size: 24,
|
||||
color: Theme.of(context).listTileTheme.iconColor
|
||||
),
|
||||
),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.responseStatus,
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Expanded(
|
||||
child: ListView(
|
||||
physics: height >= 720 == true
|
||||
? const NeverScrollableScrollPhysics()
|
||||
: null,
|
||||
children: [
|
||||
filterStatusListItem(
|
||||
id: "all",
|
||||
icon: Icons.shield_rounded,
|
||||
label: AppLocalizations.of(context)!.all,
|
||||
onChanged: (value) => setState(() => selectedResultStatus = value!)
|
||||
),
|
||||
filterStatusListItem(
|
||||
id: "filtered",
|
||||
icon: Icons.shield_rounded,
|
||||
label: AppLocalizations.of(context)!.filtered,
|
||||
onChanged: (value) => setState(() => selectedResultStatus = value!)
|
||||
),
|
||||
filterStatusListItem(
|
||||
id: "processed",
|
||||
icon: Icons.verified_user_rounded,
|
||||
label: AppLocalizations.of(context)!.processedRow,
|
||||
onChanged: (value) => setState(() => selectedResultStatus = value!)
|
||||
),
|
||||
filterStatusListItem(
|
||||
id: "whitelisted",
|
||||
icon: Icons.verified_user_rounded,
|
||||
label: AppLocalizations.of(context)!.processedWhitelistRow,
|
||||
onChanged: (value) => setState(() => selectedResultStatus = value!)
|
||||
),
|
||||
filterStatusListItem(
|
||||
id: "blocked",
|
||||
icon: Icons.gpp_bad_rounded,
|
||||
label: AppLocalizations.of(context)!.blocked,
|
||||
onChanged: (value) => setState(() => selectedResultStatus = value!)
|
||||
),
|
||||
filterStatusListItem(
|
||||
id: "blocked_safebrowsing",
|
||||
icon: Icons.gpp_bad_rounded,
|
||||
label: AppLocalizations.of(context)!.blockedSafeBrowsingRow,
|
||||
onChanged: (value) => setState(() => selectedResultStatus = value!)
|
||||
),
|
||||
filterStatusListItem(
|
||||
id: "blocked_parental",
|
||||
icon: Icons.gpp_bad_rounded,
|
||||
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!)
|
||||
),
|
||||
Flexible(
|
||||
child: SingleChildScrollView(
|
||||
child: Wrap(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 24,
|
||||
bottom: 16,
|
||||
),
|
||||
child: Icon(
|
||||
Icons.shield_rounded,
|
||||
size: 24,
|
||||
color: Theme.of(context).listTileTheme.iconColor
|
||||
),
|
||||
),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.responseStatus,
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
Container(height: 16),
|
||||
filterStatusListItem(
|
||||
id: "all",
|
||||
icon: Icons.shield_rounded,
|
||||
label: AppLocalizations.of(context)!.all,
|
||||
onChanged: (value) => setState(() => selectedResultStatus = value!)
|
||||
),
|
||||
filterStatusListItem(
|
||||
id: "filtered",
|
||||
icon: Icons.shield_rounded,
|
||||
label: AppLocalizations.of(context)!.filtered,
|
||||
onChanged: (value) => setState(() => selectedResultStatus = value!)
|
||||
),
|
||||
filterStatusListItem(
|
||||
id: "processed",
|
||||
icon: Icons.verified_user_rounded,
|
||||
label: AppLocalizations.of(context)!.processedRow,
|
||||
onChanged: (value) => setState(() => selectedResultStatus = value!)
|
||||
),
|
||||
filterStatusListItem(
|
||||
id: "whitelisted",
|
||||
icon: Icons.verified_user_rounded,
|
||||
label: AppLocalizations.of(context)!.processedWhitelistRow,
|
||||
onChanged: (value) => setState(() => selectedResultStatus = value!)
|
||||
),
|
||||
filterStatusListItem(
|
||||
id: "blocked",
|
||||
icon: Icons.gpp_bad_rounded,
|
||||
label: AppLocalizations.of(context)!.blocked,
|
||||
onChanged: (value) => setState(() => selectedResultStatus = value!)
|
||||
),
|
||||
filterStatusListItem(
|
||||
id: "blocked_safebrowsing",
|
||||
icon: Icons.gpp_bad_rounded,
|
||||
label: AppLocalizations.of(context)!.blockedSafeBrowsingRow,
|
||||
onChanged: (value) => setState(() => selectedResultStatus = value!)
|
||||
),
|
||||
filterStatusListItem(
|
||||
id: "blocked_parental",
|
||||
icon: Icons.gpp_bad_rounded,
|
||||
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(
|
||||
|
@ -188,7 +187,30 @@ class _FilterStatusModalState extends State<FilterStatusModal> {
|
|||
),
|
||||
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 {
|
||||
final Log log;
|
||||
final bool dialog;
|
||||
|
||||
const LogDetailsScreen({
|
||||
Key? key,
|
||||
required this.log
|
||||
required this.log,
|
||||
required this.dialog
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
@ -105,25 +107,8 @@ class LogDetailsScreen extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
|
||||
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: ListView(
|
||||
Widget content() {
|
||||
return ListView(
|
||||
children: [
|
||||
SectionLabel(label: AppLocalizations.of(context)!.status),
|
||||
LogListTile(
|
||||
|
@ -247,7 +232,87 @@ class LogDetailsScreen extends StatelessWidget {
|
|||
)).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
|
||||
|
||||
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/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/block_unblock_domain.dart';
|
||||
|
@ -18,12 +18,18 @@ class LogTile extends StatelessWidget {
|
|||
final Log log;
|
||||
final int length;
|
||||
final int index;
|
||||
final bool? isLogSelected;
|
||||
final void Function(Log) onLogTap;
|
||||
final bool? useAlwaysNormalTile;
|
||||
|
||||
const LogTile({
|
||||
Key? key,
|
||||
required this.log,
|
||||
required this.length,
|
||||
required this.index
|
||||
required this.index,
|
||||
this.isLogSelected,
|
||||
required this.onLogTap,
|
||||
this.useAlwaysNormalTile
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
@ -38,7 +44,7 @@ class LogTile extends StatelessWidget {
|
|||
required String text
|
||||
}) {
|
||||
return SizedBox(
|
||||
width: 70,
|
||||
width: 80,
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(
|
||||
|
@ -96,132 +102,249 @@ class LogTile extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () => Navigator.push(context, MaterialPageRoute(
|
||||
builder: (context) => LogDetailsScreen(log: log)
|
||||
)),
|
||||
onLongPress: () => openOptionsModal(log),
|
||||
child: Container(
|
||||
width: double.maxFinite,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: width-130,
|
||||
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(
|
||||
if (width > 1100 && !(useAlwaysNormalTile == 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: () => onLogTap(log),
|
||||
child: Container(
|
||||
width: double.maxFinite,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
color: isLogSelected == true
|
||||
? Theme.of(context).colorScheme.primaryContainer
|
||||
: null
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
...[
|
||||
Icon(
|
||||
Icons.smartphone_rounded,
|
||||
size: 16,
|
||||
color: Theme.of(context).listTileTheme.textColor,
|
||||
),
|
||||
const SizedBox(width: 5),
|
||||
Flexible(
|
||||
child: Text(
|
||||
log.client,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).listTileTheme.textColor,
|
||||
fontSize: 14,
|
||||
height: 1.4,
|
||||
fontWeight: FontWeight.w400,
|
||||
Flexible(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
log.question.name,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
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
|
||||
const SizedBox(height: 5),
|
||||
if (log.client.length <= 15 && appConfigProvider.showNameTimeLogs == false) Row(
|
||||
children: [
|
||||
...[
|
||||
Icon(
|
||||
Icons.smartphone_rounded,
|
||||
size: 16,
|
||||
color: Theme.of(context).listTileTheme.textColor,
|
||||
),
|
||||
const SizedBox(width: 5),
|
||||
Flexible(
|
||||
child: Text(
|
||||
log.client,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).listTileTheme.textColor,
|
||||
fontSize: 14,
|
||||
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: [
|
||||
Row(
|
||||
children: [
|
||||
),
|
||||
generateLogStatus()
|
||||
],
|
||||
)
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
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(
|
||||
Icons.smartphone_rounded,
|
||||
size: 16,
|
||||
color: Theme.of(context).listTileTheme.textColor,
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
const SizedBox(width: 5),
|
||||
Flexible(
|
||||
child: Text(
|
||||
log.client,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).listTileTheme.textColor,
|
||||
fontSize: 13
|
||||
fontSize: 14,
|
||||
height: 1.4,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
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: [
|
||||
const SizedBox(width: 15),
|
||||
...[
|
||||
Icon(
|
||||
Icons.schedule_rounded,
|
||||
size: 16,
|
||||
color: Theme.of(context).listTileTheme.textColor,
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
SizedBox(
|
||||
const SizedBox(width: 5),
|
||||
Flexible(
|
||||
child: Text(
|
||||
convertTimestampLocalTimezone(log.time, 'HH:mm:ss'),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
|
@ -230,22 +353,23 @@ class LogTile extends StatelessWidget {
|
|||
fontSize: 13
|
||||
),
|
||||
),
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
if (appConfigProvider.showNameTimeLogs == true && log.elapsedMs != '') ...[
|
||||
const SizedBox(height: 10),
|
||||
],
|
||||
),
|
||||
if (log.client.length > 15 || appConfigProvider.showNameTimeLogs == true) Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.timer,
|
||||
Icons.smartphone_rounded,
|
||||
size: 16,
|
||||
color: Theme.of(context).listTileTheme.textColor,
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
SizedBox(
|
||||
Flexible(
|
||||
child: Text(
|
||||
"${double.parse(log.elapsedMs).toStringAsFixed(2)} ms",
|
||||
log.client,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
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),
|
||||
generateLogStatus()
|
||||
],
|
||||
const SizedBox(width: 10),
|
||||
generateLogStatus()
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
// ignore_for_file: use_build_context_synchronously
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
@ -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_config_modal.dart';
|
||||
import 'package:adguard_home_manager/screens/logs/log_tile.dart';
|
||||
import 'package:adguard_home_manager/screens/logs/log_details_screen.dart';
|
||||
|
||||
import 'package:adguard_home_manager/functions/snackbar.dart';
|
||||
import 'package:adguard_home_manager/classes/process_modal.dart';
|
||||
|
@ -64,6 +67,8 @@ class _LogsWidgetState extends State<LogsWidget> {
|
|||
|
||||
bool showDivider = true;
|
||||
|
||||
Log? selectedLog;
|
||||
|
||||
Future fetchLogs({
|
||||
int? inOffset,
|
||||
bool? loadingMore,
|
||||
|
@ -188,6 +193,8 @@ class _LogsWidgetState extends State<LogsWidget> {
|
|||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||
final logsProvider = Provider.of<LogsProvider>(context);
|
||||
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
|
||||
void updateConfig(Map<String, dynamic> data) async {
|
||||
ProcessModal processModal = ProcessModal(context: context);
|
||||
processModal.open(AppLocalizations.of(context)!.updatingSettings);
|
||||
|
@ -252,12 +259,25 @@ class _LogsWidgetState extends State<LogsWidget> {
|
|||
|
||||
|
||||
void openFilersModal() {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) => const LogsFiltersModal(),
|
||||
backgroundColor: Colors.transparent,
|
||||
isScrollControlled: true
|
||||
);
|
||||
if (width > 700 || !(Platform.isAndroid || Platform.isIOS)) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => const LogsFiltersModal(
|
||||
dialog: true,
|
||||
),
|
||||
barrierDismissible: false
|
||||
);
|
||||
}
|
||||
else {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) => const LogsFiltersModal(
|
||||
dialog: false,
|
||||
),
|
||||
backgroundColor: Colors.transparent,
|
||||
isScrollControlled: true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final Map<String, String> translatedString = {
|
||||
|
@ -319,6 +339,18 @@ class _LogsWidgetState extends State<LogsWidget> {
|
|||
log: logsProvider.logsData!.data[index],
|
||||
index: index,
|
||||
length: logsProvider.logsData!.data.length,
|
||||
isLogSelected: selectedLog != null && selectedLog == logsProvider.logsData!.data[index],
|
||||
onLogTap: (log) {
|
||||
if (width <= 1100) {
|
||||
Navigator.push(context, MaterialPageRoute(
|
||||
builder: (context) => LogDetailsScreen(
|
||||
log: log,
|
||||
dialog: false,
|
||||
)
|
||||
));
|
||||
}
|
||||
setState(() => selectedLog = log);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -383,161 +415,210 @@ class _LogsWidgetState extends State<LogsWidget> {
|
|||
}
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(AppLocalizations.of(context)!.logs),
|
||||
centerTitle: false,
|
||||
actions: [
|
||||
logsProvider.loadStatus == 1
|
||||
? IconButton(
|
||||
onPressed: openFilersModal,
|
||||
icon: const Icon(Icons.filter_list_rounded)
|
||||
)
|
||||
: const SizedBox(),
|
||||
IconButton(
|
||||
onPressed: () => {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) => LogsConfigModal(
|
||||
onConfirm: updateConfig,
|
||||
onClear: clearQueries,
|
||||
),
|
||||
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,
|
||||
)
|
||||
Widget logsScreen() {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(AppLocalizations.of(context)!.logs),
|
||||
centerTitle: false,
|
||||
actions: [
|
||||
if (!(Platform.isAndroid || Platform.isIOS)) IconButton(
|
||||
onPressed: () => fetchLogs(inOffset: 0),
|
||||
icon: const Icon(Icons.refresh_rounded),
|
||||
tooltip: AppLocalizations.of(context)!.refresh,
|
||||
),
|
||||
logsProvider.loadStatus == 1
|
||||
? IconButton(
|
||||
onPressed: openFilersModal,
|
||||
icon: const Icon(Icons.filter_list_rounded),
|
||||
tooltip: AppLocalizations.of(context)!.filters,
|
||||
)
|
||||
: const SizedBox(),
|
||||
IconButton(
|
||||
tooltip: AppLocalizations.of(context)!.settings,
|
||||
onPressed: () => {
|
||||
if (width > 700 || !(Platform.isAndroid || Platform.isIOS)) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => LogsConfigModal(
|
||||
onConfirm: updateConfig,
|
||||
onClear: clearQueries,
|
||||
dialog: true,
|
||||
),
|
||||
barrierDismissible: false
|
||||
)
|
||||
),
|
||||
child: ListView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
children: [
|
||||
if (logsProvider.appliedFilters.searchText != null) ...[
|
||||
}
|
||||
else {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
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),
|
||||
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 {
|
||||
final void Function(Map<String, dynamic>) onConfirm;
|
||||
final void Function() onClear;
|
||||
final bool dialog;
|
||||
|
||||
const LogsConfigModal({
|
||||
Key? key,
|
||||
required this.onConfirm,
|
||||
required this.onClear,
|
||||
required this.dialog
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
@ -30,6 +32,7 @@ class LogsConfigModal extends StatelessWidget {
|
|||
context: context,
|
||||
onConfirm: onConfirm,
|
||||
onClear: onClear,
|
||||
dialog: dialog,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -40,6 +43,7 @@ class LogsConfigModalWidget extends StatefulWidget {
|
|||
final BuildContext context;
|
||||
final void Function(Map<String, dynamic>) onConfirm;
|
||||
final void Function() onClear;
|
||||
final bool dialog;
|
||||
|
||||
const LogsConfigModalWidget({
|
||||
Key? key,
|
||||
|
@ -48,6 +52,7 @@ class LogsConfigModalWidget extends StatefulWidget {
|
|||
required this.context,
|
||||
required this.onConfirm,
|
||||
required this.onClear,
|
||||
required this.dialog
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
@ -146,125 +151,154 @@ class _LogsConfigModalWidgetState extends State<LogsConfigModalWidget> {
|
|||
Widget generateBody() {
|
||||
switch (loadStatus) {
|
||||
case 0:
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
return Padding(
|
||||
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:
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Expanded(
|
||||
child: ListView(
|
||||
physics: (Platform.isIOS ? 436 : 420) < MediaQuery.of(context).size.height
|
||||
? const NeverScrollableScrollPhysics()
|
||||
: null,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 24),
|
||||
child: Icon(
|
||||
Icons.settings,
|
||||
size: 24,
|
||||
color: Theme.of(context).listTileTheme.iconColor
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
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,
|
||||
),
|
||||
Flexible(
|
||||
child: SingleChildScrollView(
|
||||
child: Wrap(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 24),
|
||||
child: Icon(
|
||||
Icons.settings,
|
||||
size: 24,
|
||||
color: Theme.of(context).listTileTheme.iconColor
|
||||
),
|
||||
Switch(
|
||||
value: generalSwitch,
|
||||
onChanged: (value) => setState(() => generalSwitch = value),
|
||||
)
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
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),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14),
|
||||
child: Column(
|
||||
children: [
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () => setState(() => anonymizeClientIp = !anonymizeClientIp),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 30),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context)!.anonymizeClientIp,
|
||||
style: const TextStyle(
|
||||
fontSize: 16
|
||||
Container(height: 16),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14),
|
||||
child: Column(
|
||||
children: [
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () => setState(() => anonymizeClientIp = !anonymizeClientIp),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 30),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context)!.anonymizeClientIp,
|
||||
style: const TextStyle(
|
||||
fontSize: 16
|
||||
),
|
||||
),
|
||||
),
|
||||
Switch(
|
||||
value: anonymizeClientIp,
|
||||
onChanged: (value) => setState(() => anonymizeClientIp = value),
|
||||
Switch(
|
||||
value: anonymizeClientIp,
|
||||
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(
|
||||
|
@ -316,31 +350,29 @@ class _LogsConfigModalWidgetState extends State<LogsConfigModalWidget> {
|
|||
);
|
||||
|
||||
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),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.logSettingsNotLoaded,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontSize: 22,
|
||||
color: Colors.grey,
|
||||
),
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.error,
|
||||
color: Colors.red,
|
||||
size: 50,
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.logSettingsNotLoaded,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
|
||||
default:
|
||||
|
@ -348,16 +380,28 @@ class _LogsConfigModalWidgetState extends State<LogsConfigModalWidget> {
|
|||
}
|
||||
}
|
||||
|
||||
return Container(
|
||||
height: Platform.isIOS ? 436 : 420,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(28),
|
||||
topRight: Radius.circular(28)
|
||||
if (widget.dialog == true) {
|
||||
return Dialog(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 500
|
||||
),
|
||||
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';
|
||||
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
final logsProvider = Provider.of<LogsProvider>(context);
|
||||
|
||||
return LogsFiltersModalWidget(
|
||||
logsProvider: logsProvider
|
||||
logsProvider: logsProvider,
|
||||
dialog: dialog,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LogsFiltersModalWidget extends StatefulWidget {
|
||||
final LogsProvider logsProvider;
|
||||
final bool dialog;
|
||||
|
||||
const LogsFiltersModalWidget({
|
||||
Key? key,
|
||||
required this.logsProvider
|
||||
required this.logsProvider,
|
||||
required this.dialog
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
@ -57,6 +65,8 @@ class _LogsFiltersModalWidgetState extends State<LogsFiltersModalWidget> {
|
|||
final serversProvider = Provider.of<ServersProvider>(context);
|
||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
|
||||
final Map<String, String> translatedString = {
|
||||
"all": AppLocalizations.of(context)!.all,
|
||||
"filtered": AppLocalizations.of(context)!.filtered,
|
||||
|
@ -101,25 +111,51 @@ class _LogsFiltersModalWidgetState extends State<LogsFiltersModalWidget> {
|
|||
}
|
||||
|
||||
void openSelectFilterStatus() {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) => FilterStatusModal(
|
||||
value: logsProvider.selectedResultStatus,
|
||||
),
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent
|
||||
);
|
||||
if (width > 700 || !(Platform.isAndroid || Platform.isIOS)) {
|
||||
showDialog(
|
||||
barrierDismissible: false,
|
||||
context: context,
|
||||
builder: (context) => FilterStatusModal(
|
||||
value: logsProvider.selectedResultStatus,
|
||||
dialog: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
else {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) => FilterStatusModal(
|
||||
value: logsProvider.selectedResultStatus,
|
||||
dialog: false,
|
||||
),
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void openSelectClients() {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) => ClientsModal(
|
||||
value: logsProvider.selectedClients,
|
||||
),
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent
|
||||
);
|
||||
if (width > 700 || !(Platform.isAndroid || Platform.isIOS)) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => ClientsModal(
|
||||
value: logsProvider.selectedClients,
|
||||
dialog: true,
|
||||
),
|
||||
barrierDismissible: false
|
||||
);
|
||||
}
|
||||
else {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) => ClientsModal(
|
||||
value: logsProvider.selectedClients,
|
||||
dialog: false,
|
||||
),
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void filterLogs() async {
|
||||
|
@ -161,47 +197,45 @@ class _LogsFiltersModalWidgetState extends State<LogsFiltersModalWidget> {
|
|||
}
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: MediaQuery.of(context).viewInsets,
|
||||
child: Container(
|
||||
height: Platform.isIOS ? 446 : 430,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).dialogBackgroundColor,
|
||||
borderRadius: const BorderRadius.only(
|
||||
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,
|
||||
Widget content() {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(
|
||||
child: SingleChildScrollView(
|
||||
child: Wrap(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 24,
|
||||
bottom: 16,
|
||||
),
|
||||
child: Icon(
|
||||
Icons.filter_list_rounded,
|
||||
size: 24,
|
||||
color: Theme.of(context).listTileTheme.iconColor
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 24,
|
||||
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: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Row(
|
||||
|
@ -233,7 +267,7 @@ class _LogsFiltersModalWidgetState extends State<LogsFiltersModalWidget> {
|
|||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Container(height: 16),
|
||||
CustomListTile(
|
||||
title: AppLocalizations.of(context)!.client,
|
||||
subtitle: logsProvider.selectedClients != null
|
||||
|
@ -270,26 +304,55 @@ class _LogsFiltersModalWidgetState extends State<LogsFiltersModalWidget> {
|
|||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: resetFilters,
|
||||
child: Text(AppLocalizations.of(context)!.resetFilters)
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => filterLogs(),
|
||||
child: Text(AppLocalizations.of(context)!.apply)
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: resetFilters,
|
||||
child: Text(AppLocalizations.of(context)!.resetFilters)
|
||||
),
|
||||
TextButton(
|
||||
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';
|
||||
|
||||
class Servers extends StatefulWidget {
|
||||
const Servers({Key? key}) : super(key: key);
|
||||
final double? breakingWidth;
|
||||
|
||||
const Servers({
|
||||
Key? key,
|
||||
this.breakingWidth
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<Servers> createState() => _ServersState();
|
||||
|
@ -55,16 +60,31 @@ class _ServersState extends State<Servers> {
|
|||
final serversProvider = Provider.of<ServersProvider>(context);
|
||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
|
||||
for (var i = 0; i < serversProvider.serversList.length; i++) {
|
||||
expandableControllerList.add(ExpandableController());
|
||||
}
|
||||
|
||||
void openAddServerModal() async {
|
||||
await Future.delayed(const Duration(seconds: 0), (() => {
|
||||
Navigator.push(context, MaterialPageRoute(
|
||||
fullscreenDialog: true,
|
||||
builder: (BuildContext context) => const AddServerModal()
|
||||
))
|
||||
if (width > 700) {
|
||||
showDialog(
|
||||
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,
|
||||
controllers: expandableControllerList,
|
||||
onChange: expandOrContract,
|
||||
scrollController: scrollController
|
||||
scrollController: scrollController,
|
||||
breakingWidth: widget.breakingWidth ?? 700,
|
||||
),
|
||||
AnimatedPositioned(
|
||||
duration: const Duration(milliseconds: 100),
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
@ -72,78 +74,114 @@ class _AccessSettingsWidgetState extends State<AccessSettingsWidget> with Ticker
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final serversProvider = Provider.of<ServersProvider>(context);
|
||||
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(
|
||||
controller: tabController,
|
||||
isScrollable: true,
|
||||
unselectedLabelColor: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
tabs: [
|
||||
Tab(
|
||||
icon: const Icon(Icons.check),
|
||||
text: AppLocalizations.of(context)!.allowedClients,
|
||||
),
|
||||
Tab(
|
||||
icon: const Icon(Icons.block),
|
||||
text: AppLocalizations.of(context)!.disallowedClients,
|
||||
),
|
||||
Tab(
|
||||
icon: const Icon(Icons.link_rounded),
|
||||
text: AppLocalizations.of(context)!.disallowedDomains,
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
Widget body() {
|
||||
return 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
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
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: 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
|
||||
),
|
||||
]
|
||||
)
|
||||
];
|
||||
}),
|
||||
body: body()
|
||||
)
|
||||
)
|
||||
),
|
||||
);
|
||||
),
|
||||
);
|
||||
}
|
||||
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 {
|
||||
final String type;
|
||||
final void Function(String, String) onConfirm;
|
||||
final bool dialog;
|
||||
|
||||
const AddClientModal({
|
||||
Key? key,
|
||||
required this.type,
|
||||
required this.onConfirm
|
||||
required this.onConfirm,
|
||||
required this.dialog,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
@ -65,59 +67,61 @@ class _AddClientModalState extends State<AddClientModal> {
|
|||
}
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: MediaQuery.of(context).viewInsets,
|
||||
child: Container(
|
||||
height: Platform.isIOS ? 321 : 305,
|
||||
Widget content() {
|
||||
return Padding(
|
||||
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(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Expanded(
|
||||
child: ListView(
|
||||
physics: (Platform.isIOS ? 338 : 322) < MediaQuery.of(context).size.height
|
||||
? const NeverScrollableScrollPhysics()
|
||||
: null,
|
||||
children: [
|
||||
Icon(
|
||||
icon(),
|
||||
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
|
||||
Flexible(
|
||||
child: SingleChildScrollView(
|
||||
child: Wrap(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
icon(),
|
||||
size: 24,
|
||||
color: Theme.of(context).listTileTheme.iconColor
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: fieldController,
|
||||
onChanged: (_) => checkValidValues(),
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.link_rounded),
|
||||
border: const OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(10)
|
||||
)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
title(),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
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(
|
||||
|
@ -129,7 +133,7 @@ class _AddClientModalState extends State<AddClientModal> {
|
|||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(AppLocalizations.of(context)!.cancel)
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
const SizedBox(width: 16),
|
||||
TextButton(
|
||||
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
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
@ -68,6 +70,8 @@ class _ClientsListState extends State<ClientsList> {
|
|||
final serversProvider = Provider.of<ServersProvider>(context);
|
||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
|
||||
void confirmRemoveItem(String client, String type) async {
|
||||
Map<String, List<String>> body = {
|
||||
"allowed_clients": serversProvider.clients.data!.clientsAllowedBlocked?.allowedClients ?? [],
|
||||
|
@ -209,6 +213,7 @@ class _ClientsListState extends State<ClientsList> {
|
|||
}
|
||||
|
||||
return CustomTabContentList(
|
||||
noSliver: !(Platform.isAndroid || Platform.isIOS) ? true : false,
|
||||
loadingGenerator: () => SizedBox(
|
||||
width: double.maxFinite,
|
||||
height: MediaQuery.of(context).size.height-171,
|
||||
|
@ -362,15 +367,28 @@ class _ClientsListState extends State<ClientsList> {
|
|||
refreshIndicatorOffset: 0,
|
||||
fab: FloatingActionButton(
|
||||
onPressed: () {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) => AddClientModal(
|
||||
type: widget.type,
|
||||
onConfirm: confirmAddItem
|
||||
),
|
||||
backgroundColor: Colors.transparent,
|
||||
isScrollControlled: true
|
||||
);
|
||||
if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AddClientModal(
|
||||
type: widget.type,
|
||||
onConfirm: confirmAddItem,
|
||||
dialog: 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),
|
||||
),
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
// ignore_for_file: use_build_context_synchronously
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_split_view/flutter_split_view.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
|
@ -16,6 +19,8 @@ class AdvancedSettings extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
|
||||
Future updateSslCheck(bool newStatus) async {
|
||||
final result = await appConfigProvider.setOverrideSslCheck(newStatus);
|
||||
if (result == true) {
|
||||
|
@ -64,11 +69,16 @@ class AdvancedSettings extends StatelessWidget {
|
|||
title: AppLocalizations.of(context)!.logs,
|
||||
subtitle: AppLocalizations.of(context)!.checkAppLogs,
|
||||
onTap: () => {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const AppLogs()
|
||||
if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) {
|
||||
SplitView.of(context).push(const AppLogs())
|
||||
}
|
||||
else {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const AppLogs()
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
padding: const EdgeInsets.only(
|
||||
top: 10,
|
||||
|
|
|
@ -5,10 +5,12 @@ import 'package:adguard_home_manager/models/dhcp.dart';
|
|||
|
||||
class AddStaticLeaseModal extends StatefulWidget {
|
||||
final void Function(Lease) onConfirm;
|
||||
final bool dialog;
|
||||
|
||||
const AddStaticLeaseModal({
|
||||
Key? key,
|
||||
required this.onConfirm,
|
||||
required this.dialog
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
@ -65,45 +67,47 @@ class _AddStaticLeaseModalState extends State<AddStaticLeaseModal> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: MediaQuery.of(context).viewInsets,
|
||||
child: Container(
|
||||
height: 510,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).dialogBackgroundColor,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(28),
|
||||
topRight: Radius.circular(28)
|
||||
)
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ListView(
|
||||
physics: 550 < MediaQuery.of(context).size.height
|
||||
? const NeverScrollableScrollPhysics()
|
||||
: null,
|
||||
Widget content() {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(
|
||||
child: SingleChildScrollView(
|
||||
child: Wrap(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 24),
|
||||
child: Icon(
|
||||
Icons.add,
|
||||
size: 24,
|
||||
color: Theme.of(context).listTileTheme.iconColor
|
||||
padding: const EdgeInsets.only(bottom: 16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
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: const EdgeInsets.symmetric(horizontal: 28),
|
||||
padding: const EdgeInsets.only(
|
||||
left: 24, right: 24, bottom: 12
|
||||
),
|
||||
child: TextFormField(
|
||||
controller: macController,
|
||||
onChanged: validateMac,
|
||||
|
@ -119,9 +123,8 @@ class _AddStaticLeaseModalState extends State<AddStaticLeaseModal> {
|
|||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 28),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
||||
child: TextFormField(
|
||||
controller: ipController,
|
||||
onChanged: validateIp,
|
||||
|
@ -137,9 +140,10 @@ class _AddStaticLeaseModalState extends State<AddStaticLeaseModal> {
|
|||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 28),
|
||||
padding: const EdgeInsets.only(
|
||||
left: 24, right: 24, top: 12
|
||||
),
|
||||
child: TextFormField(
|
||||
controller: hostNameController,
|
||||
onChanged: (value) {
|
||||
|
@ -166,44 +170,70 @@ class _AddStaticLeaseModalState extends State<AddStaticLeaseModal> {
|
|||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(AppLocalizations.of(context)!.cancel),
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
TextButton(
|
||||
onPressed: validData == true
|
||||
? () {
|
||||
Navigator.pop(context);
|
||||
widget.onConfirm(
|
||||
Lease(
|
||||
mac: macController.text,
|
||||
hostname: hostNameController.text,
|
||||
ip: ipController.text
|
||||
)
|
||||
);
|
||||
}
|
||||
: null,
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.confirm,
|
||||
style: TextStyle(
|
||||
color: validData == true
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Colors.grey
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(AppLocalizations.of(context)!.cancel),
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
TextButton(
|
||||
onPressed: validData == true
|
||||
? () {
|
||||
Navigator.pop(context);
|
||||
widget.onConfirm(
|
||||
Lease(
|
||||
mac: macController.text,
|
||||
hostname: hostNameController.text,
|
||||
ip: ipController.text
|
||||
)
|
||||
);
|
||||
}
|
||||
: null,
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.confirm,
|
||||
style: TextStyle(
|
||||
color: validData == true
|
||||
? 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
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_split_view/flutter_split_view.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/widgets/section_label.dart';
|
||||
|
@ -204,7 +206,7 @@ class _DhcpWidgetState extends State<DhcpWidget> {
|
|||
|
||||
@override
|
||||
void initState() {
|
||||
loadDhcpStatus();
|
||||
if (mounted) loadDhcpStatus();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
|
@ -213,6 +215,8 @@ class _DhcpWidgetState extends State<DhcpWidget> {
|
|||
final serversProvider = Provider.of<ServersProvider>(context);
|
||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
|
||||
void saveSettings() async {
|
||||
ProcessModal processModal = ProcessModal(context: context);
|
||||
processModal.open(AppLocalizations.of(context)!.savingSettings);
|
||||
|
@ -354,24 +358,33 @@ class _DhcpWidgetState extends State<DhcpWidget> {
|
|||
void selectInterface() {
|
||||
ScaffoldMessenger.of(context).clearSnackBars();
|
||||
Future.delayed(const Duration(seconds: 0), () {
|
||||
showFlexibleBottomSheet(
|
||||
minHeight: 0.6,
|
||||
initHeight: 0.6,
|
||||
maxHeight: 0.95,
|
||||
isCollapsible: true,
|
||||
duration: const Duration(milliseconds: 250),
|
||||
anchors: [0.95],
|
||||
context: context,
|
||||
builder: (ctx, controller, offset) => SelectInterfaceModal(
|
||||
interfaces: serversProvider.dhcp.data!.networkInterfaces,
|
||||
scrollController: controller,
|
||||
onSelect: (interface) => setState(() {
|
||||
clearAll();
|
||||
selectedInterface = interface;
|
||||
})
|
||||
),
|
||||
bottomSheetColor: Colors.transparent
|
||||
);
|
||||
if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => SelectInterfaceModal(
|
||||
interfaces: serversProvider.dhcp.data!.networkInterfaces,
|
||||
onSelect: (interface) => setState(() {
|
||||
clearAll();
|
||||
selectedInterface = interface;
|
||||
}),
|
||||
dialog: true,
|
||||
)
|
||||
);
|
||||
}
|
||||
else {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
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:
|
||||
if (selectedInterface != null) {
|
||||
return ListView(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 10,
|
||||
left: 16,
|
||||
right: 16
|
||||
),
|
||||
child: Material(
|
||||
color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
child: InkWell(
|
||||
onTap: selectedInterface != null
|
||||
? () => setState(() => enabled = !enabled)
|
||||
: null,
|
||||
return SingleChildScrollView(
|
||||
child: Wrap(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 10,
|
||||
left: 16,
|
||||
right: 16
|
||||
),
|
||||
child: Material(
|
||||
color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
vertical: 12
|
||||
child: InkWell(
|
||||
onTap: selectedInterface != null
|
||||
? () => setState(() => enabled = !enabled)
|
||||
: null,
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
vertical: 12
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context)!.enableDhcpServer,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
),
|
||||
if (selectedInterface != null) ...[
|
||||
Text(
|
||||
selectedInterface!.name,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context).listTileTheme.textColor
|
||||
),
|
||||
)
|
||||
]
|
||||
],
|
||||
),
|
||||
Switch(
|
||||
value: enabled,
|
||||
onChanged: selectedInterface != null
|
||||
? (value) => setState(() => enabled = value)
|
||||
: null,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (selectedInterface!.ipv4Addresses.isNotEmpty) ...[
|
||||
SectionLabel(
|
||||
label: AppLocalizations.of(context)!.ipv4settings,
|
||||
padding: const EdgeInsets.only(
|
||||
top: 24, left: 16, right: 16, bottom: 8
|
||||
)
|
||||
),
|
||||
FractionallySizedBox(
|
||||
widthFactor: width > 900 ? 0.5 : 1,
|
||||
child: Padding(
|
||||
padding: width > 900
|
||||
? const EdgeInsets.only(top: 12, bottom: 12, left: 16, right: 8)
|
||||
: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
child: TextFormField(
|
||||
controller: ipv4StartRangeController,
|
||||
onChanged: (value) => validateIpV4(value, 'ipv4StartRangeError', AppLocalizations.of(context)!.ipNotValid),
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.skip_previous_rounded),
|
||||
border: const OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(10)
|
||||
)
|
||||
),
|
||||
errorText: ipv4StartRangeError,
|
||||
labelText: AppLocalizations.of(context)!.startOfRange,
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
),
|
||||
),
|
||||
FractionallySizedBox(
|
||||
widthFactor: width > 900 ? 0.5 : 1,
|
||||
child: Padding(
|
||||
padding: width > 900
|
||||
? const EdgeInsets.only(top: 12, bottom: 12, left: 8, right: 16)
|
||||
: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
child: TextFormField(
|
||||
controller: ipv4EndRangeController,
|
||||
onChanged: (value) => validateIpV4(value, 'ipv4EndRangeError', AppLocalizations.of(context)!.ipNotValid),
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.skip_next_rounded),
|
||||
border: const OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(10)
|
||||
)
|
||||
),
|
||||
errorText: ipv4EndRangeError,
|
||||
labelText: AppLocalizations.of(context)!.endOfRange,
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
),
|
||||
),
|
||||
FractionallySizedBox(
|
||||
widthFactor: width > 900 ? 0.5 : 1,
|
||||
child: Padding(
|
||||
padding: width > 900
|
||||
? const EdgeInsets.only(top: 12, bottom: 12, left: 16, right: 8)
|
||||
: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
child: TextFormField(
|
||||
controller: ipv4SubnetMaskController,
|
||||
onChanged: (value) => validateIpV4(value, 'ipv4SubnetMaskError', AppLocalizations.of(context)!.subnetMaskNotValid),
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.hub_rounded),
|
||||
border: const OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(10)
|
||||
)
|
||||
),
|
||||
errorText: ipv4SubnetMaskError,
|
||||
labelText: AppLocalizations.of(context)!.subnetMask,
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
),
|
||||
),
|
||||
FractionallySizedBox(
|
||||
widthFactor: width > 900 ? 0.5 : 1,
|
||||
child: Padding(
|
||||
padding: width > 900
|
||||
? const EdgeInsets.only(top: 12, bottom: 12, left: 8, right: 16)
|
||||
: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
child: TextFormField(
|
||||
controller: ipv4GatewayController,
|
||||
onChanged: (value) => validateIpV4(value, 'ipv4GatewayError', AppLocalizations.of(context)!.gatewayNotValid),
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.router_rounded),
|
||||
border: const OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(10)
|
||||
)
|
||||
),
|
||||
errorText: ipv4GatewayError,
|
||||
labelText: AppLocalizations.of(context)!.gateway,
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
),
|
||||
),
|
||||
FractionallySizedBox(
|
||||
widthFactor: 1,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
child: TextFormField(
|
||||
controller: ipv4LeaseTimeController,
|
||||
onChanged: (value) {
|
||||
if (int.tryParse(value).runtimeType == int) {
|
||||
setState(() => ipv4LeaseTimeError = null);
|
||||
}
|
||||
else {
|
||||
setState(() => ipv4LeaseTimeError = AppLocalizations.of(context)!.leaseTimeNotValid);
|
||||
}
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.timer),
|
||||
border: const OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(10)
|
||||
)
|
||||
),
|
||||
errorText: ipv4LeaseTimeError,
|
||||
labelText: AppLocalizations.of(context)!.leaseTime,
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
if (selectedInterface!.ipv6Addresses.isNotEmpty) ...[
|
||||
SectionLabel(
|
||||
label: AppLocalizations.of(context)!.ipv6settings,
|
||||
padding: const EdgeInsets.all(16)
|
||||
),
|
||||
FractionallySizedBox(
|
||||
widthFactor: width > 900 ? 0.5 : 1,
|
||||
child: Padding(
|
||||
padding: width > 900
|
||||
? const EdgeInsets.only(top: 8, bottom: 12, left: 16, right: 8)
|
||||
: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
child: TextFormField(
|
||||
controller: ipv6StartRangeController,
|
||||
onChanged: (value) => validateIpV4(value, 'ipv6StartRangeError', AppLocalizations.of(context)!.ipNotValid),
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.skip_next_rounded),
|
||||
border: const OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(10)
|
||||
)
|
||||
),
|
||||
errorText: ipv6StartRangeError,
|
||||
labelText: AppLocalizations.of(context)!.startOfRange,
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
),
|
||||
),
|
||||
FractionallySizedBox(
|
||||
widthFactor: width > 900 ? 0.5 : 1,
|
||||
child: Padding(
|
||||
padding: width > 900
|
||||
? const EdgeInsets.only(top: 8, bottom: 12, left: 8, right: 16)
|
||||
: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
child: TextFormField(
|
||||
controller: ipv6EndRangeController,
|
||||
onChanged: (value) => validateIpV4(value, 'ipv6EndRangeError', AppLocalizations.of(context)!.ipNotValid),
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.skip_previous_rounded),
|
||||
border: const OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(10)
|
||||
)
|
||||
),
|
||||
errorText: ipv6EndRangeError,
|
||||
labelText: AppLocalizations.of(context)!.endOfRange,
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
),
|
||||
),
|
||||
FractionallySizedBox(
|
||||
widthFactor: 1,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
child: TextFormField(
|
||||
controller: ipv6LeaseTimeController,
|
||||
onChanged: (value) {
|
||||
if (int.tryParse(value).runtimeType == int) {
|
||||
setState(() => ipv6LeaseTimeError = null);
|
||||
}
|
||||
else {
|
||||
setState(() => ipv6LeaseTimeError = AppLocalizations.of(context)!.leaseTimeNotValid);
|
||||
}
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.timer),
|
||||
border: const OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(10)
|
||||
)
|
||||
),
|
||||
errorText: ipv6LeaseTimeError,
|
||||
labelText: AppLocalizations.of(context)!.leaseTime,
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 20),
|
||||
SectionLabel(
|
||||
label: AppLocalizations.of(context)!.dhcpLeases,
|
||||
padding: const EdgeInsets.all(16),
|
||||
),
|
||||
if (width <= 900) Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
Navigator.push(context, MaterialPageRoute(
|
||||
builder: (context) => DhcpLeases(
|
||||
items: serversProvider.dhcp.data!.dhcpStatus.leases,
|
||||
staticLeases: false,
|
||||
)
|
||||
));
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
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,
|
||||
Text(
|
||||
AppLocalizations.of(context)!.dhcpLeases,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
Icons.arrow_forward_rounded,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (selectedInterface!.ipv4Addresses.isNotEmpty) ...[
|
||||
SectionLabel(
|
||||
label: AppLocalizations.of(context)!.ipv4settings,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
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)
|
||||
if (width <= 900) Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
Navigator.push(context, MaterialPageRoute(
|
||||
builder: (context) => DhcpLeases(
|
||||
items: serversProvider.dhcp.data!.dhcpStatus.staticLeases,
|
||||
staticLeases: true,
|
||||
)
|
||||
),
|
||||
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(
|
||||
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(
|
||||
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,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context)!.dhcpStatic,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
Icons.arrow_forward_rounded,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
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,
|
||||
Icon(
|
||||
Icons.arrow_forward_rounded,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
Icons.arrow_forward_rounded,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10)
|
||||
],
|
||||
if (width > 900) Row(
|
||||
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 {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.neededSelectInterface,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.5)
|
||||
),
|
||||
Flexible(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.neededSelectInterface,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.5)
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
ElevatedButton(
|
||||
onPressed: selectInterface,
|
||||
child: Text(AppLocalizations.of(context)!.selectInterface)
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
ElevatedButton(
|
||||
onPressed: selectInterface,
|
||||
child: Text(AppLocalizations.of(context)!.selectInterface)
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
// ignore_for_file: use_build_context_synchronously
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:animations/animations.dart';
|
||||
|
@ -30,6 +32,8 @@ class DhcpLeases extends StatelessWidget {
|
|||
final serversProvider = Provider.of<ServersProvider>(context);
|
||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
|
||||
void deleteLease(Lease lease) async {
|
||||
ProcessModal processModal = ProcessModal(context: context);
|
||||
processModal.open(AppLocalizations.of(context)!.deleting);
|
||||
|
@ -119,14 +123,26 @@ class DhcpLeases extends StatelessWidget {
|
|||
}
|
||||
|
||||
void openAddStaticLease() {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) => AddStaticLeaseModal(
|
||||
onConfirm: createLease
|
||||
),
|
||||
backgroundColor: Colors.transparent,
|
||||
isScrollControlled: true
|
||||
);
|
||||
if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AddStaticLeaseModal(
|
||||
onConfirm: createLease,
|
||||
dialog: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
else {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) => AddStaticLeaseModal(
|
||||
onConfirm: createLease,
|
||||
dialog: false,
|
||||
),
|
||||
backgroundColor: Colors.transparent,
|
||||
isScrollControlled: true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
|
|
|
@ -7,173 +7,177 @@ import 'package:adguard_home_manager/models/dhcp.dart';
|
|||
|
||||
class SelectInterfaceModal extends StatelessWidget {
|
||||
final List<NetworkInterface> interfaces;
|
||||
final ScrollController scrollController;
|
||||
final void Function(NetworkInterface) onSelect;
|
||||
final bool dialog;
|
||||
|
||||
const SelectInterfaceModal({
|
||||
Key? key,
|
||||
required this.interfaces,
|
||||
required this.scrollController,
|
||||
required this.onSelect,
|
||||
required this.dialog
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).dialogBackgroundColor,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(28),
|
||||
topRight: Radius.circular(28)
|
||||
)
|
||||
),
|
||||
child: Column(
|
||||
Widget content() {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Expanded(
|
||||
child: ListView(
|
||||
controller: scrollController,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 24),
|
||||
child: Icon(
|
||||
Icons.settings_ethernet_rounded,
|
||||
size: 24,
|
||||
color: Theme.of(context).listTileTheme.iconColor
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.selectInterface,
|
||||
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
|
||||
),
|
||||
Flexible(
|
||||
child: SingleChildScrollView(
|
||||
child: Wrap(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 24),
|
||||
child: Icon(
|
||||
Icons.settings_ethernet_rounded,
|
||||
size: 24,
|
||||
color: Theme.of(context).listTileTheme.iconColor
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
"${AppLocalizations.of(context)!.hardwareAddress}: ",
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.selectInterface,
|
||||
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
|
||||
),
|
||||
Text(
|
||||
interfaces[index].hardwareAddress,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
"${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),
|
||||
],
|
||||
),
|
||||
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 != '') ...[
|
||||
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(', ')}",
|
||||
if (interfaces[index].gatewayIp != '') ...[
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
"${AppLocalizations.of(context)!.gatewayIp}: ",
|
||||
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(', ')}",
|
||||
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(
|
||||
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(
|
||||
|
@ -190,7 +194,30 @@ class SelectInterfaceModal extends StatelessWidget {
|
|||
),
|
||||
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(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: MediaQuery.of(context).size.width-74,
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
controller: c['controller'],
|
||||
onChanged: (value) => validateIp(c, value),
|
||||
|
@ -199,6 +198,7 @@ class _BootstrapDnsScreenState extends State<BootstrapDnsScreen> {
|
|||
)
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
setState(() => bootstrapControllers = bootstrapControllers.where((con) => con != c).toList());
|
||||
|
|
|
@ -257,7 +257,8 @@ class _CacheConfigDnsScreenState extends State<CacheConfigDnsScreen> {
|
|||
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 {
|
||||
final String? comment;
|
||||
final void Function(String) onConfirm;
|
||||
final bool dialog;
|
||||
|
||||
const CommentModal({
|
||||
Key? key,
|
||||
this.comment,
|
||||
required this.onConfirm
|
||||
required this.onConfirm,
|
||||
required this.dialog
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
@ -30,43 +32,41 @@ class _CommentModalState extends State<CommentModal> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: MediaQuery.of(context).viewInsets,
|
||||
child: Container(
|
||||
height: 310,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(28),
|
||||
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,
|
||||
Widget content() {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(
|
||||
child: SingleChildScrollView(
|
||||
child: Wrap(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 24),
|
||||
child: Icon(
|
||||
Icons.comment_rounded,
|
||||
size: 24,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
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: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: TextFormField(
|
||||
|
@ -95,38 +95,64 @@ class _CommentModalState extends State<CommentModal> {
|
|||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(AppLocalizations.of(context)!.cancel)
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
TextButton(
|
||||
onPressed: validData == true
|
||||
? () {
|
||||
Navigator.pop(context);
|
||||
widget.onConfirm("# ${commentController.text}");
|
||||
}
|
||||
: null,
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.confirm,
|
||||
style: TextStyle(
|
||||
color: validData == true
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Colors.grey
|
||||
),
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(AppLocalizations.of(context)!.cancel)
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
TextButton(
|
||||
onPressed: validData == true
|
||||
? () {
|
||||
Navigator.pop(context);
|
||||
widget.onConfirm("# ${commentController.text}");
|
||||
}
|
||||
: null,
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.confirm,
|
||||
style: TextStyle(
|
||||
color: validData == true
|
||||
? 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
|
||||
|
||||
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';
|
||||
|
||||
|
@ -76,6 +79,19 @@ class _DnsSettingsWidgetState extends State<DnsSettingsWidget> {
|
|||
final serversProvider = Provider.of<ServersProvider>(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() {
|
||||
switch (widget.serversProvider.dnsInfo.loadStatus) {
|
||||
case 0:
|
||||
|
@ -105,51 +121,51 @@ class _DnsSettingsWidgetState extends State<DnsSettingsWidget> {
|
|||
CustomListTile(
|
||||
title: AppLocalizations.of(context)!.upstreamDns,
|
||||
subtitle: AppLocalizations.of(context)!.upstreamDnsDescription,
|
||||
onTap: () => Navigator.push(context, MaterialPageRoute(
|
||||
builder: (context) => UpstreamDnsScreen(
|
||||
onTap: () => navigate(
|
||||
UpstreamDnsScreen(
|
||||
serversProvider: serversProvider
|
||||
)
|
||||
)),
|
||||
),
|
||||
icon: Icons.upload_rounded,
|
||||
),
|
||||
CustomListTile(
|
||||
title: AppLocalizations.of(context)!.bootstrapDns,
|
||||
subtitle: AppLocalizations.of(context)!.bootstrapDnsDescription,
|
||||
onTap: () => Navigator.push(context, MaterialPageRoute(
|
||||
builder: (context) => BootstrapDnsScreen(
|
||||
onTap: () => navigate(
|
||||
BootstrapDnsScreen(
|
||||
serversProvider: serversProvider
|
||||
)
|
||||
)),
|
||||
),
|
||||
icon: Icons.dns_rounded,
|
||||
),
|
||||
CustomListTile(
|
||||
title: AppLocalizations.of(context)!.privateReverseDnsServers,
|
||||
subtitle: AppLocalizations.of(context)!.privateReverseDnsDescription,
|
||||
onTap: () => Navigator.push(context, MaterialPageRoute(
|
||||
builder: (context) => PrivateReverseDnsServersScreen(
|
||||
onTap: () => navigate(
|
||||
PrivateReverseDnsServersScreen(
|
||||
serversProvider: serversProvider
|
||||
)
|
||||
)),
|
||||
),
|
||||
icon: Icons.person_rounded,
|
||||
),
|
||||
CustomListTile(
|
||||
title: AppLocalizations.of(context)!.dnsServerSettings,
|
||||
subtitle: AppLocalizations.of(context)!.dnsServerSettingsDescription,
|
||||
onTap: () => Navigator.push(context, MaterialPageRoute(
|
||||
builder: (context) => DnsServerSettingsScreen(
|
||||
onTap: () => navigate(
|
||||
DnsServerSettingsScreen(
|
||||
serversProvider: serversProvider
|
||||
)
|
||||
)),
|
||||
),
|
||||
icon: Icons.settings,
|
||||
),
|
||||
CustomListTile(
|
||||
title: AppLocalizations.of(context)!.dnsCacheConfig,
|
||||
subtitle: AppLocalizations.of(context)!.dnsCacheConfigDescription,
|
||||
onTap: () => Navigator.push(context, MaterialPageRoute(
|
||||
builder: (context) => CacheConfigDnsScreen(
|
||||
onTap: () => navigate(
|
||||
CacheConfigDnsScreen(
|
||||
serversProvider: serversProvider
|
||||
)
|
||||
)),
|
||||
),
|
||||
icon: Icons.storage_rounded,
|
||||
),
|
||||
],
|
||||
|
|
|
@ -229,8 +229,7 @@ class _PrivateReverseDnsServersScreenState extends State<PrivateReverseDnsServer
|
|||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: MediaQuery.of(context).size.width-74,
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
controller: c['controller'],
|
||||
onChanged: (value) => validateAddress(c, value),
|
||||
|
@ -246,6 +245,7 @@ class _PrivateReverseDnsServersScreenState extends State<PrivateReverseDnsServer
|
|||
)
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
setState(() => reverseResolversControllers = reverseResolversControllers.where((con) => con != c).toList());
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
// ignore_for_file: use_build_context_synchronously
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
@ -72,35 +74,72 @@ class _UpstreamDnsScreenState extends State<UpstreamDnsScreen> {
|
|||
final serversProvider = Provider.of<ServersProvider>(context);
|
||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
|
||||
void openAddCommentModal() {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) => CommentModal(
|
||||
onConfirm: (value) {
|
||||
dnsServers.add({
|
||||
'comment': value
|
||||
});
|
||||
},
|
||||
),
|
||||
backgroundColor: Colors.transparent,
|
||||
isScrollControlled: true,
|
||||
isDismissible: true
|
||||
);
|
||||
if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => CommentModal(
|
||||
onConfirm: (value) {
|
||||
setState(() {
|
||||
dnsServers.add({
|
||||
'comment': value
|
||||
});
|
||||
});
|
||||
},
|
||||
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) {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) => CommentModal(
|
||||
comment: item['comment'],
|
||||
onConfirm: (value) {
|
||||
setState(() => dnsServers[position] = { 'comment': value });
|
||||
},
|
||||
),
|
||||
backgroundColor: Colors.transparent,
|
||||
isScrollControlled: true,
|
||||
isDismissible: true
|
||||
);
|
||||
if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => CommentModal(
|
||||
comment: item['comment'],
|
||||
onConfirm: (value) {
|
||||
setState(() => dnsServers[position] = { 'comment': value });
|
||||
},
|
||||
dialog: 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 {
|
||||
|
@ -185,13 +224,12 @@ class _UpstreamDnsScreenState extends State<UpstreamDnsScreen> {
|
|||
),
|
||||
...dnsServers.map((item) => Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 16, right: 6, bottom: 20
|
||||
left: 16, right: 6, bottom: 24
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
if (item['controller'] != null) SizedBox(
|
||||
width: MediaQuery.of(context).size.width-74,
|
||||
if (item['controller'] != null) Expanded(
|
||||
child: TextFormField(
|
||||
controller: item['controller'],
|
||||
onChanged: (_) => checkValidValues(),
|
||||
|
@ -206,6 +244,7 @@ class _UpstreamDnsScreenState extends State<UpstreamDnsScreen> {
|
|||
)
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
if (item['comment'] != null) Expanded(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
|
@ -232,10 +271,12 @@ class _UpstreamDnsScreenState extends State<UpstreamDnsScreen> {
|
|||
},
|
||||
icon: const Icon(Icons.remove_circle_outline),
|
||||
tooltip: AppLocalizations.of(context)!.remove,
|
||||
)
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
],
|
||||
),
|
||||
)).toList(),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
|
|
|
@ -7,10 +7,12 @@ import 'package:adguard_home_manager/models/rewrite_rules.dart';
|
|||
|
||||
class AddDnsRewriteModal extends StatefulWidget {
|
||||
final void Function(RewriteRulesData) onConfirm;
|
||||
final bool dialog;
|
||||
|
||||
const AddDnsRewriteModal({
|
||||
Key? key,
|
||||
required this.onConfirm
|
||||
required this.onConfirm,
|
||||
required this.dialog
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
@ -50,45 +52,45 @@ class _AddDnsRewriteModalState extends State<AddDnsRewriteModal> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: MediaQuery.of(context).viewInsets,
|
||||
child: Container(
|
||||
height: Platform.isIOS ? 416 : 400,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(28),
|
||||
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,
|
||||
Widget content() {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(
|
||||
child: SingleChildScrollView(
|
||||
child: Wrap(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 24),
|
||||
child: Icon(
|
||||
Icons.add,
|
||||
size: 24,
|
||||
color: Theme.of(context).listTileTheme.iconColor
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
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)!.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: const EdgeInsets.symmetric(horizontal: 24),
|
||||
padding: const EdgeInsets.only(
|
||||
left: 24, right: 24, bottom: 12
|
||||
),
|
||||
child: TextFormField(
|
||||
controller: domainController,
|
||||
onChanged: validateDomain,
|
||||
|
@ -104,9 +106,10 @@ class _AddDnsRewriteModalState extends State<AddDnsRewriteModal> {
|
|||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
padding: const EdgeInsets.only(
|
||||
left: 24, right: 24, top: 12
|
||||
),
|
||||
child: TextFormField(
|
||||
controller: answerController,
|
||||
onChanged: (_) => checkValidValues(),
|
||||
|
@ -124,44 +127,70 @@ class _AddDnsRewriteModalState extends State<AddDnsRewriteModal> {
|
|||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(AppLocalizations.of(context)!.cancel),
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
TextButton(
|
||||
onPressed: validData == true
|
||||
? () {
|
||||
Navigator.pop(context);
|
||||
widget.onConfirm(
|
||||
RewriteRulesData(
|
||||
domain: domainController.text,
|
||||
answer: answerController.text
|
||||
)
|
||||
);
|
||||
}
|
||||
: null,
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.confirm,
|
||||
style: TextStyle(
|
||||
color: validData == true
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.onSurface.withOpacity(0.38)
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(AppLocalizations.of(context)!.cancel),
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
TextButton(
|
||||
onPressed: validData == true
|
||||
? () {
|
||||
Navigator.pop(context);
|
||||
widget.onConfirm(
|
||||
RewriteRulesData(
|
||||
domain: domainController.text,
|
||||
answer: answerController.text
|
||||
)
|
||||
);
|
||||
}
|
||||
: null,
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.confirm,
|
||||
style: TextStyle(
|
||||
color: validData == true
|
||||
? 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
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.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 appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
|
||||
void deleteDnsRewrite(RewriteRulesData rule) async {
|
||||
ProcessModal processModal = ProcessModal(context: context);
|
||||
processModal.open(AppLocalizations.of(context)!.deleting);
|
||||
|
@ -288,14 +292,26 @@ class _DnsRewritesWidgetState extends State<DnsRewritesWidget> {
|
|||
body: generateBody(),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: () => {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) => AddDnsRewriteModal(
|
||||
onConfirm: addDnsRewrite,
|
||||
),
|
||||
backgroundColor: Colors.transparent,
|
||||
isScrollControlled: true
|
||||
)
|
||||
if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AddDnsRewriteModal(
|
||||
onConfirm: addDnsRewrite,
|
||||
dialog: true,
|
||||
),
|
||||
)
|
||||
}
|
||||
else {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) => AddDnsRewriteModal(
|
||||
onConfirm: addDnsRewrite,
|
||||
dialog: false,
|
||||
),
|
||||
backgroundColor: Colors.transparent,
|
||||
isScrollControlled: true
|
||||
)
|
||||
}
|
||||
},
|
||||
child: const Icon(Icons.add),
|
||||
),
|
||||
|
|
|
@ -26,8 +26,12 @@ class EncryptionTextField extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
padding: width > 900
|
||||
? const EdgeInsets.symmetric(horizontal: 8)
|
||||
: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxHeight: 200
|
||||
|
|
|
@ -234,6 +234,8 @@ class _EncryptionSettingsWidgetState extends State<EncryptionSettingsWidget> {
|
|||
final serversProvider = Provider.of<ServersProvider>(context);
|
||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
|
||||
void saveData() async {
|
||||
ProcessModal processModal = ProcessModal(context: context);
|
||||
processModal.open(AppLocalizations.of(context)!.savingConfig);
|
||||
|
@ -343,43 +345,59 @@ class _EncryptionSettingsWidgetState extends State<EncryptionSettingsWidget> {
|
|||
disabled: !enabled,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
EncryptionTextField(
|
||||
enabled: enabled,
|
||||
controller: httpsPortController,
|
||||
icon: Icons.numbers_rounded,
|
||||
onChanged: (value) {
|
||||
setState(() => httpsPortError = validatePort(context, value));
|
||||
onEditValidate();
|
||||
},
|
||||
errorText: httpsPortError,
|
||||
label: AppLocalizations.of(context)!.httpsPort,
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
EncryptionTextField(
|
||||
enabled: enabled,
|
||||
controller: tlsPortController,
|
||||
icon: Icons.numbers_rounded,
|
||||
onChanged: (value) {
|
||||
setState(() => tlsPortError = validatePort(context, value));
|
||||
onEditValidate();
|
||||
},
|
||||
errorText: tlsPortError,
|
||||
label: AppLocalizations.of(context)!.tlsPort,
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
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,
|
||||
Wrap(
|
||||
children: [
|
||||
FractionallySizedBox(
|
||||
widthFactor: width > 900 ? 0.33 : 1,
|
||||
child: EncryptionTextField(
|
||||
enabled: enabled,
|
||||
controller: httpsPortController,
|
||||
icon: Icons.numbers_rounded,
|
||||
onChanged: (value) {
|
||||
setState(() => httpsPortError = validatePort(context, value));
|
||||
onEditValidate();
|
||||
},
|
||||
errorText: httpsPortError,
|
||||
label: AppLocalizations.of(context)!.httpsPort,
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: width <= 900
|
||||
? const EdgeInsets.symmetric(vertical: 24)
|
||||
: const EdgeInsets.all(0),
|
||||
child: FractionallySizedBox(
|
||||
widthFactor: width > 900 ? 0.33 : 1,
|
||||
child: EncryptionTextField(
|
||||
enabled: enabled,
|
||||
controller: tlsPortController,
|
||||
icon: Icons.numbers_rounded,
|
||||
onChanged: (value) {
|
||||
setState(() => tlsPortError = validatePort(context, value));
|
||||
onEditValidate();
|
||||
},
|
||||
errorText: tlsPortError,
|
||||
label: AppLocalizations.of(context)!.tlsPort,
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
),
|
||||
),
|
||||
FractionallySizedBox(
|
||||
widthFactor: width > 900 ? 0.33 : 1,
|
||||
child: EncryptionTextField(
|
||||
enabled: enabled,
|
||||
controller: dnsOverQuicPortController,
|
||||
icon: Icons.numbers_rounded,
|
||||
onChanged: (value) {
|
||||
setState(() => dnsOverQuicPortError = validatePort(context, value));
|
||||
onEditValidate();
|
||||
},
|
||||
errorText: dnsOverQuicPortError,
|
||||
label: AppLocalizations.of(context)!.dnsOverQuicPort,
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SectionLabel(
|
||||
label: AppLocalizations.of(context)!.certificates,
|
||||
|
|
|
@ -52,24 +52,26 @@ class _SafeSearchSettingsScreenWidgetState extends State<SafeSearchSettingsScree
|
|||
bool youtubeEnabled = false;
|
||||
|
||||
Future requestSafeSearchSettings() async {
|
||||
final result = await getServerStatus(widget.serversProvider.selectedServer!);
|
||||
if (mounted) {
|
||||
if (result['result'] == 'success') {
|
||||
widget.serversProvider.setServerStatusData(result['data']);
|
||||
widget.serversProvider.setServerStatusLoad(1);
|
||||
setState(() {
|
||||
generalEnabled = result['data'].safeSearchEnabled;
|
||||
bingEnabled = result['data'].safeSeachBing;
|
||||
duckduckgoEnabled = result['data'].safeSearchDuckduckgo;
|
||||
googleEnabled = result['data'].safeSearchGoogle;
|
||||
pixabayEnabled = result['data'].safeSearchPixabay;
|
||||
yandexEnabled = result['data'].safeSearchYandex;
|
||||
youtubeEnabled = result['data'].safeSearchYoutube;
|
||||
});
|
||||
}
|
||||
else {
|
||||
widget.appConfigProvider.addLog(result['log']);
|
||||
widget.serversProvider.setServerStatusLoad(2);
|
||||
final result = await getServerStatus(widget.serversProvider.selectedServer!);
|
||||
if (mounted) {
|
||||
if (result['result'] == 'success') {
|
||||
widget.serversProvider.setServerStatusData(result['data']);
|
||||
widget.serversProvider.setServerStatusLoad(1);
|
||||
setState(() {
|
||||
generalEnabled = result['data'].safeSearchEnabled;
|
||||
bingEnabled = result['data'].safeSeachBing;
|
||||
duckduckgoEnabled = result['data'].safeSearchDuckduckgo;
|
||||
googleEnabled = result['data'].safeSearchGoogle;
|
||||
pixabayEnabled = result['data'].safeSearchPixabay;
|
||||
yandexEnabled = result['data'].safeSearchYandex;
|
||||
youtubeEnabled = result['data'].safeSearchYoutube;
|
||||
});
|
||||
}
|
||||
else {
|
||||
widget.appConfigProvider.addLog(result['log']);
|
||||
widget.serversProvider.setServerStatusLoad(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.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: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/dhcp/dhcp.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/dns/dns.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/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/constants/strings.dart';
|
||||
|
@ -29,17 +33,84 @@ import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
|||
class Settings extends StatelessWidget {
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||
final serversProvider = Provider.of<ServersProvider>(context);
|
||||
|
||||
void navigateServers() {
|
||||
Future.delayed(const Duration(milliseconds: 0), (() {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(builder: (context) => const Servers())
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
|
||||
if (width <= 900 && appConfigProvider.selectedSettingsScreen != null) {
|
||||
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(
|
||||
|
@ -49,85 +120,55 @@ class Settings extends StatelessWidget {
|
|||
),
|
||||
body: ListView(
|
||||
children: [
|
||||
if (serversProvider.selectedServer != null) ...[
|
||||
if (serversProvider.selectedServer != null && serversProvider.serverStatus.data != null) ...[
|
||||
SectionLabel(label: AppLocalizations.of(context)!.serverSettings),
|
||||
if (serverVersionIsAhead(
|
||||
currentVersion: serversProvider.serverStatus.data!.serverVersion,
|
||||
referenceVersion: 'v0.107.28',
|
||||
referenceVersionBeta: 'v0.108.0-b.33'
|
||||
) == true) CustomListTile(
|
||||
) == true) settingsTile(
|
||||
icon: Icons.search_rounded,
|
||||
title: AppLocalizations.of(context)!.safeSearch,
|
||||
subtitle: AppLocalizations.of(context)!.safeSearchSettings,
|
||||
onTap: () => {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const SafeSearchSettingsScreen()
|
||||
)
|
||||
)
|
||||
},
|
||||
thisItem: 0,
|
||||
screenToNavigate: const SafeSearchSettingsScreen(),
|
||||
),
|
||||
CustomListTile(
|
||||
settingsTile(
|
||||
icon: Icons.lock_rounded,
|
||||
title: AppLocalizations.of(context)!.accessSettings,
|
||||
subtitle: AppLocalizations.of(context)!.accessSettingsDescription,
|
||||
onTap: () => {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const AccessSettings()
|
||||
)
|
||||
)
|
||||
},
|
||||
thisItem: 1,
|
||||
screenToNavigate: const AccessSettings(),
|
||||
),
|
||||
CustomListTile(
|
||||
settingsTile(
|
||||
icon: Icons.install_desktop_rounded,
|
||||
title: AppLocalizations.of(context)!.dhcpSettings,
|
||||
subtitle: AppLocalizations.of(context)!.dhcpSettingsDescription,
|
||||
onTap: () => {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const Dhcp()
|
||||
)
|
||||
)
|
||||
},
|
||||
thisItem: 2,
|
||||
screenToNavigate: const Dhcp(),
|
||||
),
|
||||
CustomListTile(
|
||||
settingsTile(
|
||||
icon: Icons.dns_rounded,
|
||||
title: AppLocalizations.of(context)!.dnsSettings,
|
||||
subtitle: AppLocalizations.of(context)!.dnsSettingsDescription,
|
||||
onTap: () => {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const DnsSettings()
|
||||
)
|
||||
)
|
||||
},
|
||||
thisItem: 3,
|
||||
screenToNavigate: const DnsSettings(),
|
||||
),
|
||||
CustomListTile(
|
||||
settingsTile(
|
||||
icon: Icons.security_rounded,
|
||||
title: AppLocalizations.of(context)!.encryptionSettings,
|
||||
subtitle: AppLocalizations.of(context)!.encryptionSettingsDescription,
|
||||
onTap: () => {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const EncryptionSettings()
|
||||
)
|
||||
)
|
||||
},
|
||||
thisItem: 4,
|
||||
screenToNavigate: const EncryptionSettings(),
|
||||
),
|
||||
CustomListTile(
|
||||
settingsTile(
|
||||
icon: Icons.route_rounded,
|
||||
title: AppLocalizations.of(context)!.dnsRewrites,
|
||||
subtitle: AppLocalizations.of(context)!.dnsRewritesDescription,
|
||||
onTap: () => {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const DnsRewrites()
|
||||
)
|
||||
)
|
||||
},
|
||||
thisItem: 5,
|
||||
screenToNavigate: const DnsRewrites(),
|
||||
),
|
||||
if (serversProvider.updateAvailable.data != null) CustomListTile(
|
||||
if (serversProvider.updateAvailable.data != null) settingsTile(
|
||||
icon: Icons.system_update_rounded,
|
||||
title: AppLocalizations.of(context)!.updates,
|
||||
subtitle: AppLocalizations.of(context)!.updatesDescription,
|
||||
|
@ -144,37 +185,26 @@ class Settings extends StatelessWidget {
|
|||
),
|
||||
)
|
||||
: null,
|
||||
onTap: () => {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const UpdateScreen()
|
||||
)
|
||||
)
|
||||
},
|
||||
thisItem: 6,
|
||||
screenToNavigate: const UpdateScreen(),
|
||||
),
|
||||
CustomListTile(
|
||||
settingsTile(
|
||||
icon: Icons.info_rounded,
|
||||
title: AppLocalizations.of(context)!.serverInformation,
|
||||
subtitle: AppLocalizations.of(context)!.serverInformationDescription,
|
||||
onTap: () => {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const ServerInformation()
|
||||
)
|
||||
)
|
||||
},
|
||||
thisItem: 7,
|
||||
screenToNavigate: const ServerInformation(),
|
||||
),
|
||||
],
|
||||
SectionLabel(label: AppLocalizations.of(context)!.appSettings),
|
||||
CustomListTile(
|
||||
settingsTile(
|
||||
icon: Icons.palette_rounded,
|
||||
title: AppLocalizations.of(context)!.customization,
|
||||
subtitle: AppLocalizations.of(context)!.customizationDescription,
|
||||
onTap: () => Navigator.push(context, MaterialPageRoute(
|
||||
builder: (context) => const Customization()
|
||||
))
|
||||
thisItem: 8,
|
||||
screenToNavigate: const Customization(),
|
||||
),
|
||||
CustomListTile(
|
||||
settingsTile(
|
||||
icon: Icons.storage_rounded,
|
||||
title: AppLocalizations.of(context)!.servers,
|
||||
subtitle: serversProvider.selectedServer != null
|
||||
|
@ -182,31 +212,22 @@ class Settings extends StatelessWidget {
|
|||
? "${AppLocalizations.of(context)!.connectedTo} ${serversProvider.selectedServer!.name}"
|
||||
: "${AppLocalizations.of(context)!.selectedServer} ${serversProvider.selectedServer!.name}"
|
||||
: AppLocalizations.of(context)!.noServerSelected,
|
||||
onTap: navigateServers,
|
||||
thisItem: 9,
|
||||
screenToNavigate: const Servers(),
|
||||
),
|
||||
CustomListTile(
|
||||
settingsTile(
|
||||
icon: Icons.settings,
|
||||
title: AppLocalizations.of(context)!.generalSettings,
|
||||
subtitle: AppLocalizations.of(context)!.generalSettingsDescription,
|
||||
onTap: () => {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const GeneralSettings()
|
||||
)
|
||||
)
|
||||
},
|
||||
thisItem: 10,
|
||||
screenToNavigate: const GeneralSettings(),
|
||||
),
|
||||
CustomListTile(
|
||||
settingsTile(
|
||||
icon: Icons.build_outlined,
|
||||
title: AppLocalizations.of(context)!.advancedSettings,
|
||||
subtitle: AppLocalizations.of(context)!.advancedSetupDescription,
|
||||
onTap: () => {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const AdvancedSettings()
|
||||
)
|
||||
)
|
||||
},
|
||||
thisItem: 11,
|
||||
screenToNavigate: const AdvancedSettings(),
|
||||
),
|
||||
SectionLabel(label: AppLocalizations.of(context)!.aboutApp),
|
||||
CustomListTile(
|
||||
|
@ -222,7 +243,7 @@ class Settings extends StatelessWidget {
|
|||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
IconButton(
|
||||
if (Platform.isAndroid) IconButton(
|
||||
onPressed: () => openUrl(Urls.playStore),
|
||||
icon: SvgPicture.asset(
|
||||
'assets/resources/google-play.svg',
|
||||
|
|
|
@ -65,16 +65,18 @@ class UpdateScreen extends StatelessWidget {
|
|||
Widget headerPortrait() {
|
||||
return Column(
|
||||
children: [
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
IconButton(
|
||||
if (Navigator.canPop(context)) IconButton(
|
||||
icon: Icon(
|
||||
Icons.arrow_back,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
if (!Navigator.canPop(context)) const SizedBox(),
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
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
|
||||
? ListView(
|
||||
children: [
|
||||
|
@ -313,51 +201,20 @@ class UpdateScreen extends StatelessWidget {
|
|||
: null;
|
||||
|
||||
return Scaffold(
|
||||
body: MediaQuery.of(context).size.width > 700
|
||||
? Row(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Container(
|
||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Container(
|
||||
height: MediaQuery.of(context).size.height,
|
||||
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(),
|
||||
]
|
||||
)
|
||||
body: Column(
|
||||
children: [
|
||||
Container(
|
||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||
child: SafeArea(
|
||||
child: headerPortrait()
|
||||
)
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
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 {
|
||||
final Server? server;
|
||||
final bool window;
|
||||
|
||||
const AddServerModal({
|
||||
Key? key,
|
||||
this.server,
|
||||
required this.window
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
@ -429,15 +431,227 @@ class _AddServerModalState extends State<AddServerModal> {
|
|||
}
|
||||
}
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(AppLocalizations.of(context)!.createConnection),
|
||||
actions: [
|
||||
List<Widget> form() {
|
||||
return [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||
margin: const EdgeInsets.only(
|
||||
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: const EdgeInsets.only(right: 10),
|
||||
child: IconButton(
|
||||
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(
|
||||
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
|
||||
? AppLocalizations.of(context)!.connect
|
||||
: AppLocalizations.of(context)!.save,
|
||||
|
@ -452,203 +666,49 @@ class _AddServerModalState extends State<AddServerModal> {
|
|||
: Icons.save_rounded
|
||||
)
|
||||
),
|
||||
),
|
||||
],
|
||||
toolbarHeight: 70,
|
||||
const SizedBox(width: 10)
|
||||
],
|
||||
toolbarHeight: 70,
|
||||
),
|
||||
body: ListView(
|
||||
children: form(),
|
||||
)
|
||||
),
|
||||
body: ListView(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||
margin: const EdgeInsets.only(
|
||||
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),
|
||||
],
|
||||
),
|
||||
),
|
||||
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(
|
||||
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,
|
||||
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) {
|
||||
logsProvider.resetFilters();
|
||||
}
|
||||
// Reset settings selected screen
|
||||
if (value != screens.length-1) {
|
||||
appConfigProvider.setSelectedSettingsScreen(screen: null);
|
||||
}
|
||||
appConfigProvider.setSelectedScreen(value);
|
||||
},
|
||||
);
|
||||
|
|
|
@ -10,6 +10,7 @@ class CustomListTile extends StatelessWidget {
|
|||
final EdgeInsets? padding;
|
||||
final void Function()? onLongPress;
|
||||
final bool? disabled;
|
||||
final void Function(bool)? onHover;
|
||||
|
||||
const CustomListTile({
|
||||
Key? key,
|
||||
|
@ -21,7 +22,8 @@ class CustomListTile extends StatelessWidget {
|
|||
this.trailing,
|
||||
this.padding,
|
||||
this.onLongPress,
|
||||
this.disabled
|
||||
this.disabled,
|
||||
this.onHover,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
@ -30,6 +32,7 @@ class CustomListTile extends StatelessWidget {
|
|||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
onHover: onHover,
|
||||
onLongPress: onLongPress,
|
||||
child: Padding(
|
||||
padding: padding ?? const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
|
|
|
@ -41,33 +41,35 @@ class CustomRadioListTile extends StatelessWidget {
|
|||
backgroundColor: radioBackgroundColor,
|
||||
),
|
||||
const SizedBox(width: 24),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
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),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: MediaQuery.of(context).size.width-110,
|
||||
child: Text(
|
||||
subtitle!,
|
||||
title,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).listTileTheme.textColor,
|
||||
fontSize: 14
|
||||
fontSize: 16,
|
||||
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
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const CircularProgressIndicator(),
|
||||
const SizedBox(width: 40),
|
||||
|
|
|
@ -23,9 +23,17 @@ class DeleteModal extends StatelessWidget {
|
|||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||
|
||||
void removeServer() async {
|
||||
final previouslySelectedServer = serversProvider.selectedServer;
|
||||
|
||||
final deleted = await serversProvider.removeServer(serverToDelete);
|
||||
|
||||
Navigator.pop(context);
|
||||
|
||||
if (deleted == true) {
|
||||
if (previouslySelectedServer != null && previouslySelectedServer.id == serverToDelete.id) {
|
||||
appConfigProvider.setSelectedScreen(0);
|
||||
}
|
||||
|
||||
showSnacbkar(
|
||||
context: context,
|
||||
appConfigProvider: appConfigProvider,
|
||||
|
|
|
@ -5,6 +5,7 @@ import 'package:provider/provider.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_tile_item.dart';
|
||||
|
||||
import 'package:adguard_home_manager/providers/servers_provider.dart';
|
||||
|
||||
|
@ -13,6 +14,7 @@ class ServersList extends StatelessWidget {
|
|||
final List<ExpandableController> controllers;
|
||||
final Function(int) onChange;
|
||||
final ScrollController scrollController;
|
||||
final double breakingWidth;
|
||||
|
||||
const ServersList({
|
||||
Key? key,
|
||||
|
@ -20,23 +22,44 @@ class ServersList extends StatelessWidget {
|
|||
required this.controllers,
|
||||
required this.onChange,
|
||||
required this.scrollController,
|
||||
required this.breakingWidth
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final serversProvider = Provider.of<ServersProvider>(context);
|
||||
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
|
||||
if (serversProvider.serversList.isNotEmpty) {
|
||||
return ListView.builder(
|
||||
controller: scrollController,
|
||||
itemCount: serversProvider.serversList.length,
|
||||
itemBuilder: (context, index) => ServersListItem(
|
||||
expandableController: controllers[index],
|
||||
server: serversProvider.serversList[index],
|
||||
index: index,
|
||||
onChange: onChange
|
||||
)
|
||||
);
|
||||
if (width > breakingWidth) {
|
||||
return ListView(
|
||||
children: [
|
||||
Wrap(
|
||||
children: serversProvider.serversList.asMap().entries.map(
|
||||
(s) => ServersTileItem(
|
||||
server: serversProvider.serversList[s.key],
|
||||
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 {
|
||||
return SizedBox(
|
||||
|
|
|
@ -71,6 +71,8 @@ class _ServersListItemState extends State<ServersListItem> with SingleTickerProv
|
|||
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(
|
||||
|
@ -85,10 +87,25 @@ class _ServersListItemState extends State<ServersListItem> with SingleTickerProv
|
|||
|
||||
void openAddServerBottomSheet({Server? server}) async {
|
||||
await Future.delayed(const Duration(seconds: 0), (() => {
|
||||
Navigator.push(context, MaterialPageRoute(
|
||||
fullscreenDialog: true,
|
||||
builder: (BuildContext context) => AddServerModal(server: server)
|
||||
))
|
||||
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,
|
||||
)
|
||||
))
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
|
@ -356,7 +373,6 @@ class _ServersListItemState extends State<ServersListItem> with SingleTickerProv
|
|||
);
|
||||
}
|
||||
|
||||
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
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 Widget? fab;
|
||||
final bool? fabVisible;
|
||||
final bool? noSliver;
|
||||
final EdgeInsets? listPadding;
|
||||
|
||||
const CustomTabContentList({
|
||||
Key? key,
|
||||
|
@ -27,7 +29,9 @@ class CustomTabContentList extends StatelessWidget {
|
|||
required this.onRefresh,
|
||||
this.refreshIndicatorOffset,
|
||||
this.fab,
|
||||
this.fabVisible
|
||||
this.fabVisible,
|
||||
this.noSliver,
|
||||
this.listPadding
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
@ -36,95 +40,157 @@ class CustomTabContentList extends StatelessWidget {
|
|||
|
||||
switch (loadStatus) {
|
||||
case LoadStatus.loading:
|
||||
return SafeArea(
|
||||
top: false,
|
||||
bottom: false,
|
||||
child: Builder(
|
||||
builder: (BuildContext context) => CustomScrollView(
|
||||
slivers: [
|
||||
SliverOverlapInjector(
|
||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||
),
|
||||
SliverFillRemaining(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: loadingGenerator()
|
||||
if (noSliver == true) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: loadingGenerator()
|
||||
);
|
||||
}
|
||||
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.symmetric(horizontal: 16),
|
||||
child: loadingGenerator()
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
case LoadStatus.loaded:
|
||||
return Stack(
|
||||
children: [
|
||||
SafeArea(
|
||||
top: false,
|
||||
bottom: false,
|
||||
child: Builder(
|
||||
builder: (BuildContext context) {
|
||||
return RefreshIndicator(
|
||||
onRefresh: onRefresh,
|
||||
edgeOffset: refreshIndicatorOffset ?? 95,
|
||||
child: CustomScrollView(
|
||||
slivers: <Widget>[
|
||||
SliverOverlapInjector(
|
||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||
),
|
||||
if (itemsCount > 0) SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) => contentWidget(index),
|
||||
childCount: itemsCount
|
||||
if (noSliver == true) {
|
||||
if (itemsCount > 0) {
|
||||
return Stack(
|
||||
children: [
|
||||
ListView.builder(
|
||||
padding: listPadding,
|
||||
itemCount: itemsCount,
|
||||
itemBuilder: (context, index) => contentWidget(index),
|
||||
),
|
||||
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: [
|
||||
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) SliverFillRemaining(
|
||||
child: noData,
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
if (itemsCount > 0) SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) => contentWidget(index),
|
||||
childCount: itemsCount
|
||||
),
|
||||
),
|
||||
if (itemsCount == 0) SliverFillRemaining(
|
||||
child: 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!
|
||||
),
|
||||
],
|
||||
);
|
||||
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!
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
case LoadStatus.error:
|
||||
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()
|
||||
),
|
||||
)
|
||||
],
|
||||
if (noSliver == true) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 95,
|
||||
left: 16,
|
||||
right: 16
|
||||
),
|
||||
)
|
||||
);
|
||||
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:
|
||||
return const SizedBox();
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
@ -22,14 +24,30 @@ class UpdateModal extends StatefulWidget {
|
|||
class _UpdateModalState extends State<UpdateModal> {
|
||||
bool doNotRemember = false;
|
||||
|
||||
String getDownloadLink() {
|
||||
return widget.gitHubRelease.assets.firstWhere((item) => item.browserDownloadUrl.contains('apk')).browserDownloadUrl;
|
||||
String? getDownloadLink() {
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||
|
||||
final downloadLink = getDownloadLink();
|
||||
|
||||
return AlertDialog(
|
||||
scrollable: true,
|
||||
title: Column(
|
||||
|
@ -104,10 +122,10 @@ class _UpdateModalState extends State<UpdateModal> {
|
|||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
TextButton(
|
||||
if (downloadLink != null) TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
widget.onDownload(getDownloadLink(), widget.gitHubRelease.tagName);
|
||||
widget.onDownload(downloadLink, widget.gitHubRelease.tagName);
|
||||
},
|
||||
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