diff --git a/ios/.gitignore b/ios/.gitignore old mode 100644 new mode 100755 diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist old mode 100644 new mode 100755 diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig old mode 100644 new mode 100755 diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig old mode 100644 new mode 100755 diff --git a/ios/Podfile b/ios/Podfile old mode 100644 new mode 100755 diff --git a/ios/Podfile.lock b/ios/Podfile.lock old mode 100644 new mode 100755 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj old mode 100644 new mode 100755 index 4f766b9..00edeae --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -68,7 +68,6 @@ 65533F0C0783FDE34AE79B0A /* Pods-Runner.release.xcconfig */, 69C2CC4A6DE17506FC5C0F13 /* Pods-Runner.profile.xcconfig */, ); - name = Pods; path = Pods; sourceTree = ""; }; @@ -156,6 +155,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastUpgradeCheck = 1430; ORGANIZATIONNAME = ""; TargetAttributes = { @@ -325,6 +325,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -335,6 +336,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -343,7 +345,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -361,15 +363,22 @@ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = 38Z3B9TJTR; ENABLE_BITCODE = NO; + ENABLE_USER_SCRIPT_SANDBOXING = NO; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "AdGuard Home"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.jgeek00.adguard_home_manager; + PRODUCT_BUNDLE_IDENTIFIER = com.jgeek00.adguardHomeManager; PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; name = Profile; @@ -397,6 +406,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -407,6 +417,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -421,7 +432,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -452,6 +463,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -462,6 +474,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -470,7 +483,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -490,16 +503,23 @@ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = 38Z3B9TJTR; ENABLE_BITCODE = NO; + ENABLE_USER_SCRIPT_SANDBOXING = NO; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "AdGuard Home"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.jgeek00.adguard_home_manager; + PRODUCT_BUNDLE_IDENTIFIER = com.jgeek00.adguardHomeManager; PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; @@ -513,15 +533,22 @@ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = 38Z3B9TJTR; ENABLE_BITCODE = NO; + ENABLE_USER_SCRIPT_SANDBOXING = NO; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "AdGuard Home"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.jgeek00.adguard_home_manager; + PRODUCT_BUNDLE_IDENTIFIER = com.jgeek00.adguardHomeManager; PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; diff --git a/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata old mode 100644 new mode 100755 diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist old mode 100644 new mode 100755 diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings old mode 100644 new mode 100755 diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme old mode 100644 new mode 100755 diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata old mode 100644 new mode 100755 diff --git a/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist old mode 100644 new mode 100755 diff --git a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings old mode 100644 new mode 100755 diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json b/ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png b/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png b/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@2x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@2x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@3x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@3x.png old mode 100644 new mode 100755 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md old mode 100644 new mode 100755 diff --git a/ios/Runner/Base.lproj/LaunchScreen.storyboard b/ios/Runner/Base.lproj/LaunchScreen.storyboard old mode 100644 new mode 100755 diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard old mode 100644 new mode 100755 diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist old mode 100644 new mode 100755 index f9aeb27..55fe6b7 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -1,53 +1,53 @@ - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleDisplayName - AdGuard Home Manager - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - adguard_home_manager - CFBundlePackageType - APPL - CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) - CFBundleSignature - ???? - CFBundleVersion - $(FLUTTER_BUILD_NUMBER) - LSRequiresIPhoneOS - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UIViewControllerBasedStatusBarAppearance - - CADisableMinimumFrameDurationOnPhone - - UIApplicationSupportsIndirectInputEvents - - UIStatusBarHidden - - + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + AdGuard Home + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + adguardHomeManager + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIStatusBarHidden + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + diff --git a/ios/Runner/Runner-Bridging-Header.h b/ios/Runner/Runner-Bridging-Header.h old mode 100644 new mode 100755 diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 4fe20b3..6bf0c27 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -713,5 +713,17 @@ "noFallbackDnsAdded": "No fallback DNS servers added.", "blockedResponseTtl": "Blocked response TTL", "blockedResponseTtlDescription": "Specifies for how many seconds the clients should cache a filtered response", - "invalidValue": "Invalid value" + "invalidValue": "Invalid value", + "noDataChart": "There's no data to display this chart.", + "noData": "No data", + "unblockClient": "Unblock client", + "blockingClient": "Blocking client...", + "unblockingClient": "Unblocking client...", + "upstreamDnsCacheConfiguration": "Configuración de la caché DNS upstream", + "enableDnsCachingClient": "Enable DNS caching for this client", + "dnsCacheSize": "DNS cache size", + "nameInvalid": "Name is required", + "oneIdentifierRequired": "At least one identifier is required", + "dnsCacheNumber": "DNS cache size must be a number", + "errors": "Errors" } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index d3e0855..a6a222b 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -713,5 +713,17 @@ "noFallbackDnsAdded": "No hay servidores DNS alternativos añadidos.", "blockedResponseTtl": "Respuesta TTL bloqueada", "blockedResponseTtlDescription": "Especifica durante cuántos segundos los clientes deben almacenar en cache una respuesta filtrada", - "invalidValue": "Valor no válido" + "invalidValue": "Valor no válido", + "noDataChart": "No hay datos para mostrar este gráfico.", + "noData": "No hay datos", + "unblockClient": "Desbloquear cliente", + "blockingClient": "Bloqueando cliente...", + "unblockingClient": "Desbloqueando cliente...", + "upstreamDnsCacheConfiguration": "Configuración de la caché DNS upstream", + "enableDnsCachingClient": "Habilitar caché de DNS para este cliente", + "dnsCacheSize": "Tamaño de caché de DNS", + "nameInvalid": "Se requiere un nombre", + "oneIdentifierRequired": "Se require al menos un identificador", + "dnsCacheNumber": "El tamaño de caché de DNS debe ser un número", + "errors": "Errores" } \ No newline at end of file diff --git a/lib/models/clients.dart b/lib/models/clients.dart index 5808391..67740bd 100644 --- a/lib/models/clients.dart +++ b/lib/models/clients.dart @@ -89,6 +89,8 @@ class Client { final SafeSearch? safeSearch; final bool? ignoreQuerylog; final bool? ignoreStatistics; + final bool? upstreamsCacheEnabled; + final int? upstreamsCacheSize; Client({ required this.name, @@ -104,6 +106,8 @@ class Client { required this.safeSearch, required this.ignoreQuerylog, required this.ignoreStatistics, + required this.upstreamsCacheEnabled, + required this.upstreamsCacheSize, }); factory Client.fromJson(Map json) => Client( @@ -121,7 +125,9 @@ class Client { ? SafeSearch.fromJson(json["safe_search"]) : null, ignoreQuerylog: json["ignore_querylog"], - ignoreStatistics: json["ignore_statistics"] + ignoreStatistics: json["ignore_statistics"], + upstreamsCacheEnabled: json["upstreams_cache_enabled"], + upstreamsCacheSize: json["upstreams_cache_size"] ); Map toJson() => { @@ -138,5 +144,7 @@ class Client { "use_global_settings": useGlobalSettings, "ignore_querylog": ignoreQuerylog, "ignore_statistics": ignoreStatistics, + "upstreams_cache_enabled": upstreamsCacheEnabled, + "upstreams_cache_size": upstreamsCacheSize }; } \ No newline at end of file diff --git a/lib/models/menu_option.dart b/lib/models/menu_option.dart index b89f8d9..e898514 100644 --- a/lib/models/menu_option.dart +++ b/lib/models/menu_option.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; class MenuOption { final IconData? icon; final String title; - final void Function(dynamic) action; + final void Function() action; final bool? disabled; const MenuOption({ diff --git a/lib/providers/clients_provider.dart b/lib/providers/clients_provider.dart index bfcf19d..36185cc 100644 --- a/lib/providers/clients_provider.dart +++ b/lib/providers/clients_provider.dart @@ -190,6 +190,16 @@ class ClientsProvider with ChangeNotifier { "blocked_hosts": clients!.clientsAllowedBlocked?.blockedHosts ?? [], }; + if (body['allowed_clients']!.contains(item)) { + body['allowed_clients'] = body['allowed_clients']!.where((e) => e != item).toList(); + } + else if (body['disallowed_clients']!.contains(item)) { + body['disallowed_clients'] = body['disallowed_clients']!.where((e) => e != item).toList(); + } + else if (body['blocked_hosts']!.contains(item)) { + body['blocked_hosts'] = body['blocked_hosts']!.where((e) => e != item).toList(); + } + if (type == AccessSettingsList.allowed) { body['allowed_clients']!.add(item); } @@ -223,6 +233,18 @@ class ClientsProvider with ChangeNotifier { } } + AccessSettingsList? checkClientList(String client) { + if (_clients!.clientsAllowedBlocked!.allowedClients.contains(client)) { + return AccessSettingsList.allowed; + } + else if (_clients!.clientsAllowedBlocked!.disallowedClients.contains(client)) { + return AccessSettingsList.disallowed; + } + else { + return null; + } + } + Future removeClientList(String client, AccessSettingsList type) async { Map> body = { "allowed_clients": clients!.clientsAllowedBlocked?.allowedClients ?? [], diff --git a/lib/providers/logs_provider.dart b/lib/providers/logs_provider.dart index 7861e6d..d9bc55f 100644 --- a/lib/providers/logs_provider.dart +++ b/lib/providers/logs_provider.dart @@ -114,9 +114,15 @@ class LogsProvider with ChangeNotifier { _offset = value; } - void setSelectedResultStatus(String value) { + void setSelectedResultStatus({ + required String value, + bool? refetch + }) { _selectedResultStatus = value; notifyListeners(); + if (refetch = true) { + filterLogs(); + } } void setSearchText(String? value) { diff --git a/lib/screens/clients/client/active_client_tile.dart b/lib/screens/clients/client/active_client_tile.dart index 76c928c..7a7a825 100644 --- a/lib/screens/clients/client/active_client_tile.dart +++ b/lib/screens/clients/client/active_client_tile.dart @@ -28,11 +28,11 @@ class ActiveClientTile extends StatelessWidget { return Padding( padding: const EdgeInsets.symmetric(horizontal: 12), child: OptionsMenu( - options: [ + options: (_) => [ MenuOption( icon: Icons.copy_rounded, title: AppLocalizations.of(context)!.copyClipboard, - action: (_) => copyToClipboard( + action: () => copyToClipboard( value: client.name != '' ? client.name! : client.ip, @@ -99,11 +99,11 @@ class ActiveClientTile extends StatelessWidget { } else { return OptionsMenu( - options: [ + options: (_) => [ MenuOption( icon: Icons.copy_rounded, title: AppLocalizations.of(context)!.copyClipboard, - action: (_) => copyToClipboard( + action: () => copyToClipboard( value: client.name != '' ? client.name! : client.ip, diff --git a/lib/screens/clients/client/added_client_tile.dart b/lib/screens/clients/client/added_client_tile.dart index 4f4037e..8c10fd7 100644 --- a/lib/screens/clients/client/added_client_tile.dart +++ b/lib/screens/clients/client/added_client_tile.dart @@ -46,11 +46,11 @@ class _AddedClientTileState extends State { color: Colors.transparent, borderRadius: BorderRadius.circular(28), child: OptionsMenu( - options: [ + options: (_) => [ MenuOption( icon: Icons.copy_rounded, title: AppLocalizations.of(context)!.copyClipboard, - action: (_) => copyToClipboard( + action: () => copyToClipboard( value: widget.client.ids.toString().replaceAll(RegExp(r'^\[|\]$'), ''), successMessage: AppLocalizations.of(context)!.copiedClipboard, ) @@ -164,16 +164,16 @@ class _AddedClientTileState extends State { } else { return OptionsMenu( - options: [ + options: (_) => [ if (widget.onEdit != null) MenuOption( title: AppLocalizations.of(context)!.seeDetails, icon: Icons.file_open_rounded, - action: (_) => widget.onEdit!(widget.client) + action: () => widget.onEdit!(widget.client) ), MenuOption( icon: Icons.copy_rounded, title: AppLocalizations.of(context)!.copyClipboard, - action: (_) => copyToClipboard( + action: () => copyToClipboard( value: widget.client.ids.toString().replaceAll(RegExp(r'^\[|\]$'), ''), successMessage: AppLocalizations.of(context)!.copiedClipboard, ) diff --git a/lib/screens/clients/client/blocked_services_section.dart b/lib/screens/clients/client/blocked_services_section.dart index f6effd4..4a7fc11 100644 --- a/lib/screens/clients/client/blocked_services_section.dart +++ b/lib/screens/clients/client/blocked_services_section.dart @@ -22,7 +22,7 @@ class BlockedServicesSection extends StatelessWidget { return Column( children: [ Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), + padding: const EdgeInsets.symmetric(horizontal: 16), child: Material( color: Theme.of(context).colorScheme.primary.withOpacity(0.1), borderRadius: BorderRadius.circular(28), @@ -31,8 +31,8 @@ class BlockedServicesSection extends StatelessWidget { borderRadius: BorderRadius.circular(28), child: Padding( padding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 5 + horizontal: 16, + vertical: 6 ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -56,7 +56,7 @@ class BlockedServicesSection extends StatelessWidget { ), ), ), - const SizedBox(height: 10), + const SizedBox(height: 12), Material( color: Colors.transparent, child: InkWell( @@ -69,7 +69,7 @@ class BlockedServicesSection extends StatelessWidget { : null, child: Padding( padding: const EdgeInsets.symmetric( - vertical: 8, horizontal: 24 + vertical: 8, horizontal: 32 ), child: Row( children: [ diff --git a/lib/screens/clients/client/client_form.dart b/lib/screens/clients/client/client_form.dart index 5c164af..e127466 100644 --- a/lib/screens/clients/client/client_form.dart +++ b/lib/screens/clients/client/client_form.dart @@ -1,6 +1,3 @@ -import 'dart:io'; - -import 'package:adguard_home_manager/widgets/custom_switch_list_tile.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -12,6 +9,7 @@ import 'package:adguard_home_manager/screens/clients/client/settings_tile.dart'; import 'package:adguard_home_manager/screens/clients/client/tags_section.dart'; import 'package:adguard_home_manager/screens/clients/client/upstream_servers_section.dart'; +import 'package:adguard_home_manager/widgets/custom_switch_list_tile.dart'; import 'package:adguard_home_manager/widgets/custom_list_tile.dart'; import 'package:adguard_home_manager/widgets/section_label.dart'; @@ -22,7 +20,6 @@ class ClientForm extends StatelessWidget { final bool isFullScreen; final Client? client; final TextEditingController nameController; - final void Function(bool) updateValidValues; final List identifiersControllers; final List selectedTags; final bool useGlobalSettingsFiltering; @@ -50,13 +47,17 @@ class ClientForm extends StatelessWidget { final void Function(bool) updateIgnoreClientQueryLog; final bool ignoreClientStatistics; final void Function(bool) updateIgnoreClientStatistics; + final bool enableDnsCache; + final void Function(bool) updateEnableDnsCache; + final TextEditingController dnsCacheField; + final String? dnsCacheError; + final void Function(String?) updateDnsCacheError; const ClientForm({ super.key, required this.isFullScreen, required this.client, required this.nameController, - required this.updateValidValues, required this.identifiersControllers, required this.selectedTags, required this.useGlobalSettingsFiltering, @@ -84,26 +85,24 @@ class ClientForm extends StatelessWidget { required this.ignoreClientStatistics, required this.updateIgnoreClientQueryLog, required this.updateIgnoreClientStatistics, + required this.enableDnsCache, + required this.updateEnableDnsCache, + required this.dnsCacheField, + required this.dnsCacheError, + required this.updateDnsCacheError, }); @override Widget build(BuildContext context) { - return ListView( - padding: const EdgeInsets.only(top: 0), + return Column( children: [ - if (isFullScreen == true) const SizedBox(height: 24), - if (isFullScreen == false) const SizedBox(height: 6), + const SizedBox(height: 8), Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), + padding: const EdgeInsets.symmetric(horizontal: 16), child: TextFormField( enabled: client != null ? false : true, controller: nameController, - onChanged: (_) => updateValidValues( - checkValidValues( - identifiersControllers: identifiersControllers, - nameController: nameController - ) - ), + onChanged: (_) => {}, decoration: InputDecoration( prefixIcon: const Icon(Icons.badge_rounded), border: const OutlineInputBorder( @@ -117,7 +116,7 @@ class ClientForm extends StatelessWidget { ), SectionLabel( label: AppLocalizations.of(context)!.tags, - padding: const EdgeInsets.all(24), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), ), TagsSection( selectedTags: selectedTags, @@ -127,28 +126,17 @@ class ClientForm extends StatelessWidget { identifiersControllers: identifiersControllers, onUpdateIdentifiersControllers: (c) { updateIdentifiersControllers(c); - updateValidValues( - checkValidValues( - nameController: nameController, - identifiersControllers: identifiersControllers - ) - ); }, - onCheckValidValues: () => updateValidValues( - checkValidValues( - identifiersControllers: identifiersControllers, - nameController: nameController - ) - ), + onCheckValidValues: () => {} ), SectionLabel( label: AppLocalizations.of(context)!.settings, padding: const EdgeInsets.only( - left: 24, right: 24, top: 12, bottom: 24 + left: 16, right: 16, top: 12, bottom: 24 ) ), Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), + padding: const EdgeInsets.symmetric(horizontal: 16), child: Material( color: Theme.of(context).colorScheme.primary.withOpacity(0.1), borderRadius: BorderRadius.circular(28), @@ -157,8 +145,8 @@ class ClientForm extends StatelessWidget { borderRadius: BorderRadius.circular(28), child: Padding( padding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 5 + horizontal: 16, + vertical: 6 ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -182,7 +170,7 @@ class ClientForm extends StatelessWidget { ), ), ), - const SizedBox(height: 10), + const SizedBox(height: 8), SettingsTile( label: AppLocalizations.of(context)!.enableFiltering, value: enableFiltering, @@ -204,7 +192,7 @@ class ClientForm extends StatelessWidget { CustomListTile( title: AppLocalizations.of(context)!.safeSearch, padding: const EdgeInsets.symmetric( - horizontal: 42, + horizontal: 34, vertical: 16 ), trailing: Padding( @@ -228,15 +216,15 @@ class ClientForm extends StatelessWidget { ), SectionLabel( label: AppLocalizations.of(context)!.queryLogsAndStatistics, - padding: const EdgeInsets.all(24), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), ), CustomSwitchListTile( title: AppLocalizations.of(context)!.ignoreClientQueryLog, value: ignoreClientQueryLog, onChanged: updateIgnoreClientQueryLog, padding: const EdgeInsets.symmetric( - horizontal: 24, - vertical: 4 + horizontal: 16, + vertical: 6 ), ), CustomSwitchListTile( @@ -244,13 +232,13 @@ class ClientForm extends StatelessWidget { value: ignoreClientStatistics, onChanged: updateIgnoreClientStatistics, padding: const EdgeInsets.symmetric( - horizontal: 24, - vertical: 4 + horizontal: 16, + vertical: 6 ), ), SectionLabel( label: AppLocalizations.of(context)!.blockedServices, - padding: const EdgeInsets.all(24), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), ), BlockedServicesSection( useGlobalSettingsServices: useGlobalSettingsServices, @@ -260,15 +248,40 @@ class ClientForm extends StatelessWidget { ), UpstreamServersSection( upstreamServers: upstreamServers, - onCheckValidValues: () => updateValidValues( - checkValidValues( - identifiersControllers: identifiersControllers, - nameController: nameController - ) - ), + onCheckValidValues: () => {}, onUpdateUpstreamServers: updateUpstreamServers ), - SizedBox(height: Platform.isIOS ? 48 : 24) + SectionLabel( + label: AppLocalizations.of(context)!.upstreamDnsCacheConfiguration, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), + ), + CustomSwitchListTile( + title: AppLocalizations.of(context)!.enableDnsCachingClient, + value: enableDnsCache, + onChanged: updateEnableDnsCache, + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 6 + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), + child: TextFormField( + controller: dnsCacheField, + onChanged: (v) => updateDnsCacheError(!validateNumber(v) ? AppLocalizations.of(context)!.invalidValue : null), + decoration: InputDecoration( + prefixIcon: const Icon(Icons.storage_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + labelText: AppLocalizations.of(context)!.dnsCacheSize, + errorText: dnsCacheError + ), + keyboardType: TextInputType.number, + ), + ), ], ); } diff --git a/lib/screens/clients/client/client_screen.dart b/lib/screens/clients/client/client_screen.dart index 9a16e1a..f41de69 100644 --- a/lib/screens/clients/client/client_screen.dart +++ b/lib/screens/clients/client/client_screen.dart @@ -39,6 +39,8 @@ class ClientScreen extends StatefulWidget { } class _ClientScreenState extends State { + final _scrollController = ScrollController(); + final Uuid uuid = const Uuid(); bool validValues = false; @@ -76,6 +78,15 @@ class _ClientScreenState extends State { bool _ignoreClientQueryLog = false; bool _ignoreClientStatistics = false; + bool _enableDnsCache = false; + final _dnsCacheField = TextEditingController(); + String? _dnsCacheError; + + // VALIDATIONS + bool _nameValid = true; + bool _identifiersValid = true; + bool _dnsCacheValid = true; + void enableDisableGlobalSettingsFiltering() { if (useGlobalSettingsFiltering == true) { setState(() { @@ -125,6 +136,10 @@ class _ClientScreenState extends State { )).toList(); _ignoreClientQueryLog = widget.client!.ignoreQuerylog ?? false; _ignoreClientStatistics = widget.client!.ignoreStatistics ?? false; + _enableDnsCache = widget.client!.upstreamsCacheEnabled ?? false; + _dnsCacheField.text = widget.client!.upstreamsCacheSize != null + ? widget.client!.upstreamsCacheSize.toString() + : ""; } super.initState(); } @@ -147,20 +162,37 @@ class _ClientScreenState extends State { upstreams: List.from(upstreamServers.map((e) => e.controller.text)), tags: selectedTags, ignoreQuerylog: _ignoreClientQueryLog, - ignoreStatistics: _ignoreClientStatistics + ignoreStatistics: _ignoreClientStatistics, + upstreamsCacheEnabled: _enableDnsCache, + upstreamsCacheSize: _dnsCacheField.text != "" + ? int.parse(_dnsCacheField.text) + : null ); widget.onConfirm(client); } + void validateValues() { + _nameValid = nameController.text != ''; + _identifiersValid = identifiersControllers.isNotEmpty && identifiersControllers[0].controller.text != ''; + _dnsCacheValid = (_dnsCacheField.text == "" || _dnsCacheField.text != "" && RegExp(r'^\d+$').hasMatch(_dnsCacheField.text)); + if (_nameValid && _identifiersValid && _dnsCacheValid) { + createClient(); + Navigator.pop(context); + } + else { + _scrollController.animateTo( + 0, + curve: Curves.easeOut, + duration: const Duration(milliseconds: 500) + ); + setState(() => {}); + } + } + List actions() { return [ IconButton( - onPressed: validValues == true - ? () { - createClient(); - Navigator.pop(context); - } - : null, + onPressed: validateValues, icon: const Icon(Icons.save_rounded), tooltip: AppLocalizations.of(context)!.save, ), @@ -193,38 +225,52 @@ class _ClientScreenState extends State { actions: actions(), ), body: SafeArea( - child: ClientForm( - isFullScreen: true, - client: widget.client, - nameController: nameController, - updateValidValues: (v) => setState(() => validValues = v), - identifiersControllers: identifiersControllers, - selectedTags: selectedTags, - useGlobalSettingsFiltering: useGlobalSettingsFiltering, - enableFiltering: enableFiltering, - enableParentalControl: enableParentalControl, - enableSafeBrowsing: enableSafeBrowsing, - enableSafeSearch: enableSafeSearch, - safeSearch: safeSearch, - blockedServices: blockedServices, - updateBlockedServices: (v) => setState(() => blockedServices = v), - upstreamServers: upstreamServers, - updateUpstreamServers: (v) => setState(() => upstreamServers = v), - defaultSafeSearch: defaultSafeSearch, - useGlobalSettingsServices: useGlobalSettingsServices, - updateSelectedTags: (v) => setState(() => selectedTags = v), - updateIdentifiersControllers: (v) => setState(() => identifiersControllers = v), - enableDisableGlobalSettingsFiltering: enableDisableGlobalSettingsFiltering, - updateEnableFiltering: (v) => setState(() => enableFiltering = v), - updateEnableParentalControl: (v) => setState(() => enableParentalControl = v), - updateEnableSafeBrowsing: (v) => setState(() => enableSafeBrowsing = v), - updateEnableSafeSearch: (v) => setState(() => enableSafeSearch = v), - updateSafeSearch: (v) => setState(() => safeSearch = v), - updateUseGlobalSettingsServices: (v) => setState(() => useGlobalSettingsServices = v), - ignoreClientQueryLog: _ignoreClientQueryLog, - ignoreClientStatistics: _ignoreClientStatistics, - updateIgnoreClientQueryLog: (v) => setState(() => _ignoreClientQueryLog = v), - updateIgnoreClientStatistics: (v) => setState(() => _ignoreClientStatistics = v), + child: ListView( + controller: _scrollController, + children: [ + if (!_nameValid || !_identifiersValid || !_dnsCacheValid) _Errors( + nameValid: _nameValid, + identifiersValid: _identifiersValid, + dnsCacheValid: _dnsCacheValid + ), + ClientForm( + isFullScreen: true, + client: widget.client, + nameController: nameController, + identifiersControllers: identifiersControllers, + selectedTags: selectedTags, + useGlobalSettingsFiltering: useGlobalSettingsFiltering, + enableFiltering: enableFiltering, + enableParentalControl: enableParentalControl, + enableSafeBrowsing: enableSafeBrowsing, + enableSafeSearch: enableSafeSearch, + safeSearch: safeSearch, + blockedServices: blockedServices, + updateBlockedServices: (v) => setState(() => blockedServices = v), + upstreamServers: upstreamServers, + updateUpstreamServers: (v) => setState(() => upstreamServers = v), + defaultSafeSearch: defaultSafeSearch, + useGlobalSettingsServices: useGlobalSettingsServices, + updateSelectedTags: (v) => setState(() => selectedTags = v), + updateIdentifiersControllers: (v) => setState(() => identifiersControllers = v), + enableDisableGlobalSettingsFiltering: enableDisableGlobalSettingsFiltering, + updateEnableFiltering: (v) => setState(() => enableFiltering = v), + updateEnableParentalControl: (v) => setState(() => enableParentalControl = v), + updateEnableSafeBrowsing: (v) => setState(() => enableSafeBrowsing = v), + updateEnableSafeSearch: (v) => setState(() => enableSafeSearch = v), + updateSafeSearch: (v) => setState(() => safeSearch = v), + updateUseGlobalSettingsServices: (v) => setState(() => useGlobalSettingsServices = v), + ignoreClientQueryLog: _ignoreClientQueryLog, + ignoreClientStatistics: _ignoreClientStatistics, + updateIgnoreClientQueryLog: (v) => setState(() => _ignoreClientQueryLog = v), + updateIgnoreClientStatistics: (v) => setState(() => _ignoreClientStatistics = v), + enableDnsCache: _enableDnsCache, + updateEnableDnsCache: (v) => setState(() => _enableDnsCache = v), + dnsCacheField: _dnsCacheField, + dnsCacheError: _dnsCacheError, + updateDnsCacheError: (v) => setState(() => _dnsCacheError = v) + ), + ], ), ), ), @@ -264,38 +310,52 @@ class _ClientScreenState extends State { ), ), Flexible( - child: ClientForm( - isFullScreen: false, - client: widget.client, - nameController: nameController, - updateValidValues: (v) => setState(() => validValues = v), - identifiersControllers: identifiersControllers, - selectedTags: selectedTags, - useGlobalSettingsFiltering: useGlobalSettingsFiltering, - enableFiltering: enableFiltering, - enableParentalControl: enableParentalControl, - enableSafeBrowsing: enableSafeBrowsing, - enableSafeSearch: enableSafeSearch, - safeSearch: safeSearch, - blockedServices: blockedServices, - updateBlockedServices: (v) => setState(() => blockedServices = v), - upstreamServers: upstreamServers, - updateUpstreamServers: (v) => setState(() => upstreamServers = v), - defaultSafeSearch: defaultSafeSearch, - useGlobalSettingsServices: useGlobalSettingsServices, - updateSelectedTags: (v) => setState(() => selectedTags = v), - updateIdentifiersControllers: (v) => setState(() => identifiersControllers = v), - enableDisableGlobalSettingsFiltering: enableDisableGlobalSettingsFiltering, - updateEnableFiltering: (v) => setState(() => enableFiltering = v), - updateEnableParentalControl: (v) => setState(() => enableParentalControl = v), - updateEnableSafeBrowsing: (v) => setState(() => enableSafeBrowsing = v), - updateEnableSafeSearch: (v) => setState(() => enableSafeSearch = v), - updateSafeSearch: (v) => setState(() => safeSearch = v), - updateUseGlobalSettingsServices: (v) => setState(() => useGlobalSettingsServices = v), - ignoreClientQueryLog: _ignoreClientQueryLog, - ignoreClientStatistics: _ignoreClientStatistics, - updateIgnoreClientQueryLog: (v) => setState(() => _ignoreClientQueryLog = v), - updateIgnoreClientStatistics: (v) => setState(() => _ignoreClientStatistics = v), + child: ListView( + controller: _scrollController, + children: [ + if (!_nameValid || !_identifiersValid || !_dnsCacheValid) _Errors( + nameValid: _nameValid, + identifiersValid: _identifiersValid, + dnsCacheValid: _dnsCacheValid + ), + ClientForm( + isFullScreen: false, + client: widget.client, + nameController: nameController, + identifiersControllers: identifiersControllers, + selectedTags: selectedTags, + useGlobalSettingsFiltering: useGlobalSettingsFiltering, + enableFiltering: enableFiltering, + enableParentalControl: enableParentalControl, + enableSafeBrowsing: enableSafeBrowsing, + enableSafeSearch: enableSafeSearch, + safeSearch: safeSearch, + blockedServices: blockedServices, + updateBlockedServices: (v) => setState(() => blockedServices = v), + upstreamServers: upstreamServers, + updateUpstreamServers: (v) => setState(() => upstreamServers = v), + defaultSafeSearch: defaultSafeSearch, + useGlobalSettingsServices: useGlobalSettingsServices, + updateSelectedTags: (v) => setState(() => selectedTags = v), + updateIdentifiersControllers: (v) => setState(() => identifiersControllers = v), + enableDisableGlobalSettingsFiltering: enableDisableGlobalSettingsFiltering, + updateEnableFiltering: (v) => setState(() => enableFiltering = v), + updateEnableParentalControl: (v) => setState(() => enableParentalControl = v), + updateEnableSafeBrowsing: (v) => setState(() => enableSafeBrowsing = v), + updateEnableSafeSearch: (v) => setState(() => enableSafeSearch = v), + updateSafeSearch: (v) => setState(() => safeSearch = v), + updateUseGlobalSettingsServices: (v) => setState(() => useGlobalSettingsServices = v), + ignoreClientQueryLog: _ignoreClientQueryLog, + ignoreClientStatistics: _ignoreClientStatistics, + updateIgnoreClientQueryLog: (v) => setState(() => _ignoreClientQueryLog = v), + updateIgnoreClientStatistics: (v) => setState(() => _ignoreClientStatistics = v), + enableDnsCache: _enableDnsCache, + updateEnableDnsCache: (v) => setState(() => _enableDnsCache = v), + dnsCacheField: _dnsCacheField, + dnsCacheError: _dnsCacheError, + updateDnsCacheError: (v) => setState(() => _dnsCacheError = v) + ), + ], ), ) ], @@ -306,3 +366,56 @@ class _ClientScreenState extends State { } } +class _Errors extends StatelessWidget { + final bool nameValid; + final bool identifiersValid; + final bool dnsCacheValid; + + const _Errors({ + required this.nameValid, + required this.identifiersValid, + required this.dnsCacheValid, + }); + + @override + Widget build(BuildContext context) { + return Card( + elevation: 0, + color: Colors.red.withOpacity(0.2), + margin: const EdgeInsets.all(16), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + AppLocalizations.of(context)!.errors, + style: const TextStyle( + fontSize: 18 + ), + ), + const SizedBox(height: 8), + if (!nameValid) Text( + "● ${AppLocalizations.of(context)!.nameInvalid}", + style: const TextStyle( + fontSize: 14 + ), + ), + if (!identifiersValid) Text( + "● ${AppLocalizations.of(context)!.oneIdentifierRequired}", + style: const TextStyle( + fontSize: 14 + ), + ), + if (!dnsCacheValid) Text( + "● ${AppLocalizations.of(context)!.dnsCacheNumber}", + style: const TextStyle( + fontSize: 14 + ), + ), + ], + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/screens/clients/client/client_screen_functions.dart b/lib/screens/clients/client/client_screen_functions.dart index 1f292c6..f0338a1 100644 --- a/lib/screens/clients/client/client_screen_functions.dart +++ b/lib/screens/clients/client/client_screen_functions.dart @@ -74,22 +74,6 @@ void openSafeSearchModal({ ); } -bool checkValidValues({ - required TextEditingController nameController, - required List identifiersControllers -}) { - if ( - nameController.text != '' && - identifiersControllers.isNotEmpty && - identifiersControllers[0].controller.text != '' - ) { - return true; - } - else { - return false; - } -} - void openClientFormModal({ required BuildContext context, required double width, @@ -123,4 +107,10 @@ void openClientFormModal({ onDelete: onDelete, ), ); +} + +bool validateNumber(String value) { + if (value == "") return true; + final regexp = RegExp(r'^\d+$'); + return regexp.hasMatch(value); } \ No newline at end of file diff --git a/lib/screens/clients/client/identifiers_section.dart b/lib/screens/clients/client/identifiers_section.dart index 91a56eb..c5f9d4c 100644 --- a/lib/screens/clients/client/identifiers_section.dart +++ b/lib/screens/clients/client/identifiers_section.dart @@ -11,11 +11,11 @@ class IdentifiersSection extends StatefulWidget { final void Function() onCheckValidValues; const IdentifiersSection({ - Key? key, + super.key, required this.identifiersControllers, required this.onUpdateIdentifiersControllers, required this.onCheckValidValues - }) : super(key: key); + }); @override State createState() => _IdentifiersSectionState(); @@ -34,11 +34,11 @@ class _IdentifiersSectionState extends State { SectionLabel( label: AppLocalizations.of(context)!.identifiers, padding: const EdgeInsets.only( - left: 24, right: 24, top: 24, bottom: 12 + left: 16, right: 16, top: 24, bottom: 12 ) ), Padding( - padding: const EdgeInsets.only(right: 20), + padding: const EdgeInsets.only(right: 10), child: IconButton( onPressed: () => widget.onUpdateIdentifiersControllers([ ...widget.identifiersControllers, @@ -54,7 +54,7 @@ class _IdentifiersSectionState extends State { ), if (widget.identifiersControllers.isNotEmpty) ...widget.identifiersControllers.map((controller) => Padding( padding: const EdgeInsets.only( - top: 12, bottom: 12, left: 24, right: 20 + top: 12, bottom: 12, left: 16, right: 10 ), child: Row( crossAxisAlignment: CrossAxisAlignment.center, @@ -75,9 +75,9 @@ class _IdentifiersSectionState extends State { ), ), ), - const SizedBox(width: 16), + const SizedBox(width: 12), Padding( - padding: const EdgeInsets.only(bottom: 25), + padding: const EdgeInsets.only(bottom: 24), child: IconButton( onPressed: () => widget.onUpdateIdentifiersControllers( widget.identifiersControllers.where((e) => e.id != controller.id).toList() @@ -87,7 +87,7 @@ class _IdentifiersSectionState extends State { ) ], ), - )).toList(), + )), if (widget.identifiersControllers.isEmpty) Container( padding: const EdgeInsets.symmetric(vertical: 16), child: Text( diff --git a/lib/screens/clients/client/settings_tile.dart b/lib/screens/clients/client/settings_tile.dart index c2d6ede..9b26b2c 100644 --- a/lib/screens/clients/client/settings_tile.dart +++ b/lib/screens/clients/client/settings_tile.dart @@ -7,12 +7,12 @@ class SettingsTile extends StatelessWidget { final bool useGlobalSettingsFiltering; const SettingsTile({ - Key? key, + super.key, required this.label, required this.value, this.onChange, required this.useGlobalSettingsFiltering - }) : super(key: key); + }); @override Widget build(BuildContext context) { @@ -23,10 +23,7 @@ class SettingsTile extends StatelessWidget { ? value != null ? () => onChange!(!value!) : null : null, child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 42, - vertical: 5 - ), + padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 6), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ diff --git a/lib/screens/clients/client/tags_section.dart b/lib/screens/clients/client/tags_section.dart index a6fa67c..8d63a0e 100644 --- a/lib/screens/clients/client/tags_section.dart +++ b/lib/screens/clients/client/tags_section.dart @@ -7,10 +7,10 @@ class TagsSection extends StatelessWidget { final void Function(List) onTagsSelected; const TagsSection({ - Key? key, + super.key, required this.selectedTags, required this.onTagsSelected - }) : super(key: key); + }); @override Widget build(BuildContext context) { @@ -24,7 +24,7 @@ class TagsSection extends StatelessWidget { ) , child: Padding( padding: const EdgeInsets.symmetric( - vertical: 0, horizontal: 24 + vertical: 0, horizontal: 16 ), child: Row( children: [ diff --git a/lib/screens/clients/client/upstream_servers_section.dart b/lib/screens/clients/client/upstream_servers_section.dart index 97c8974..56fb7aa 100644 --- a/lib/screens/clients/client/upstream_servers_section.dart +++ b/lib/screens/clients/client/upstream_servers_section.dart @@ -11,11 +11,11 @@ class UpstreamServersSection extends StatefulWidget { final void Function(List) onUpdateUpstreamServers; const UpstreamServersSection({ - Key? key, + super.key, required this.upstreamServers, required this.onCheckValidValues, required this.onUpdateUpstreamServers - }) : super(key: key); + }); @override State createState() => _UpstreamServersSectionState(); @@ -33,10 +33,10 @@ class _UpstreamServersSectionState extends State { children: [ SectionLabel( label: AppLocalizations.of(context)!.upstreamServers, - padding: const EdgeInsets.all(24), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), ), Padding( - padding: const EdgeInsets.only(right: 20), + padding: const EdgeInsets.only(right: 12), child: IconButton( onPressed: () => setState(() => widget.upstreamServers.add( ControllerListItem( @@ -50,7 +50,7 @@ class _UpstreamServersSectionState extends State { ], ), if (widget.upstreamServers.isNotEmpty) ...widget.upstreamServers.map((controller) => Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), + padding: const EdgeInsets.only(left: 16, right: 12), child: Padding( padding: const EdgeInsets.only(bottom: 20), child: Row( @@ -71,7 +71,7 @@ class _UpstreamServersSectionState extends State { ), ), ), - const SizedBox(width: 16), + const SizedBox(width: 12), IconButton( onPressed: () => widget.onUpdateUpstreamServers( widget.upstreamServers.where((e) => e.id != controller.id).toList() @@ -81,7 +81,7 @@ class _UpstreamServersSectionState extends State { ], ), ), - )).toList(), + )), if (widget.upstreamServers.isEmpty) Container( padding: const EdgeInsets.symmetric(vertical: 16), child: Column( diff --git a/lib/screens/clients/search_clients.dart b/lib/screens/clients/search_clients.dart index d8e1104..e5b1451 100644 --- a/lib/screens/clients/search_clients.dart +++ b/lib/screens/clients/search_clients.dart @@ -226,16 +226,16 @@ class _SearchClientsState extends State { itemCount: clientsScreen.length, padding: const EdgeInsets.only(bottom: 0), itemBuilder: (context, index) => OptionsMenu( - options: [ + options: (v) => [ MenuOption( icon: Icons.edit_rounded, title: AppLocalizations.of(context)!.edit, - action: (v) => openClientModal(v) + action: () => openClientModal(v) ), MenuOption( icon: Icons.delete_rounded, title: AppLocalizations.of(context)!.delete, - action: (v) => openDeleteModal(v) + action: () => openDeleteModal(v) ), ], value: clientsScreen[index], diff --git a/lib/screens/filters/filters_triple_column.dart b/lib/screens/filters/filters_triple_column.dart index 3401bac..7d15639 100644 --- a/lib/screens/filters/filters_triple_column.dart +++ b/lib/screens/filters/filters_triple_column.dart @@ -283,11 +283,11 @@ class FiltersTripleColumn extends StatelessWidget { ), ], child: OptionsMenu( - options: [ + options: (_) => [ MenuOption( title: AppLocalizations.of(context)!.copyClipboard, icon: Icons.copy_rounded, - action: (_) => copyToClipboard( + action: () => copyToClipboard( value: filteringProvider.filtering!.userRules[index], successMessage: AppLocalizations.of(context)!.copiedClipboard, ) diff --git a/lib/screens/filters/list_options_menu.dart b/lib/screens/filters/list_options_menu.dart index 32e4610..c35d080 100644 --- a/lib/screens/filters/list_options_menu.dart +++ b/lib/screens/filters/list_options_menu.dart @@ -146,7 +146,7 @@ class ListOptionsMenu extends StatelessWidget { color: Colors.transparent, child: InkWell( child: OptionsMenu( - options: [ + options: (_) => [ MenuOption( title: list.enabled == true ? AppLocalizations.of(context)!.disable @@ -154,12 +154,12 @@ class ListOptionsMenu extends StatelessWidget { icon: list.enabled == true ? Icons.gpp_bad_rounded : Icons.verified_user_rounded, - action: (_) => enableDisable() + action: () => enableDisable() ), MenuOption( title: AppLocalizations.of(context)!.copyListUrl, icon: Icons.copy_rounded, - action: (_) => copyToClipboard( + action: () => copyToClipboard( value: list.url, successMessage: AppLocalizations.of(context)!.listUrlCopied ) @@ -167,12 +167,12 @@ class ListOptionsMenu extends StatelessWidget { MenuOption( title: AppLocalizations.of(context)!.openListUrl, icon: Icons.open_in_browser_rounded, - action: (_) => openUrl(list.url) + action: () => openUrl(list.url) ), MenuOption( title: AppLocalizations.of(context)!.selectionMode, icon: Icons.check_rounded, - action: (_) => Future.delayed( + action: () => Future.delayed( const Duration(milliseconds: 0), () => openSelectionMode() ) diff --git a/lib/screens/home/chart.dart b/lib/screens/home/chart.dart index ba97f79..0093ae8 100644 --- a/lib/screens/home/chart.dart +++ b/lib/screens/home/chart.dart @@ -1,17 +1,20 @@ 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/line_chart.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; -class HomeChart extends StatelessWidget { +class HomeChart extends StatefulWidget { final List data; final String label; final String primaryValue; final String secondaryValue; final Color color; final int hoursInterval; + final void Function() onTapTitle; + final bool isDesktop; const HomeChart({ super.key, @@ -20,20 +23,29 @@ class HomeChart extends StatelessWidget { required this.primaryValue, required this.secondaryValue, required this.color, - required this.hoursInterval + required this.hoursInterval, + required this.onTapTitle, + required this.isDesktop, }); + @override + State createState() => _HomeChartState(); +} + +class _HomeChartState extends State { + bool _isHover = false; + @override Widget build(BuildContext context) { final appConfigProvider = Provider.of(context); - final bool isEmpty = data.every((i) => i == 0); + final bool isEmpty = widget.data.every((i) => i == 0); if (!(appConfigProvider.hideZeroValues == true && isEmpty == true)) { List dateTimes = []; - DateTime currentDate = DateTime.now().subtract(Duration(hours: hoursInterval*data.length+1)); - for (var i = 0; i < data.length; i++) { - currentDate = currentDate.add(Duration(hours: hoursInterval)); + DateTime currentDate = DateTime.now().subtract(Duration(hours: widget.hoursInterval*widget.data.length+1)); + for (var i = 0; i < widget.data.length; i++) { + currentDate = currentDate.add(Duration(hours: widget.hoursInterval)); dateTimes.add(currentDate); } @@ -49,60 +61,87 @@ class HomeChart extends StatelessWidget { ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: isEmpty + ? CrossAxisAlignment.center + : CrossAxisAlignment.start, children: [ Flexible( - child: Text( - label, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.onSurface + child: MouseRegion( + cursor: SystemMouseCursors.click, + onEnter: (_) => setState(() => _isHover = true), + onExit: (_) => setState(() => _isHover = false), + child: GestureDetector( + onTapDown: (_) => setState(() => _isHover = true), + onTapUp: (_) => setState(() => _isHover = false), + onTap: !isEmpty ? () => widget.onTapTitle () : null, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Flexible( + child: Text( + widget.label, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w500, + color: _isHover && !isEmpty + ? Theme.of(context).colorScheme.onSurfaceVariant + : Theme.of(context).colorScheme.onSurface, + ), + ), + ), + if (!isEmpty) ...[ + const SizedBox(width: 4), + Icon( + Icons.chevron_right_rounded, + size: 20, + color: _isHover && !isEmpty + ? Theme.of(context).colorScheme.onSurfaceVariant + : Theme.of(context).colorScheme.onSurface, + ) + ], + ], + ), ), ), ), - !isEmpty - ? Column( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Text( - primaryValue, - style: TextStyle( - color: color, - fontSize: 18, - fontWeight: FontWeight.w500 - ), - ), - Text( - secondaryValue, - style: TextStyle( - fontSize: 12, - color: color - ), - ) - ], + if (!isEmpty) Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + widget.primaryValue, + style: TextStyle( + color: widget.color, + fontSize: 18, + fontWeight: FontWeight.w500 + ), + ), + Text( + widget.secondaryValue, + style: TextStyle( + fontSize: 12, + color: widget.color + ), ) - : Row( - children: [ - Text( - primaryValue, - style: TextStyle( - color: color, - fontSize: 18, - fontWeight: FontWeight.w500 - ), - ), - const SizedBox(width: 10), - Text( - "($secondaryValue)", - style: TextStyle( - fontSize: 12, - color: color - ), - ) - ], + ], + ), + if (isEmpty && !widget.isDesktop) Column( + children: [ + Icon( + Icons.show_chart_rounded, + color: Theme.of(context).colorScheme.onSurfaceVariant, + size: 16, + ), + Text( + AppLocalizations.of(context)!.noData, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), ) + ], + ) ], ), ), @@ -110,13 +149,35 @@ class HomeChart extends StatelessWidget { width: double.maxFinite, height: 150, child: CustomLineChart( - data: data, - color: color, + data: widget.data, + color: widget.color, dates: dateTimes, - daysInterval: hoursInterval == 24, + daysInterval: widget.hoursInterval == 24, context: context, ) ), + if (isEmpty && widget.isDesktop) SizedBox( + height: 150, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.show_chart_rounded, + color: Theme.of(context).colorScheme.onSurfaceVariant, + size: 30, + ), + const SizedBox(height: 20), + Text( + AppLocalizations.of(context)!.noDataChart, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16, + color: Theme.of(context).colorScheme.onSurfaceVariant + ), + ) + ], + ), + ) ], ), ), diff --git a/lib/screens/home/home.dart b/lib/screens/home/home.dart index 9e5de00..f34a186 100644 --- a/lib/screens/home/home.dart +++ b/lib/screens/home/home.dart @@ -14,6 +14,8 @@ import 'package:adguard_home_manager/screens/home/appbar.dart'; import 'package:adguard_home_manager/screens/home/fab.dart'; import 'package:adguard_home_manager/screens/home/chart.dart'; +import 'package:adguard_home_manager/providers/clients_provider.dart'; +import 'package:adguard_home_manager/providers/logs_provider.dart'; import 'package:adguard_home_manager/functions/number_format.dart'; import 'package:adguard_home_manager/constants/enums.dart'; import 'package:adguard_home_manager/providers/status_provider.dart'; @@ -38,6 +40,9 @@ class _HomeState extends State { withLoadingIndicator: statusProvider.serverStatus != null ? false : true ); + final clientsProvider = Provider.of(context, listen: false); + clientsProvider.fetchClients(updateLoading: false); + super.initState(); isVisible = true; @@ -61,6 +66,7 @@ class _HomeState extends State { Widget build(BuildContext context) { final statusProvider = Provider.of(context); final appConfigProvider = Provider.of(context); + final logsProvider = Provider.of(context); final width = MediaQuery.of(context).size.width; @@ -143,6 +149,15 @@ class _HomeState extends State { secondaryValue: "${doubleFormat(statusProvider.serverStatus!.stats.avgProcessingTime*1000, Platform.localeName)} ms", color: Colors.blue, hoursInterval: statusProvider.serverStatus!.stats.timeUnits == "days" ? 24 : 1, + onTapTitle: () { + logsProvider.setSelectedResultStatus( + value: "all", + refetch: true + ); + logsProvider.filterLogs(); + appConfigProvider.setSelectedScreen(2); + }, + isDesktop: width > 700, ), ), FractionallySizedBox( @@ -154,6 +169,14 @@ class _HomeState extends State { secondaryValue: "${statusProvider.serverStatus!.stats.numDnsQueries > 0 ? doubleFormat((statusProvider.serverStatus!.stats.numBlockedFiltering/statusProvider.serverStatus!.stats.numDnsQueries)*100, Platform.localeName) : 0}%", color: Colors.red, hoursInterval: statusProvider.serverStatus!.stats.timeUnits == "days" ? 24 : 1, + onTapTitle: () { + logsProvider.setSelectedResultStatus( + value: "blocked", + refetch: true + ); + appConfigProvider.setSelectedScreen(2); + }, + isDesktop: width > 700, ), ), FractionallySizedBox( @@ -165,6 +188,14 @@ class _HomeState extends State { secondaryValue: "${statusProvider.serverStatus!.stats.numDnsQueries > 0 ? doubleFormat((statusProvider.serverStatus!.stats.numReplacedSafebrowsing/statusProvider.serverStatus!.stats.numDnsQueries)*100, Platform.localeName) : 0}%", color: Colors.green, hoursInterval: statusProvider.serverStatus!.stats.timeUnits == "days" ? 24 : 1, + onTapTitle: () { + logsProvider.setSelectedResultStatus( + value: "blocked_safebrowsing", + refetch: true + ); + appConfigProvider.setSelectedScreen(2); + }, + isDesktop: width > 700, ), ), FractionallySizedBox( @@ -176,6 +207,15 @@ class _HomeState extends State { secondaryValue: "${statusProvider.serverStatus!.stats.numDnsQueries > 0 ? doubleFormat((statusProvider.serverStatus!.stats.numReplacedParental/statusProvider.serverStatus!.stats.numDnsQueries)*100, Platform.localeName) : 0}%", color: Colors.orange, hoursInterval: statusProvider.serverStatus!.stats.timeUnits == "days" ? 24 : 1, + onTapTitle: () { + logsProvider.setSelectedResultStatus( + value: "blocked_parental", + refetch: true + ); + logsProvider.filterLogs(); + appConfigProvider.setSelectedScreen(2); + }, + isDesktop: width > 700, ), ), ], diff --git a/lib/screens/home/top_items/row_item.dart b/lib/screens/home/top_items/row_item.dart index 569d30c..b323eb0 100644 --- a/lib/screens/home/top_items/row_item.dart +++ b/lib/screens/home/top_items/row_item.dart @@ -16,7 +16,7 @@ class RowItem extends StatefulWidget { final bool clients; final bool showColor; final String? unit; - final List options; + final List Function(dynamic) options; final void Function(dynamic)? onTapEntry; const RowItem({ diff --git a/lib/screens/home/top_items/top_items_lists.dart b/lib/screens/home/top_items/top_items_lists.dart index 7f230fb..758a0b6 100644 --- a/lib/screens/home/top_items/top_items_lists.dart +++ b/lib/screens/home/top_items/top_items_lists.dart @@ -6,6 +6,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/screens/home/top_items/top_items_section.dart'; +import 'package:adguard_home_manager/providers/clients_provider.dart'; import 'package:adguard_home_manager/functions/number_format.dart'; import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/classes/process_modal.dart'; @@ -30,6 +31,7 @@ class TopItemsLists extends StatelessWidget { final statusProvider = Provider.of(context); final appConfigProvider = Provider.of(context); final logsProvider = Provider.of(context); + final clientsProvider = Provider.of(context); List bottom = [ Padding( @@ -96,10 +98,53 @@ class TopItemsLists extends StatelessWidget { } } - void copyValueClipboard(value) { + void copyValueClipboard(dynamic value) { copyToClipboard(value: value, successMessage: AppLocalizations.of(context)!.copiedClipboard); } + void blockUnblockClient(dynamic client) async { + final currentList = clientsProvider.checkClientList(client); + final newList = currentList == AccessSettingsList.allowed || currentList == null + ? AccessSettingsList.disallowed + : AccessSettingsList.allowed; + + ProcessModal processModal = ProcessModal(); + processModal.open( + currentList == AccessSettingsList.allowed || currentList == null + ? AppLocalizations.of(context)!.blockingClient + : AppLocalizations.of(context)!.unblockingClient + ); + + final result = await clientsProvider.addClientList(client, newList); + if (!context.mounted) return; + + processModal.close(); + + if (result.successful == true) { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.clientAddedSuccessfully, + color: Colors.green + ); + } + else if (result.successful == false && result.content == 'client_another_list') { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.clientAnotherList, + color: Colors.red + ); + } + else { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: newList == AccessSettingsList.allowed || newList == AccessSettingsList.disallowed + ? AppLocalizations.of(context)!.clientNotRemoved + : AppLocalizations.of(context)!.domainNotAdded, + color: Colors.red + ); + } + } + return Column( children: order.asMap().entries.map((item) { switch (item.value) { @@ -113,16 +158,16 @@ class TopItemsLists extends StatelessWidget { withChart: true, withProgressBar: true, buildValue: (v) => v.toString(), - menuOptions: [ + menuOptions: (v) => [ MenuOption( title: AppLocalizations.of(context)!.blockDomain, icon: Icons.block_rounded, - action: (v) => blockUnblock(domain: v.toString(), newStatus: 'block') + action: () => blockUnblock(domain: v.toString(), newStatus: 'block') ), MenuOption( title: AppLocalizations.of(context)!.copyClipboard, icon: Icons.copy_rounded, - action: copyValueClipboard + action: () => copyValueClipboard(v) ), ], onTapEntry: (v) => filterDomainLogs(value: v.toString()), @@ -141,16 +186,16 @@ class TopItemsLists extends StatelessWidget { withChart: true, withProgressBar: true, buildValue: (v) => v.toString(), - menuOptions: [ + menuOptions: (v) => [ MenuOption( title: AppLocalizations.of(context)!.unblockDomain, icon: Icons.check_rounded, - action: (v) => blockUnblock(domain: v, newStatus: 'unblock') + action: () => blockUnblock(domain: v, newStatus: 'unblock') ), MenuOption( title: AppLocalizations.of(context)!.copyClipboard, icon: Icons.copy_rounded, - action: copyValueClipboard + action: () => copyValueClipboard(v) ) ], onTapEntry: (v) => filterDomainLogs(value: v), @@ -169,12 +214,21 @@ class TopItemsLists extends StatelessWidget { withChart: true, withProgressBar: true, buildValue: (v) => v.toString(), - menuOptions: [ + menuOptions: (v) => [ + if (clientsProvider.clients?.clientsAllowedBlocked != null) MenuOption( + title: clientsProvider.checkClientList(v) == AccessSettingsList.allowed || clientsProvider.checkClientList(v) == null + ? AppLocalizations.of(context)!.blockClient + : AppLocalizations.of(context)!.unblockClient, + icon: clientsProvider.checkClientList(v) == AccessSettingsList.allowed || clientsProvider.checkClientList(v) == null + ? Icons.block_rounded + : Icons.check_rounded, + action: () => blockUnblockClient(v) + ), MenuOption( title: AppLocalizations.of(context)!.copyClipboard, icon: Icons.copy_rounded, - action: copyValueClipboard - ) + action: () => copyValueClipboard(v) + ), ], onTapEntry: (v) => filterClientLogs(value: v), ), @@ -193,11 +247,11 @@ class TopItemsLists extends StatelessWidget { withChart: true, withProgressBar: true, buildValue: (v) => v.toString(), - menuOptions: [ + menuOptions: (v) => [ MenuOption( title: AppLocalizations.of(context)!.copyClipboard, icon: Icons.copy_rounded, - action: copyValueClipboard + action: () => copyValueClipboard(v) ) ], ), @@ -217,11 +271,11 @@ class TopItemsLists extends StatelessWidget { withChart: false, withProgressBar: false, buildValue: (v) => "${doubleFormat(v*1000, Platform.localeName)} ms", - menuOptions: [ + menuOptions: (v) => [ MenuOption( title: AppLocalizations.of(context)!.copyClipboard, icon: Icons.copy_rounded, - action: copyValueClipboard + action: () => copyValueClipboard(v) ) ], ), diff --git a/lib/screens/home/top_items/top_items_screen.dart b/lib/screens/home/top_items/top_items_screen.dart index f2f9b4d..1070953 100644 --- a/lib/screens/home/top_items/top_items_screen.dart +++ b/lib/screens/home/top_items/top_items_screen.dart @@ -24,7 +24,7 @@ class TopItemsScreen extends StatefulWidget { final List> data; final bool withProgressBar; final String Function(dynamic) buildValue; - final List options; + final List Function(dynamic) options; final void Function(dynamic)? onTapEntry; final bool isFullscreen; @@ -263,7 +263,7 @@ class _TopItemsScreenState extends State { class _Content extends StatelessWidget { final List> screenData; final bool? isClient; - final List options; + final List Function(dynamic) options; final bool withProgressBar; final void Function(dynamic)? onTapEntry; final String Function(dynamic) buildValue; diff --git a/lib/screens/home/top_items/top_items_section.dart b/lib/screens/home/top_items/top_items_section.dart index f084b3f..bc021cf 100644 --- a/lib/screens/home/top_items/top_items_section.dart +++ b/lib/screens/home/top_items/top_items_section.dart @@ -21,7 +21,7 @@ class TopItemsSection extends StatefulWidget { final bool withChart; final bool withProgressBar; final String Function(dynamic) buildValue; - final List menuOptions; + final List Function(dynamic) menuOptions; final void Function(dynamic)? onTapEntry; const TopItemsSection({ @@ -350,7 +350,7 @@ class _ItemsList extends StatelessWidget { final HomeTopItems type; final bool showChart; final String Function(dynamic) buildValue; - final List menuOptions; + final List Function(dynamic) menuOptions; final void Function(dynamic)? onTapEntry; const _ItemsList({ diff --git a/lib/screens/logs/filters/filter_status_modal.dart b/lib/screens/logs/filters/filter_status_modal.dart index 88f782b..c08d9a8 100644 --- a/lib/screens/logs/filters/filter_status_modal.dart +++ b/lib/screens/logs/filters/filter_status_modal.dart @@ -34,7 +34,7 @@ class _FilterStatusModalState extends State { final logsProvider = Provider.of(context); void apply() async { - logsProvider.setSelectedResultStatus(selectedResultStatus); + logsProvider.setSelectedResultStatus(value: selectedResultStatus); Navigator.pop(context); } diff --git a/lib/screens/logs/filters/logs_filters_modal.dart b/lib/screens/logs/filters/logs_filters_modal.dart index 5e7e746..aec6b27 100644 --- a/lib/screens/logs/filters/logs_filters_modal.dart +++ b/lib/screens/logs/filters/logs_filters_modal.dart @@ -262,13 +262,13 @@ class _FiltersList extends StatelessWidget { FilterChip( selected: logsProvider.selectedResultStatus == "all", label: Text(AppLocalizations.of(context)!.all), - onSelected: (_) => logsProvider.setSelectedResultStatus("all") + onSelected: (_) => logsProvider.setSelectedResultStatus(value: "all") ), FilterChip( selected: logsProvider.selectedResultStatus == "processed" || logsProvider.selectedResultStatus == "whitelisted", label: Text(AppLocalizations.of(context)!.allowed), - onSelected: (_) => logsProvider.setSelectedResultStatus("processed") + onSelected: (_) => logsProvider.setSelectedResultStatus(value: "processed") ), FilterChip( selected: logsProvider.selectedResultStatus == "blocked" || @@ -276,7 +276,7 @@ class _FiltersList extends StatelessWidget { logsProvider.selectedResultStatus == "blocked_parental" || logsProvider.selectedResultStatus == "safe_search", label: Text(AppLocalizations.of(context)!.blocked), - onSelected: (_) => logsProvider.setSelectedResultStatus("blocked") + onSelected: (_) => logsProvider.setSelectedResultStatus(value: "blocked") ), ], ), diff --git a/lib/screens/logs/log_tile.dart b/lib/screens/logs/log_tile.dart index af130e2..f5db5ee 100644 --- a/lib/screens/logs/log_tile.dart +++ b/lib/screens/logs/log_tile.dart @@ -128,7 +128,7 @@ class LogTile extends StatelessWidget { child: OptionsMenu( onTap: (_) => onLogTap(log), borderRadius: BorderRadius.circular(28), - options: [ + options: (_) => [ if (log.question.name != null) MenuOption( title: domainBlocked == true ? AppLocalizations.of(context)!.unblockDomain @@ -136,15 +136,15 @@ class LogTile extends StatelessWidget { icon: domainBlocked == true ? Icons.check_rounded : Icons.block_rounded, - action: (_) => blockUnblock( + action: () => blockUnblock( domain: log.question.name!, newStatus: domainBlocked == true ? 'unblock' : 'block' ) ), - MenuOption( + if (log.question.name != null) MenuOption( title: AppLocalizations.of(context)!.copyClipboard, icon: Icons.copy_rounded, - action: (v) => copyToClipboard(value: v, successMessage: AppLocalizations.of(context)!.copiedClipboard) + action: () => copyToClipboard(value: log.question.name!, successMessage: AppLocalizations.of(context)!.copiedClipboard) ) ], child: Container( @@ -306,7 +306,7 @@ class LogTile extends StatelessWidget { color: Colors.transparent, child: OptionsMenu( onTap: (_) => onLogTap(log), - options: [ + options: (_) => [ if (log.question.name != null) MenuOption( title: domainBlocked == true ? AppLocalizations.of(context)!.unblockDomain @@ -314,7 +314,7 @@ class LogTile extends StatelessWidget { icon: domainBlocked == true ? Icons.check_rounded : Icons.block_rounded, - action: (_) => blockUnblock( + action: () => blockUnblock( domain: log.question.name!, newStatus: domainBlocked == true ? 'unblock' : 'block' ) @@ -322,7 +322,7 @@ class LogTile extends StatelessWidget { if (log.question.name != null) MenuOption( title: AppLocalizations.of(context)!.copyClipboard, icon: Icons.copy_rounded, - action: (_) => copyToClipboard( + action: () => copyToClipboard( value: log.question.name!, successMessage: AppLocalizations.of(context)!.copiedClipboard ) diff --git a/lib/screens/logs/logs_list.dart b/lib/screens/logs/logs_list.dart index ac0596a..ced94ca 100644 --- a/lib/screens/logs/logs_list.dart +++ b/lib/screens/logs/logs_list.dart @@ -22,11 +22,11 @@ class LogsListWidget extends StatefulWidget { final void Function(Log) onLogSelected; const LogsListWidget({ - Key? key, + super.key, required this.twoColumns, required this.selectedLog, required this.onLogSelected, - }) : super(key: key); + }); @override State createState() => _LogsListWidgetState(); diff --git a/lib/screens/logs/logs_list_appbar.dart b/lib/screens/logs/logs_list_appbar.dart index f467d26..8aeb752 100644 --- a/lib/screens/logs/logs_list_appbar.dart +++ b/lib/screens/logs/logs_list_appbar.dart @@ -256,7 +256,7 @@ class LogsListAppBar extends StatelessWidget { clients: logsProvider.appliedFilters.clients ) ); - logsProvider.setSelectedResultStatus('all'); + logsProvider.setSelectedResultStatus(value: 'all'); logsProvider.fetchLogs( inOffset: 0, responseStatus: 'all' diff --git a/lib/screens/settings/dns/dns_server_settings.dart b/lib/screens/settings/dns/dns_server_settings.dart index ba04422..b13f593 100644 --- a/lib/screens/settings/dns/dns_server_settings.dart +++ b/lib/screens/settings/dns/dns_server_settings.dart @@ -156,7 +156,7 @@ class _DnsServerSettingsScreenState extends State { "blocking_mode": blockingMode, "blocking_ipv4": ipv4controller.text, "blocking_ipv6": ipv6controller.text, - "blocked_response_ttl": int.parse(_ttlController.text) + "blocked_response_ttl": int.tryParse(_ttlController.text) }); processModal.close(); diff --git a/lib/services/api_client.dart b/lib/services/api_client.dart index c2e1fca..b45622a 100644 --- a/lib/services/api_client.dart +++ b/lib/services/api_client.dart @@ -39,10 +39,17 @@ class ApiClientV2 { Future getServerVersion() async { final result = await HttpRequestClient.get(urlPath: '/status', server: server); if (result.successful == true) { - return ApiResponse( - successful: true, - content: jsonDecode(result.body!)['version'] - ); + try { + return ApiResponse( + successful: true, + content: jsonDecode(result.body!)['version'] + ); + } on FormatException { + return const ApiResponse(successful: false); + } catch (e, stackTrace) { + Sentry.captureException(e, stackTrace: stackTrace); + return const ApiResponse(successful: false); + } } else { return const ApiResponse(successful: false); diff --git a/lib/widgets/options_menu.dart b/lib/widgets/options_menu.dart index 861edab..ad91b33 100644 --- a/lib/widgets/options_menu.dart +++ b/lib/widgets/options_menu.dart @@ -11,7 +11,7 @@ import 'package:adguard_home_manager/models/menu_option.dart'; class OptionsMenu extends StatelessWidget { final Widget child; - final List options; + final List Function(dynamic) options; final dynamic value; final BorderRadius? borderRadius; final void Function(dynamic)? onTap; @@ -40,11 +40,11 @@ class OptionsMenu extends StatelessWidget { return Material( color: Colors.transparent, child: ContextMenuArea( - builder: (context) => options.map((opt) => CustomListTile( + builder: (context) => options(value).map((opt) => CustomListTile( title: opt.title, icon: opt.icon, onTap: () { - opt.action(value); + opt.action(); Navigator.pop(context); }, )).toList(), @@ -64,7 +64,7 @@ class OptionsMenu extends StatelessWidget { } class _OptionsModal extends StatelessWidget { - final List options; + final List Function(dynamic) options; final dynamic value; const _OptionsModal({ @@ -98,12 +98,12 @@ class _OptionsModal extends StatelessWidget { ), child: SingleChildScrollView( child: Wrap( - children: options.map((opt) => CustomListTileDialog( + children: options(value).map((opt) => CustomListTileDialog( title: opt.title, icon: opt.icon, onTap: () { Navigator.pop(context); - opt.action(value); + opt.action(); }, )).toList() ), diff --git a/pubspec.yaml b/pubspec.yaml index 9bd3484..5edc2c7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 2.13.0+117 +version: 2.14.0+119 environment: sdk: '>=2.18.1 <3.0.0'