Merge branch 'beta'

This commit is contained in:
Juan Gilsanz Polo 2023-12-20 15:51:26 +01:00
commit 24881bc1e2
86 changed files with 707 additions and 345 deletions

0
ios/.gitignore vendored Normal file → Executable file
View file

0
ios/Flutter/AppFrameworkInfo.plist Normal file → Executable file
View file

0
ios/Flutter/Debug.xcconfig Normal file → Executable file
View file

0
ios/Flutter/Release.xcconfig Normal file → Executable file
View file

0
ios/Podfile Normal file → Executable file
View file

0
ios/Podfile.lock Normal file → Executable file
View file

41
ios/Runner.xcodeproj/project.pbxproj Normal file → Executable file
View file

@ -68,7 +68,6 @@
65533F0C0783FDE34AE79B0A /* Pods-Runner.release.xcconfig */, 65533F0C0783FDE34AE79B0A /* Pods-Runner.release.xcconfig */,
69C2CC4A6DE17506FC5C0F13 /* Pods-Runner.profile.xcconfig */, 69C2CC4A6DE17506FC5C0F13 /* Pods-Runner.profile.xcconfig */,
); );
name = Pods;
path = Pods; path = Pods;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
@ -156,6 +155,7 @@
97C146E61CF9000F007C117D /* Project object */ = { 97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1430; LastUpgradeCheck = 1430;
ORGANIZATIONNAME = ""; ORGANIZATIONNAME = "";
TargetAttributes = { TargetAttributes = {
@ -325,6 +325,7 @@
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES;
@ -335,6 +336,7 @@
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO; ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu99; GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES; GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
@ -343,7 +345,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0; IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos; SUPPORTED_PLATFORMS = iphoneos;
@ -361,15 +363,22 @@
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 38Z3B9TJTR; DEVELOPMENT_TEAM = 38Z3B9TJTR;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "AdGuard Home";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
PRODUCT_BUNDLE_IDENTIFIER = com.jgeek00.adguard_home_manager; PRODUCT_BUNDLE_IDENTIFIER = com.jgeek00.adguardHomeManager;
PRODUCT_NAME = "$(TARGET_NAME)"; 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_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic"; VERSIONING_SYSTEM = "apple-generic";
}; };
name = Profile; name = Profile;
@ -397,6 +406,7 @@
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES;
@ -407,6 +417,7 @@
DEBUG_INFORMATION_FORMAT = dwarf; DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES; ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu99; GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO; GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES; GCC_NO_COMMON_BLOCKS = YES;
@ -421,7 +432,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0; IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = YES; MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos; SDKROOT = iphoneos;
@ -452,6 +463,7 @@
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES;
@ -462,6 +474,7 @@
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO; ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu99; GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES; GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
@ -470,7 +483,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0; IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos; SUPPORTED_PLATFORMS = iphoneos;
@ -490,16 +503,23 @@
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 38Z3B9TJTR; DEVELOPMENT_TEAM = 38Z3B9TJTR;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "AdGuard Home";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
PRODUCT_BUNDLE_IDENTIFIER = com.jgeek00.adguard_home_manager; PRODUCT_BUNDLE_IDENTIFIER = com.jgeek00.adguardHomeManager;
PRODUCT_NAME = "$(TARGET_NAME)"; 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_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic"; VERSIONING_SYSTEM = "apple-generic";
}; };
name = Debug; name = Debug;
@ -513,15 +533,22 @@
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 38Z3B9TJTR; DEVELOPMENT_TEAM = 38Z3B9TJTR;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "AdGuard Home";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
PRODUCT_BUNDLE_IDENTIFIER = com.jgeek00.adguard_home_manager; PRODUCT_BUNDLE_IDENTIFIER = com.jgeek00.adguardHomeManager;
PRODUCT_NAME = "$(TARGET_NAME)"; 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_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic"; VERSIONING_SYSTEM = "apple-generic";
}; };
name = Release; name = Release;

0
ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata generated Normal file → Executable file
View file

View file

0
ios/Runner.xcworkspace/contents.xcworkspacedata generated Normal file → Executable file
View file

View file

View file

0
ios/Runner/AppDelegate.swift Normal file → Executable file
View file

View file

View file

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 526 B

After

Width:  |  Height:  |  Size: 526 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1,005 B

After

Width:  |  Height:  |  Size: 1,005 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 773 B

After

Width:  |  Height:  |  Size: 773 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1,005 B

After

Width:  |  Height:  |  Size: 1,005 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2 KiB

After

Width:  |  Height:  |  Size: 2 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Before After
Before After

0
ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json vendored Normal file → Executable file
View file

0
ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png vendored Normal file → Executable file
View file

Before

Width:  |  Height:  |  Size: 68 B

After

Width:  |  Height:  |  Size: 68 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 70 B

After

Width:  |  Height:  |  Size: 70 B

Before After
Before After

0
ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json vendored Normal file → Executable file
View file

0
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png vendored Normal file → Executable file
View file

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Before After
Before After

0
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png vendored Normal file → Executable file
View file

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Before After
Before After

0
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png vendored Normal file → Executable file
View file

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Before After
Before After

0
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark.png vendored Normal file → Executable file
View file

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Before After
Before After

0
ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md vendored Normal file → Executable file
View file

0
ios/Runner/Base.lproj/LaunchScreen.storyboard Normal file → Executable file
View file

0
ios/Runner/Base.lproj/Main.storyboard Normal file → Executable file
View file

98
ios/Runner/Info.plist Normal file → Executable file
View file

@ -1,53 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>CFBundleDevelopmentRegion</key> <key>CADisableMinimumFrameDurationOnPhone</key>
<string>$(DEVELOPMENT_LANGUAGE)</string> <true/>
<key>CFBundleDisplayName</key> <key>CFBundleDevelopmentRegion</key>
<string>AdGuard Home Manager</string> <string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key> <key>CFBundleDisplayName</key>
<string>$(EXECUTABLE_NAME)</string> <string>AdGuard Home</string>
<key>CFBundleIdentifier</key> <key>CFBundleExecutable</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> <string>$(EXECUTABLE_NAME)</string>
<key>CFBundleInfoDictionaryVersion</key> <key>CFBundleIdentifier</key>
<string>6.0</string> <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleName</key> <key>CFBundleInfoDictionaryVersion</key>
<string>adguard_home_manager</string> <string>6.0</string>
<key>CFBundlePackageType</key> <key>CFBundleName</key>
<string>APPL</string> <string>adguardHomeManager</string>
<key>CFBundleShortVersionString</key> <key>CFBundlePackageType</key>
<string>$(FLUTTER_BUILD_NAME)</string> <string>APPL</string>
<key>CFBundleSignature</key> <key>CFBundleShortVersionString</key>
<string>????</string> <string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleVersion</key> <key>CFBundleSignature</key>
<string>$(FLUTTER_BUILD_NUMBER)</string> <string>????</string>
<key>LSRequiresIPhoneOS</key> <key>CFBundleVersion</key>
<true/> <string>$(FLUTTER_BUILD_NUMBER)</string>
<key>UILaunchStoryboardName</key> <key>LSRequiresIPhoneOS</key>
<string>LaunchScreen</string> <true/>
<key>UIMainStoryboardFile</key> <key>UIApplicationSupportsIndirectInputEvents</key>
<string>Main</string> <true/>
<key>UISupportedInterfaceOrientations</key> <key>UILaunchStoryboardName</key>
<array> <string>LaunchScreen</string>
<string>UIInterfaceOrientationPortrait</string> <key>UIMainStoryboardFile</key>
<string>UIInterfaceOrientationLandscapeLeft</string> <string>Main</string>
<string>UIInterfaceOrientationLandscapeRight</string> <key>UIStatusBarHidden</key>
</array> <false/>
<key>UISupportedInterfaceOrientations~ipad</key> <key>UISupportedInterfaceOrientations</key>
<array> <array>
<string>UIInterfaceOrientationPortrait</string> <string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string> <string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeRight</string>
<string>UIInterfaceOrientationLandscapeRight</string> </array>
</array> <key>UISupportedInterfaceOrientations~ipad</key>
<key>UIViewControllerBasedStatusBarAppearance</key> <array>
<false/> <string>UIInterfaceOrientationPortrait</string>
<key>CADisableMinimumFrameDurationOnPhone</key> <string>UIInterfaceOrientationPortraitUpsideDown</string>
<true/> <string>UIInterfaceOrientationLandscapeLeft</string>
<key>UIApplicationSupportsIndirectInputEvents</key> <string>UIInterfaceOrientationLandscapeRight</string>
<true/> </array>
<key>UIStatusBarHidden</key> <key>UIViewControllerBasedStatusBarAppearance</key>
<false/> <false/>
</dict> </dict>
</plist> </plist>

0
ios/Runner/Runner-Bridging-Header.h Normal file → Executable file
View file

View file

@ -713,5 +713,17 @@
"noFallbackDnsAdded": "No fallback DNS servers added.", "noFallbackDnsAdded": "No fallback DNS servers added.",
"blockedResponseTtl": "Blocked response TTL", "blockedResponseTtl": "Blocked response TTL",
"blockedResponseTtlDescription": "Specifies for how many seconds the clients should cache a filtered response", "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"
} }

View file

@ -713,5 +713,17 @@
"noFallbackDnsAdded": "No hay servidores DNS alternativos añadidos.", "noFallbackDnsAdded": "No hay servidores DNS alternativos añadidos.",
"blockedResponseTtl": "Respuesta TTL bloqueada", "blockedResponseTtl": "Respuesta TTL bloqueada",
"blockedResponseTtlDescription": "Especifica durante cuántos segundos los clientes deben almacenar en cache una respuesta filtrada", "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"
} }

View file

@ -89,6 +89,8 @@ class Client {
final SafeSearch? safeSearch; final SafeSearch? safeSearch;
final bool? ignoreQuerylog; final bool? ignoreQuerylog;
final bool? ignoreStatistics; final bool? ignoreStatistics;
final bool? upstreamsCacheEnabled;
final int? upstreamsCacheSize;
Client({ Client({
required this.name, required this.name,
@ -104,6 +106,8 @@ class Client {
required this.safeSearch, required this.safeSearch,
required this.ignoreQuerylog, required this.ignoreQuerylog,
required this.ignoreStatistics, required this.ignoreStatistics,
required this.upstreamsCacheEnabled,
required this.upstreamsCacheSize,
}); });
factory Client.fromJson(Map<String, dynamic> json) => Client( factory Client.fromJson(Map<String, dynamic> json) => Client(
@ -121,7 +125,9 @@ class Client {
? SafeSearch.fromJson(json["safe_search"]) ? SafeSearch.fromJson(json["safe_search"])
: null, : null,
ignoreQuerylog: json["ignore_querylog"], ignoreQuerylog: json["ignore_querylog"],
ignoreStatistics: json["ignore_statistics"] ignoreStatistics: json["ignore_statistics"],
upstreamsCacheEnabled: json["upstreams_cache_enabled"],
upstreamsCacheSize: json["upstreams_cache_size"]
); );
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
@ -138,5 +144,7 @@ class Client {
"use_global_settings": useGlobalSettings, "use_global_settings": useGlobalSettings,
"ignore_querylog": ignoreQuerylog, "ignore_querylog": ignoreQuerylog,
"ignore_statistics": ignoreStatistics, "ignore_statistics": ignoreStatistics,
"upstreams_cache_enabled": upstreamsCacheEnabled,
"upstreams_cache_size": upstreamsCacheSize
}; };
} }

View file

@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
class MenuOption { class MenuOption {
final IconData? icon; final IconData? icon;
final String title; final String title;
final void Function(dynamic) action; final void Function() action;
final bool? disabled; final bool? disabled;
const MenuOption({ const MenuOption({

View file

@ -190,6 +190,16 @@ class ClientsProvider with ChangeNotifier {
"blocked_hosts": clients!.clientsAllowedBlocked?.blockedHosts ?? [], "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) { if (type == AccessSettingsList.allowed) {
body['allowed_clients']!.add(item); 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<ApiResponse> removeClientList(String client, AccessSettingsList type) async { Future<ApiResponse> removeClientList(String client, AccessSettingsList type) async {
Map<String, List<String>> body = { Map<String, List<String>> body = {
"allowed_clients": clients!.clientsAllowedBlocked?.allowedClients ?? [], "allowed_clients": clients!.clientsAllowedBlocked?.allowedClients ?? [],

View file

@ -114,9 +114,15 @@ class LogsProvider with ChangeNotifier {
_offset = value; _offset = value;
} }
void setSelectedResultStatus(String value) { void setSelectedResultStatus({
required String value,
bool? refetch
}) {
_selectedResultStatus = value; _selectedResultStatus = value;
notifyListeners(); notifyListeners();
if (refetch = true) {
filterLogs();
}
} }
void setSearchText(String? value) { void setSearchText(String? value) {

View file

@ -28,11 +28,11 @@ class ActiveClientTile extends StatelessWidget {
return Padding( return Padding(
padding: const EdgeInsets.symmetric(horizontal: 12), padding: const EdgeInsets.symmetric(horizontal: 12),
child: OptionsMenu( child: OptionsMenu(
options: [ options: (_) => [
MenuOption( MenuOption(
icon: Icons.copy_rounded, icon: Icons.copy_rounded,
title: AppLocalizations.of(context)!.copyClipboard, title: AppLocalizations.of(context)!.copyClipboard,
action: (_) => copyToClipboard( action: () => copyToClipboard(
value: client.name != '' value: client.name != ''
? client.name! ? client.name!
: client.ip, : client.ip,
@ -99,11 +99,11 @@ class ActiveClientTile extends StatelessWidget {
} }
else { else {
return OptionsMenu( return OptionsMenu(
options: [ options: (_) => [
MenuOption( MenuOption(
icon: Icons.copy_rounded, icon: Icons.copy_rounded,
title: AppLocalizations.of(context)!.copyClipboard, title: AppLocalizations.of(context)!.copyClipboard,
action: (_) => copyToClipboard( action: () => copyToClipboard(
value: client.name != '' value: client.name != ''
? client.name! ? client.name!
: client.ip, : client.ip,

View file

@ -46,11 +46,11 @@ class _AddedClientTileState extends State<AddedClientTile> {
color: Colors.transparent, color: Colors.transparent,
borderRadius: BorderRadius.circular(28), borderRadius: BorderRadius.circular(28),
child: OptionsMenu( child: OptionsMenu(
options: [ options: (_) => [
MenuOption( MenuOption(
icon: Icons.copy_rounded, icon: Icons.copy_rounded,
title: AppLocalizations.of(context)!.copyClipboard, title: AppLocalizations.of(context)!.copyClipboard,
action: (_) => copyToClipboard( action: () => copyToClipboard(
value: widget.client.ids.toString().replaceAll(RegExp(r'^\[|\]$'), ''), value: widget.client.ids.toString().replaceAll(RegExp(r'^\[|\]$'), ''),
successMessage: AppLocalizations.of(context)!.copiedClipboard, successMessage: AppLocalizations.of(context)!.copiedClipboard,
) )
@ -164,16 +164,16 @@ class _AddedClientTileState extends State<AddedClientTile> {
} }
else { else {
return OptionsMenu( return OptionsMenu(
options: [ options: (_) => [
if (widget.onEdit != null) MenuOption( if (widget.onEdit != null) MenuOption(
title: AppLocalizations.of(context)!.seeDetails, title: AppLocalizations.of(context)!.seeDetails,
icon: Icons.file_open_rounded, icon: Icons.file_open_rounded,
action: (_) => widget.onEdit!(widget.client) action: () => widget.onEdit!(widget.client)
), ),
MenuOption( MenuOption(
icon: Icons.copy_rounded, icon: Icons.copy_rounded,
title: AppLocalizations.of(context)!.copyClipboard, title: AppLocalizations.of(context)!.copyClipboard,
action: (_) => copyToClipboard( action: () => copyToClipboard(
value: widget.client.ids.toString().replaceAll(RegExp(r'^\[|\]$'), ''), value: widget.client.ids.toString().replaceAll(RegExp(r'^\[|\]$'), ''),
successMessage: AppLocalizations.of(context)!.copiedClipboard, successMessage: AppLocalizations.of(context)!.copiedClipboard,
) )

View file

@ -22,7 +22,7 @@ class BlockedServicesSection extends StatelessWidget {
return Column( return Column(
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 24), padding: const EdgeInsets.symmetric(horizontal: 16),
child: Material( child: Material(
color: Theme.of(context).colorScheme.primary.withOpacity(0.1), color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
borderRadius: BorderRadius.circular(28), borderRadius: BorderRadius.circular(28),
@ -31,8 +31,8 @@ class BlockedServicesSection extends StatelessWidget {
borderRadius: BorderRadius.circular(28), borderRadius: BorderRadius.circular(28),
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 20, horizontal: 16,
vertical: 5 vertical: 6
), ),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -56,7 +56,7 @@ class BlockedServicesSection extends StatelessWidget {
), ),
), ),
), ),
const SizedBox(height: 10), const SizedBox(height: 12),
Material( Material(
color: Colors.transparent, color: Colors.transparent,
child: InkWell( child: InkWell(
@ -69,7 +69,7 @@ class BlockedServicesSection extends StatelessWidget {
: null, : null,
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
vertical: 8, horizontal: 24 vertical: 8, horizontal: 32
), ),
child: Row( child: Row(
children: [ children: [

View file

@ -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/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.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/tags_section.dart';
import 'package:adguard_home_manager/screens/clients/client/upstream_servers_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/custom_list_tile.dart';
import 'package:adguard_home_manager/widgets/section_label.dart'; import 'package:adguard_home_manager/widgets/section_label.dart';
@ -22,7 +20,6 @@ class ClientForm extends StatelessWidget {
final bool isFullScreen; final bool isFullScreen;
final Client? client; final Client? client;
final TextEditingController nameController; final TextEditingController nameController;
final void Function(bool) updateValidValues;
final List<ControllerListItem> identifiersControllers; final List<ControllerListItem> identifiersControllers;
final List<String> selectedTags; final List<String> selectedTags;
final bool useGlobalSettingsFiltering; final bool useGlobalSettingsFiltering;
@ -50,13 +47,17 @@ class ClientForm extends StatelessWidget {
final void Function(bool) updateIgnoreClientQueryLog; final void Function(bool) updateIgnoreClientQueryLog;
final bool ignoreClientStatistics; final bool ignoreClientStatistics;
final void Function(bool) updateIgnoreClientStatistics; 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({ const ClientForm({
super.key, super.key,
required this.isFullScreen, required this.isFullScreen,
required this.client, required this.client,
required this.nameController, required this.nameController,
required this.updateValidValues,
required this.identifiersControllers, required this.identifiersControllers,
required this.selectedTags, required this.selectedTags,
required this.useGlobalSettingsFiltering, required this.useGlobalSettingsFiltering,
@ -84,26 +85,24 @@ class ClientForm extends StatelessWidget {
required this.ignoreClientStatistics, required this.ignoreClientStatistics,
required this.updateIgnoreClientQueryLog, required this.updateIgnoreClientQueryLog,
required this.updateIgnoreClientStatistics, required this.updateIgnoreClientStatistics,
required this.enableDnsCache,
required this.updateEnableDnsCache,
required this.dnsCacheField,
required this.dnsCacheError,
required this.updateDnsCacheError,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ListView( return Column(
padding: const EdgeInsets.only(top: 0),
children: [ children: [
if (isFullScreen == true) const SizedBox(height: 24), const SizedBox(height: 8),
if (isFullScreen == false) const SizedBox(height: 6),
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 24), padding: const EdgeInsets.symmetric(horizontal: 16),
child: TextFormField( child: TextFormField(
enabled: client != null ? false : true, enabled: client != null ? false : true,
controller: nameController, controller: nameController,
onChanged: (_) => updateValidValues( onChanged: (_) => {},
checkValidValues(
identifiersControllers: identifiersControllers,
nameController: nameController
)
),
decoration: InputDecoration( decoration: InputDecoration(
prefixIcon: const Icon(Icons.badge_rounded), prefixIcon: const Icon(Icons.badge_rounded),
border: const OutlineInputBorder( border: const OutlineInputBorder(
@ -117,7 +116,7 @@ class ClientForm extends StatelessWidget {
), ),
SectionLabel( SectionLabel(
label: AppLocalizations.of(context)!.tags, label: AppLocalizations.of(context)!.tags,
padding: const EdgeInsets.all(24), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24),
), ),
TagsSection( TagsSection(
selectedTags: selectedTags, selectedTags: selectedTags,
@ -127,28 +126,17 @@ class ClientForm extends StatelessWidget {
identifiersControllers: identifiersControllers, identifiersControllers: identifiersControllers,
onUpdateIdentifiersControllers: (c) { onUpdateIdentifiersControllers: (c) {
updateIdentifiersControllers(c); updateIdentifiersControllers(c);
updateValidValues(
checkValidValues(
nameController: nameController,
identifiersControllers: identifiersControllers
)
);
}, },
onCheckValidValues: () => updateValidValues( onCheckValidValues: () => {}
checkValidValues(
identifiersControllers: identifiersControllers,
nameController: nameController
)
),
), ),
SectionLabel( SectionLabel(
label: AppLocalizations.of(context)!.settings, label: AppLocalizations.of(context)!.settings,
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
left: 24, right: 24, top: 12, bottom: 24 left: 16, right: 16, top: 12, bottom: 24
) )
), ),
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 24), padding: const EdgeInsets.symmetric(horizontal: 16),
child: Material( child: Material(
color: Theme.of(context).colorScheme.primary.withOpacity(0.1), color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
borderRadius: BorderRadius.circular(28), borderRadius: BorderRadius.circular(28),
@ -157,8 +145,8 @@ class ClientForm extends StatelessWidget {
borderRadius: BorderRadius.circular(28), borderRadius: BorderRadius.circular(28),
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 20, horizontal: 16,
vertical: 5 vertical: 6
), ),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -182,7 +170,7 @@ class ClientForm extends StatelessWidget {
), ),
), ),
), ),
const SizedBox(height: 10), const SizedBox(height: 8),
SettingsTile( SettingsTile(
label: AppLocalizations.of(context)!.enableFiltering, label: AppLocalizations.of(context)!.enableFiltering,
value: enableFiltering, value: enableFiltering,
@ -204,7 +192,7 @@ class ClientForm extends StatelessWidget {
CustomListTile( CustomListTile(
title: AppLocalizations.of(context)!.safeSearch, title: AppLocalizations.of(context)!.safeSearch,
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 42, horizontal: 34,
vertical: 16 vertical: 16
), ),
trailing: Padding( trailing: Padding(
@ -228,15 +216,15 @@ class ClientForm extends StatelessWidget {
), ),
SectionLabel( SectionLabel(
label: AppLocalizations.of(context)!.queryLogsAndStatistics, label: AppLocalizations.of(context)!.queryLogsAndStatistics,
padding: const EdgeInsets.all(24), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24),
), ),
CustomSwitchListTile( CustomSwitchListTile(
title: AppLocalizations.of(context)!.ignoreClientQueryLog, title: AppLocalizations.of(context)!.ignoreClientQueryLog,
value: ignoreClientQueryLog, value: ignoreClientQueryLog,
onChanged: updateIgnoreClientQueryLog, onChanged: updateIgnoreClientQueryLog,
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 24, horizontal: 16,
vertical: 4 vertical: 6
), ),
), ),
CustomSwitchListTile( CustomSwitchListTile(
@ -244,13 +232,13 @@ class ClientForm extends StatelessWidget {
value: ignoreClientStatistics, value: ignoreClientStatistics,
onChanged: updateIgnoreClientStatistics, onChanged: updateIgnoreClientStatistics,
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 24, horizontal: 16,
vertical: 4 vertical: 6
), ),
), ),
SectionLabel( SectionLabel(
label: AppLocalizations.of(context)!.blockedServices, label: AppLocalizations.of(context)!.blockedServices,
padding: const EdgeInsets.all(24), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24),
), ),
BlockedServicesSection( BlockedServicesSection(
useGlobalSettingsServices: useGlobalSettingsServices, useGlobalSettingsServices: useGlobalSettingsServices,
@ -260,15 +248,40 @@ class ClientForm extends StatelessWidget {
), ),
UpstreamServersSection( UpstreamServersSection(
upstreamServers: upstreamServers, upstreamServers: upstreamServers,
onCheckValidValues: () => updateValidValues( onCheckValidValues: () => {},
checkValidValues(
identifiersControllers: identifiersControllers,
nameController: nameController
)
),
onUpdateUpstreamServers: updateUpstreamServers 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,
),
),
], ],
); );
} }

View file

@ -39,6 +39,8 @@ class ClientScreen extends StatefulWidget {
} }
class _ClientScreenState extends State<ClientScreen> { class _ClientScreenState extends State<ClientScreen> {
final _scrollController = ScrollController();
final Uuid uuid = const Uuid(); final Uuid uuid = const Uuid();
bool validValues = false; bool validValues = false;
@ -76,6 +78,15 @@ class _ClientScreenState extends State<ClientScreen> {
bool _ignoreClientQueryLog = false; bool _ignoreClientQueryLog = false;
bool _ignoreClientStatistics = false; bool _ignoreClientStatistics = false;
bool _enableDnsCache = false;
final _dnsCacheField = TextEditingController();
String? _dnsCacheError;
// VALIDATIONS
bool _nameValid = true;
bool _identifiersValid = true;
bool _dnsCacheValid = true;
void enableDisableGlobalSettingsFiltering() { void enableDisableGlobalSettingsFiltering() {
if (useGlobalSettingsFiltering == true) { if (useGlobalSettingsFiltering == true) {
setState(() { setState(() {
@ -125,6 +136,10 @@ class _ClientScreenState extends State<ClientScreen> {
)).toList(); )).toList();
_ignoreClientQueryLog = widget.client!.ignoreQuerylog ?? false; _ignoreClientQueryLog = widget.client!.ignoreQuerylog ?? false;
_ignoreClientStatistics = widget.client!.ignoreStatistics ?? false; _ignoreClientStatistics = widget.client!.ignoreStatistics ?? false;
_enableDnsCache = widget.client!.upstreamsCacheEnabled ?? false;
_dnsCacheField.text = widget.client!.upstreamsCacheSize != null
? widget.client!.upstreamsCacheSize.toString()
: "";
} }
super.initState(); super.initState();
} }
@ -147,20 +162,37 @@ class _ClientScreenState extends State<ClientScreen> {
upstreams: List<String>.from(upstreamServers.map((e) => e.controller.text)), upstreams: List<String>.from(upstreamServers.map((e) => e.controller.text)),
tags: selectedTags, tags: selectedTags,
ignoreQuerylog: _ignoreClientQueryLog, ignoreQuerylog: _ignoreClientQueryLog,
ignoreStatistics: _ignoreClientStatistics ignoreStatistics: _ignoreClientStatistics,
upstreamsCacheEnabled: _enableDnsCache,
upstreamsCacheSize: _dnsCacheField.text != ""
? int.parse(_dnsCacheField.text)
: null
); );
widget.onConfirm(client); 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<Widget> actions() { List<Widget> actions() {
return [ return [
IconButton( IconButton(
onPressed: validValues == true onPressed: validateValues,
? () {
createClient();
Navigator.pop(context);
}
: null,
icon: const Icon(Icons.save_rounded), icon: const Icon(Icons.save_rounded),
tooltip: AppLocalizations.of(context)!.save, tooltip: AppLocalizations.of(context)!.save,
), ),
@ -193,38 +225,52 @@ class _ClientScreenState extends State<ClientScreen> {
actions: actions(), actions: actions(),
), ),
body: SafeArea( body: SafeArea(
child: ClientForm( child: ListView(
isFullScreen: true, controller: _scrollController,
client: widget.client, children: [
nameController: nameController, if (!_nameValid || !_identifiersValid || !_dnsCacheValid) _Errors(
updateValidValues: (v) => setState(() => validValues = v), nameValid: _nameValid,
identifiersControllers: identifiersControllers, identifiersValid: _identifiersValid,
selectedTags: selectedTags, dnsCacheValid: _dnsCacheValid
useGlobalSettingsFiltering: useGlobalSettingsFiltering, ),
enableFiltering: enableFiltering, ClientForm(
enableParentalControl: enableParentalControl, isFullScreen: true,
enableSafeBrowsing: enableSafeBrowsing, client: widget.client,
enableSafeSearch: enableSafeSearch, nameController: nameController,
safeSearch: safeSearch, identifiersControllers: identifiersControllers,
blockedServices: blockedServices, selectedTags: selectedTags,
updateBlockedServices: (v) => setState(() => blockedServices = v), useGlobalSettingsFiltering: useGlobalSettingsFiltering,
upstreamServers: upstreamServers, enableFiltering: enableFiltering,
updateUpstreamServers: (v) => setState(() => upstreamServers = v), enableParentalControl: enableParentalControl,
defaultSafeSearch: defaultSafeSearch, enableSafeBrowsing: enableSafeBrowsing,
useGlobalSettingsServices: useGlobalSettingsServices, enableSafeSearch: enableSafeSearch,
updateSelectedTags: (v) => setState(() => selectedTags = v), safeSearch: safeSearch,
updateIdentifiersControllers: (v) => setState(() => identifiersControllers = v), blockedServices: blockedServices,
enableDisableGlobalSettingsFiltering: enableDisableGlobalSettingsFiltering, updateBlockedServices: (v) => setState(() => blockedServices = v),
updateEnableFiltering: (v) => setState(() => enableFiltering = v), upstreamServers: upstreamServers,
updateEnableParentalControl: (v) => setState(() => enableParentalControl = v), updateUpstreamServers: (v) => setState(() => upstreamServers = v),
updateEnableSafeBrowsing: (v) => setState(() => enableSafeBrowsing = v), defaultSafeSearch: defaultSafeSearch,
updateEnableSafeSearch: (v) => setState(() => enableSafeSearch = v), useGlobalSettingsServices: useGlobalSettingsServices,
updateSafeSearch: (v) => setState(() => safeSearch = v), updateSelectedTags: (v) => setState(() => selectedTags = v),
updateUseGlobalSettingsServices: (v) => setState(() => useGlobalSettingsServices = v), updateIdentifiersControllers: (v) => setState(() => identifiersControllers = v),
ignoreClientQueryLog: _ignoreClientQueryLog, enableDisableGlobalSettingsFiltering: enableDisableGlobalSettingsFiltering,
ignoreClientStatistics: _ignoreClientStatistics, updateEnableFiltering: (v) => setState(() => enableFiltering = v),
updateIgnoreClientQueryLog: (v) => setState(() => _ignoreClientQueryLog = v), updateEnableParentalControl: (v) => setState(() => enableParentalControl = v),
updateIgnoreClientStatistics: (v) => setState(() => _ignoreClientStatistics = 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<ClientScreen> {
), ),
), ),
Flexible( Flexible(
child: ClientForm( child: ListView(
isFullScreen: false, controller: _scrollController,
client: widget.client, children: [
nameController: nameController, if (!_nameValid || !_identifiersValid || !_dnsCacheValid) _Errors(
updateValidValues: (v) => setState(() => validValues = v), nameValid: _nameValid,
identifiersControllers: identifiersControllers, identifiersValid: _identifiersValid,
selectedTags: selectedTags, dnsCacheValid: _dnsCacheValid
useGlobalSettingsFiltering: useGlobalSettingsFiltering, ),
enableFiltering: enableFiltering, ClientForm(
enableParentalControl: enableParentalControl, isFullScreen: false,
enableSafeBrowsing: enableSafeBrowsing, client: widget.client,
enableSafeSearch: enableSafeSearch, nameController: nameController,
safeSearch: safeSearch, identifiersControllers: identifiersControllers,
blockedServices: blockedServices, selectedTags: selectedTags,
updateBlockedServices: (v) => setState(() => blockedServices = v), useGlobalSettingsFiltering: useGlobalSettingsFiltering,
upstreamServers: upstreamServers, enableFiltering: enableFiltering,
updateUpstreamServers: (v) => setState(() => upstreamServers = v), enableParentalControl: enableParentalControl,
defaultSafeSearch: defaultSafeSearch, enableSafeBrowsing: enableSafeBrowsing,
useGlobalSettingsServices: useGlobalSettingsServices, enableSafeSearch: enableSafeSearch,
updateSelectedTags: (v) => setState(() => selectedTags = v), safeSearch: safeSearch,
updateIdentifiersControllers: (v) => setState(() => identifiersControllers = v), blockedServices: blockedServices,
enableDisableGlobalSettingsFiltering: enableDisableGlobalSettingsFiltering, updateBlockedServices: (v) => setState(() => blockedServices = v),
updateEnableFiltering: (v) => setState(() => enableFiltering = v), upstreamServers: upstreamServers,
updateEnableParentalControl: (v) => setState(() => enableParentalControl = v), updateUpstreamServers: (v) => setState(() => upstreamServers = v),
updateEnableSafeBrowsing: (v) => setState(() => enableSafeBrowsing = v), defaultSafeSearch: defaultSafeSearch,
updateEnableSafeSearch: (v) => setState(() => enableSafeSearch = v), useGlobalSettingsServices: useGlobalSettingsServices,
updateSafeSearch: (v) => setState(() => safeSearch = v), updateSelectedTags: (v) => setState(() => selectedTags = v),
updateUseGlobalSettingsServices: (v) => setState(() => useGlobalSettingsServices = v), updateIdentifiersControllers: (v) => setState(() => identifiersControllers = v),
ignoreClientQueryLog: _ignoreClientQueryLog, enableDisableGlobalSettingsFiltering: enableDisableGlobalSettingsFiltering,
ignoreClientStatistics: _ignoreClientStatistics, updateEnableFiltering: (v) => setState(() => enableFiltering = v),
updateIgnoreClientQueryLog: (v) => setState(() => _ignoreClientQueryLog = v), updateEnableParentalControl: (v) => setState(() => enableParentalControl = v),
updateIgnoreClientStatistics: (v) => setState(() => _ignoreClientStatistics = 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<ClientScreen> {
} }
} }
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
),
),
],
),
),
);
}
}

View file

@ -74,22 +74,6 @@ void openSafeSearchModal({
); );
} }
bool checkValidValues({
required TextEditingController nameController,
required List<ControllerListItem> identifiersControllers
}) {
if (
nameController.text != '' &&
identifiersControllers.isNotEmpty &&
identifiersControllers[0].controller.text != ''
) {
return true;
}
else {
return false;
}
}
void openClientFormModal({ void openClientFormModal({
required BuildContext context, required BuildContext context,
required double width, required double width,
@ -123,4 +107,10 @@ void openClientFormModal({
onDelete: onDelete, onDelete: onDelete,
), ),
); );
}
bool validateNumber(String value) {
if (value == "") return true;
final regexp = RegExp(r'^\d+$');
return regexp.hasMatch(value);
} }

View file

@ -11,11 +11,11 @@ class IdentifiersSection extends StatefulWidget {
final void Function() onCheckValidValues; final void Function() onCheckValidValues;
const IdentifiersSection({ const IdentifiersSection({
Key? key, super.key,
required this.identifiersControllers, required this.identifiersControllers,
required this.onUpdateIdentifiersControllers, required this.onUpdateIdentifiersControllers,
required this.onCheckValidValues required this.onCheckValidValues
}) : super(key: key); });
@override @override
State<IdentifiersSection> createState() => _IdentifiersSectionState(); State<IdentifiersSection> createState() => _IdentifiersSectionState();
@ -34,11 +34,11 @@ class _IdentifiersSectionState extends State<IdentifiersSection> {
SectionLabel( SectionLabel(
label: AppLocalizations.of(context)!.identifiers, label: AppLocalizations.of(context)!.identifiers,
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
left: 24, right: 24, top: 24, bottom: 12 left: 16, right: 16, top: 24, bottom: 12
) )
), ),
Padding( Padding(
padding: const EdgeInsets.only(right: 20), padding: const EdgeInsets.only(right: 10),
child: IconButton( child: IconButton(
onPressed: () => widget.onUpdateIdentifiersControllers([ onPressed: () => widget.onUpdateIdentifiersControllers([
...widget.identifiersControllers, ...widget.identifiersControllers,
@ -54,7 +54,7 @@ class _IdentifiersSectionState extends State<IdentifiersSection> {
), ),
if (widget.identifiersControllers.isNotEmpty) ...widget.identifiersControllers.map((controller) => Padding( if (widget.identifiersControllers.isNotEmpty) ...widget.identifiersControllers.map((controller) => Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
top: 12, bottom: 12, left: 24, right: 20 top: 12, bottom: 12, left: 16, right: 10
), ),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
@ -75,9 +75,9 @@ class _IdentifiersSectionState extends State<IdentifiersSection> {
), ),
), ),
), ),
const SizedBox(width: 16), const SizedBox(width: 12),
Padding( Padding(
padding: const EdgeInsets.only(bottom: 25), padding: const EdgeInsets.only(bottom: 24),
child: IconButton( child: IconButton(
onPressed: () => widget.onUpdateIdentifiersControllers( onPressed: () => widget.onUpdateIdentifiersControllers(
widget.identifiersControllers.where((e) => e.id != controller.id).toList() widget.identifiersControllers.where((e) => e.id != controller.id).toList()
@ -87,7 +87,7 @@ class _IdentifiersSectionState extends State<IdentifiersSection> {
) )
], ],
), ),
)).toList(), )),
if (widget.identifiersControllers.isEmpty) Container( if (widget.identifiersControllers.isEmpty) Container(
padding: const EdgeInsets.symmetric(vertical: 16), padding: const EdgeInsets.symmetric(vertical: 16),
child: Text( child: Text(

View file

@ -7,12 +7,12 @@ class SettingsTile extends StatelessWidget {
final bool useGlobalSettingsFiltering; final bool useGlobalSettingsFiltering;
const SettingsTile({ const SettingsTile({
Key? key, super.key,
required this.label, required this.label,
required this.value, required this.value,
this.onChange, this.onChange,
required this.useGlobalSettingsFiltering required this.useGlobalSettingsFiltering
}) : super(key: key); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -23,10 +23,7 @@ class SettingsTile extends StatelessWidget {
? value != null ? () => onChange!(!value!) : null ? value != null ? () => onChange!(!value!) : null
: null, : null,
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 6),
horizontal: 42,
vertical: 5
),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [

View file

@ -7,10 +7,10 @@ class TagsSection extends StatelessWidget {
final void Function(List<String>) onTagsSelected; final void Function(List<String>) onTagsSelected;
const TagsSection({ const TagsSection({
Key? key, super.key,
required this.selectedTags, required this.selectedTags,
required this.onTagsSelected required this.onTagsSelected
}) : super(key: key); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -24,7 +24,7 @@ class TagsSection extends StatelessWidget {
) , ) ,
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
vertical: 0, horizontal: 24 vertical: 0, horizontal: 16
), ),
child: Row( child: Row(
children: [ children: [

View file

@ -11,11 +11,11 @@ class UpstreamServersSection extends StatefulWidget {
final void Function(List<ControllerListItem>) onUpdateUpstreamServers; final void Function(List<ControllerListItem>) onUpdateUpstreamServers;
const UpstreamServersSection({ const UpstreamServersSection({
Key? key, super.key,
required this.upstreamServers, required this.upstreamServers,
required this.onCheckValidValues, required this.onCheckValidValues,
required this.onUpdateUpstreamServers required this.onUpdateUpstreamServers
}) : super(key: key); });
@override @override
State<UpstreamServersSection> createState() => _UpstreamServersSectionState(); State<UpstreamServersSection> createState() => _UpstreamServersSectionState();
@ -33,10 +33,10 @@ class _UpstreamServersSectionState extends State<UpstreamServersSection> {
children: [ children: [
SectionLabel( SectionLabel(
label: AppLocalizations.of(context)!.upstreamServers, label: AppLocalizations.of(context)!.upstreamServers,
padding: const EdgeInsets.all(24), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24),
), ),
Padding( Padding(
padding: const EdgeInsets.only(right: 20), padding: const EdgeInsets.only(right: 12),
child: IconButton( child: IconButton(
onPressed: () => setState(() => widget.upstreamServers.add( onPressed: () => setState(() => widget.upstreamServers.add(
ControllerListItem( ControllerListItem(
@ -50,7 +50,7 @@ class _UpstreamServersSectionState extends State<UpstreamServersSection> {
], ],
), ),
if (widget.upstreamServers.isNotEmpty) ...widget.upstreamServers.map((controller) => Padding( 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( child: Padding(
padding: const EdgeInsets.only(bottom: 20), padding: const EdgeInsets.only(bottom: 20),
child: Row( child: Row(
@ -71,7 +71,7 @@ class _UpstreamServersSectionState extends State<UpstreamServersSection> {
), ),
), ),
), ),
const SizedBox(width: 16), const SizedBox(width: 12),
IconButton( IconButton(
onPressed: () => widget.onUpdateUpstreamServers( onPressed: () => widget.onUpdateUpstreamServers(
widget.upstreamServers.where((e) => e.id != controller.id).toList() widget.upstreamServers.where((e) => e.id != controller.id).toList()
@ -81,7 +81,7 @@ class _UpstreamServersSectionState extends State<UpstreamServersSection> {
], ],
), ),
), ),
)).toList(), )),
if (widget.upstreamServers.isEmpty) Container( if (widget.upstreamServers.isEmpty) Container(
padding: const EdgeInsets.symmetric(vertical: 16), padding: const EdgeInsets.symmetric(vertical: 16),
child: Column( child: Column(

View file

@ -226,16 +226,16 @@ class _SearchClientsState extends State<SearchClients> {
itemCount: clientsScreen.length, itemCount: clientsScreen.length,
padding: const EdgeInsets.only(bottom: 0), padding: const EdgeInsets.only(bottom: 0),
itemBuilder: (context, index) => OptionsMenu( itemBuilder: (context, index) => OptionsMenu(
options: [ options: (v) => [
MenuOption( MenuOption(
icon: Icons.edit_rounded, icon: Icons.edit_rounded,
title: AppLocalizations.of(context)!.edit, title: AppLocalizations.of(context)!.edit,
action: (v) => openClientModal(v) action: () => openClientModal(v)
), ),
MenuOption( MenuOption(
icon: Icons.delete_rounded, icon: Icons.delete_rounded,
title: AppLocalizations.of(context)!.delete, title: AppLocalizations.of(context)!.delete,
action: (v) => openDeleteModal(v) action: () => openDeleteModal(v)
), ),
], ],
value: clientsScreen[index], value: clientsScreen[index],

View file

@ -283,11 +283,11 @@ class FiltersTripleColumn extends StatelessWidget {
), ),
], ],
child: OptionsMenu( child: OptionsMenu(
options: [ options: (_) => [
MenuOption( MenuOption(
title: AppLocalizations.of(context)!.copyClipboard, title: AppLocalizations.of(context)!.copyClipboard,
icon: Icons.copy_rounded, icon: Icons.copy_rounded,
action: (_) => copyToClipboard( action: () => copyToClipboard(
value: filteringProvider.filtering!.userRules[index], value: filteringProvider.filtering!.userRules[index],
successMessage: AppLocalizations.of(context)!.copiedClipboard, successMessage: AppLocalizations.of(context)!.copiedClipboard,
) )

View file

@ -146,7 +146,7 @@ class ListOptionsMenu extends StatelessWidget {
color: Colors.transparent, color: Colors.transparent,
child: InkWell( child: InkWell(
child: OptionsMenu( child: OptionsMenu(
options: [ options: (_) => [
MenuOption( MenuOption(
title: list.enabled == true title: list.enabled == true
? AppLocalizations.of(context)!.disable ? AppLocalizations.of(context)!.disable
@ -154,12 +154,12 @@ class ListOptionsMenu extends StatelessWidget {
icon: list.enabled == true icon: list.enabled == true
? Icons.gpp_bad_rounded ? Icons.gpp_bad_rounded
: Icons.verified_user_rounded, : Icons.verified_user_rounded,
action: (_) => enableDisable() action: () => enableDisable()
), ),
MenuOption( MenuOption(
title: AppLocalizations.of(context)!.copyListUrl, title: AppLocalizations.of(context)!.copyListUrl,
icon: Icons.copy_rounded, icon: Icons.copy_rounded,
action: (_) => copyToClipboard( action: () => copyToClipboard(
value: list.url, value: list.url,
successMessage: AppLocalizations.of(context)!.listUrlCopied successMessage: AppLocalizations.of(context)!.listUrlCopied
) )
@ -167,12 +167,12 @@ class ListOptionsMenu extends StatelessWidget {
MenuOption( MenuOption(
title: AppLocalizations.of(context)!.openListUrl, title: AppLocalizations.of(context)!.openListUrl,
icon: Icons.open_in_browser_rounded, icon: Icons.open_in_browser_rounded,
action: (_) => openUrl(list.url) action: () => openUrl(list.url)
), ),
MenuOption( MenuOption(
title: AppLocalizations.of(context)!.selectionMode, title: AppLocalizations.of(context)!.selectionMode,
icon: Icons.check_rounded, icon: Icons.check_rounded,
action: (_) => Future.delayed( action: () => Future.delayed(
const Duration(milliseconds: 0), const Duration(milliseconds: 0),
() => openSelectionMode() () => openSelectionMode()
) )

View file

@ -1,17 +1,20 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:adguard_home_manager/widgets/line_chart.dart'; import 'package:adguard_home_manager/widgets/line_chart.dart';
import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart';
class HomeChart extends StatelessWidget { class HomeChart extends StatefulWidget {
final List<int> data; final List<int> data;
final String label; final String label;
final String primaryValue; final String primaryValue;
final String secondaryValue; final String secondaryValue;
final Color color; final Color color;
final int hoursInterval; final int hoursInterval;
final void Function() onTapTitle;
final bool isDesktop;
const HomeChart({ const HomeChart({
super.key, super.key,
@ -20,20 +23,29 @@ class HomeChart extends StatelessWidget {
required this.primaryValue, required this.primaryValue,
required this.secondaryValue, required this.secondaryValue,
required this.color, required this.color,
required this.hoursInterval required this.hoursInterval,
required this.onTapTitle,
required this.isDesktop,
}); });
@override
State<HomeChart> createState() => _HomeChartState();
}
class _HomeChartState extends State<HomeChart> {
bool _isHover = false;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final appConfigProvider = Provider.of<AppConfigProvider>(context); final appConfigProvider = Provider.of<AppConfigProvider>(context);
final bool isEmpty = data.every((i) => i == 0); final bool isEmpty = widget.data.every((i) => i == 0);
if (!(appConfigProvider.hideZeroValues == true && isEmpty == true)) { if (!(appConfigProvider.hideZeroValues == true && isEmpty == true)) {
List<DateTime> dateTimes = []; List<DateTime> dateTimes = [];
DateTime currentDate = DateTime.now().subtract(Duration(hours: hoursInterval*data.length+1)); DateTime currentDate = DateTime.now().subtract(Duration(hours: widget.hoursInterval*widget.data.length+1));
for (var i = 0; i < data.length; i++) { for (var i = 0; i < widget.data.length; i++) {
currentDate = currentDate.add(Duration(hours: hoursInterval)); currentDate = currentDate.add(Duration(hours: widget.hoursInterval));
dateTimes.add(currentDate); dateTimes.add(currentDate);
} }
@ -49,60 +61,87 @@ class HomeChart extends StatelessWidget {
), ),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: isEmpty
? CrossAxisAlignment.center
: CrossAxisAlignment.start,
children: [ children: [
Flexible( Flexible(
child: Text( child: MouseRegion(
label, cursor: SystemMouseCursors.click,
overflow: TextOverflow.ellipsis, onEnter: (_) => setState(() => _isHover = true),
style: TextStyle( onExit: (_) => setState(() => _isHover = false),
fontSize: 18, child: GestureDetector(
fontWeight: FontWeight.w500, onTapDown: (_) => setState(() => _isHover = true),
color: Theme.of(context).colorScheme.onSurface 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 if (!isEmpty) Column(
? Column( crossAxisAlignment: CrossAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end, children: [
children: [ Text(
Text( widget.primaryValue,
primaryValue, style: TextStyle(
style: TextStyle( color: widget.color,
color: color, fontSize: 18,
fontSize: 18, fontWeight: FontWeight.w500
fontWeight: FontWeight.w500 ),
), ),
), Text(
Text( widget.secondaryValue,
secondaryValue, style: TextStyle(
style: TextStyle( fontSize: 12,
fontSize: 12, color: widget.color
color: color ),
),
)
],
) )
: Row( ],
children: [ ),
Text( if (isEmpty && !widget.isDesktop) Column(
primaryValue, children: [
style: TextStyle( Icon(
color: color, Icons.show_chart_rounded,
fontSize: 18, color: Theme.of(context).colorScheme.onSurfaceVariant,
fontWeight: FontWeight.w500 size: 16,
), ),
), Text(
const SizedBox(width: 10), AppLocalizations.of(context)!.noData,
Text( style: TextStyle(
"($secondaryValue)", fontSize: 12,
style: TextStyle( fontWeight: FontWeight.w500,
fontSize: 12, color: Theme.of(context).colorScheme.onSurfaceVariant,
color: color ),
),
)
],
) )
],
)
], ],
), ),
), ),
@ -110,13 +149,35 @@ class HomeChart extends StatelessWidget {
width: double.maxFinite, width: double.maxFinite,
height: 150, height: 150,
child: CustomLineChart( child: CustomLineChart(
data: data, data: widget.data,
color: color, color: widget.color,
dates: dateTimes, dates: dateTimes,
daysInterval: hoursInterval == 24, daysInterval: widget.hoursInterval == 24,
context: context, 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
),
)
],
),
)
], ],
), ),
), ),

View file

@ -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/fab.dart';
import 'package:adguard_home_manager/screens/home/chart.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/functions/number_format.dart';
import 'package:adguard_home_manager/constants/enums.dart'; import 'package:adguard_home_manager/constants/enums.dart';
import 'package:adguard_home_manager/providers/status_provider.dart'; import 'package:adguard_home_manager/providers/status_provider.dart';
@ -38,6 +40,9 @@ class _HomeState extends State<Home> {
withLoadingIndicator: statusProvider.serverStatus != null ? false : true withLoadingIndicator: statusProvider.serverStatus != null ? false : true
); );
final clientsProvider = Provider.of<ClientsProvider>(context, listen: false);
clientsProvider.fetchClients(updateLoading: false);
super.initState(); super.initState();
isVisible = true; isVisible = true;
@ -61,6 +66,7 @@ class _HomeState extends State<Home> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final statusProvider = Provider.of<StatusProvider>(context); final statusProvider = Provider.of<StatusProvider>(context);
final appConfigProvider = Provider.of<AppConfigProvider>(context); final appConfigProvider = Provider.of<AppConfigProvider>(context);
final logsProvider = Provider.of<LogsProvider>(context);
final width = MediaQuery.of(context).size.width; final width = MediaQuery.of(context).size.width;
@ -143,6 +149,15 @@ class _HomeState extends State<Home> {
secondaryValue: "${doubleFormat(statusProvider.serverStatus!.stats.avgProcessingTime*1000, Platform.localeName)} ms", secondaryValue: "${doubleFormat(statusProvider.serverStatus!.stats.avgProcessingTime*1000, Platform.localeName)} ms",
color: Colors.blue, color: Colors.blue,
hoursInterval: statusProvider.serverStatus!.stats.timeUnits == "days" ? 24 : 1, hoursInterval: statusProvider.serverStatus!.stats.timeUnits == "days" ? 24 : 1,
onTapTitle: () {
logsProvider.setSelectedResultStatus(
value: "all",
refetch: true
);
logsProvider.filterLogs();
appConfigProvider.setSelectedScreen(2);
},
isDesktop: width > 700,
), ),
), ),
FractionallySizedBox( FractionallySizedBox(
@ -154,6 +169,14 @@ class _HomeState extends State<Home> {
secondaryValue: "${statusProvider.serverStatus!.stats.numDnsQueries > 0 ? doubleFormat((statusProvider.serverStatus!.stats.numBlockedFiltering/statusProvider.serverStatus!.stats.numDnsQueries)*100, Platform.localeName) : 0}%", secondaryValue: "${statusProvider.serverStatus!.stats.numDnsQueries > 0 ? doubleFormat((statusProvider.serverStatus!.stats.numBlockedFiltering/statusProvider.serverStatus!.stats.numDnsQueries)*100, Platform.localeName) : 0}%",
color: Colors.red, color: Colors.red,
hoursInterval: statusProvider.serverStatus!.stats.timeUnits == "days" ? 24 : 1, hoursInterval: statusProvider.serverStatus!.stats.timeUnits == "days" ? 24 : 1,
onTapTitle: () {
logsProvider.setSelectedResultStatus(
value: "blocked",
refetch: true
);
appConfigProvider.setSelectedScreen(2);
},
isDesktop: width > 700,
), ),
), ),
FractionallySizedBox( FractionallySizedBox(
@ -165,6 +188,14 @@ class _HomeState extends State<Home> {
secondaryValue: "${statusProvider.serverStatus!.stats.numDnsQueries > 0 ? doubleFormat((statusProvider.serverStatus!.stats.numReplacedSafebrowsing/statusProvider.serverStatus!.stats.numDnsQueries)*100, Platform.localeName) : 0}%", secondaryValue: "${statusProvider.serverStatus!.stats.numDnsQueries > 0 ? doubleFormat((statusProvider.serverStatus!.stats.numReplacedSafebrowsing/statusProvider.serverStatus!.stats.numDnsQueries)*100, Platform.localeName) : 0}%",
color: Colors.green, color: Colors.green,
hoursInterval: statusProvider.serverStatus!.stats.timeUnits == "days" ? 24 : 1, hoursInterval: statusProvider.serverStatus!.stats.timeUnits == "days" ? 24 : 1,
onTapTitle: () {
logsProvider.setSelectedResultStatus(
value: "blocked_safebrowsing",
refetch: true
);
appConfigProvider.setSelectedScreen(2);
},
isDesktop: width > 700,
), ),
), ),
FractionallySizedBox( FractionallySizedBox(
@ -176,6 +207,15 @@ class _HomeState extends State<Home> {
secondaryValue: "${statusProvider.serverStatus!.stats.numDnsQueries > 0 ? doubleFormat((statusProvider.serverStatus!.stats.numReplacedParental/statusProvider.serverStatus!.stats.numDnsQueries)*100, Platform.localeName) : 0}%", secondaryValue: "${statusProvider.serverStatus!.stats.numDnsQueries > 0 ? doubleFormat((statusProvider.serverStatus!.stats.numReplacedParental/statusProvider.serverStatus!.stats.numDnsQueries)*100, Platform.localeName) : 0}%",
color: Colors.orange, color: Colors.orange,
hoursInterval: statusProvider.serverStatus!.stats.timeUnits == "days" ? 24 : 1, hoursInterval: statusProvider.serverStatus!.stats.timeUnits == "days" ? 24 : 1,
onTapTitle: () {
logsProvider.setSelectedResultStatus(
value: "blocked_parental",
refetch: true
);
logsProvider.filterLogs();
appConfigProvider.setSelectedScreen(2);
},
isDesktop: width > 700,
), ),
), ),
], ],

View file

@ -16,7 +16,7 @@ class RowItem extends StatefulWidget {
final bool clients; final bool clients;
final bool showColor; final bool showColor;
final String? unit; final String? unit;
final List<MenuOption> options; final List<MenuOption> Function(dynamic) options;
final void Function(dynamic)? onTapEntry; final void Function(dynamic)? onTapEntry;
const RowItem({ const RowItem({

View file

@ -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/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/number_format.dart';
import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/functions/snackbar.dart';
import 'package:adguard_home_manager/classes/process_modal.dart'; import 'package:adguard_home_manager/classes/process_modal.dart';
@ -30,6 +31,7 @@ class TopItemsLists extends StatelessWidget {
final statusProvider = Provider.of<StatusProvider>(context); final statusProvider = Provider.of<StatusProvider>(context);
final appConfigProvider = Provider.of<AppConfigProvider>(context); final appConfigProvider = Provider.of<AppConfigProvider>(context);
final logsProvider = Provider.of<LogsProvider>(context); final logsProvider = Provider.of<LogsProvider>(context);
final clientsProvider = Provider.of<ClientsProvider>(context);
List<Widget> bottom = [ List<Widget> bottom = [
Padding( Padding(
@ -96,10 +98,53 @@ class TopItemsLists extends StatelessWidget {
} }
} }
void copyValueClipboard(value) { void copyValueClipboard(dynamic value) {
copyToClipboard(value: value, successMessage: AppLocalizations.of(context)!.copiedClipboard); 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( return Column(
children: order.asMap().entries.map((item) { children: order.asMap().entries.map((item) {
switch (item.value) { switch (item.value) {
@ -113,16 +158,16 @@ class TopItemsLists extends StatelessWidget {
withChart: true, withChart: true,
withProgressBar: true, withProgressBar: true,
buildValue: (v) => v.toString(), buildValue: (v) => v.toString(),
menuOptions: [ menuOptions: (v) => [
MenuOption( MenuOption(
title: AppLocalizations.of(context)!.blockDomain, title: AppLocalizations.of(context)!.blockDomain,
icon: Icons.block_rounded, icon: Icons.block_rounded,
action: (v) => blockUnblock(domain: v.toString(), newStatus: 'block') action: () => blockUnblock(domain: v.toString(), newStatus: 'block')
), ),
MenuOption( MenuOption(
title: AppLocalizations.of(context)!.copyClipboard, title: AppLocalizations.of(context)!.copyClipboard,
icon: Icons.copy_rounded, icon: Icons.copy_rounded,
action: copyValueClipboard action: () => copyValueClipboard(v)
), ),
], ],
onTapEntry: (v) => filterDomainLogs(value: v.toString()), onTapEntry: (v) => filterDomainLogs(value: v.toString()),
@ -141,16 +186,16 @@ class TopItemsLists extends StatelessWidget {
withChart: true, withChart: true,
withProgressBar: true, withProgressBar: true,
buildValue: (v) => v.toString(), buildValue: (v) => v.toString(),
menuOptions: [ menuOptions: (v) => [
MenuOption( MenuOption(
title: AppLocalizations.of(context)!.unblockDomain, title: AppLocalizations.of(context)!.unblockDomain,
icon: Icons.check_rounded, icon: Icons.check_rounded,
action: (v) => blockUnblock(domain: v, newStatus: 'unblock') action: () => blockUnblock(domain: v, newStatus: 'unblock')
), ),
MenuOption( MenuOption(
title: AppLocalizations.of(context)!.copyClipboard, title: AppLocalizations.of(context)!.copyClipboard,
icon: Icons.copy_rounded, icon: Icons.copy_rounded,
action: copyValueClipboard action: () => copyValueClipboard(v)
) )
], ],
onTapEntry: (v) => filterDomainLogs(value: v), onTapEntry: (v) => filterDomainLogs(value: v),
@ -169,12 +214,21 @@ class TopItemsLists extends StatelessWidget {
withChart: true, withChart: true,
withProgressBar: true, withProgressBar: true,
buildValue: (v) => v.toString(), 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( MenuOption(
title: AppLocalizations.of(context)!.copyClipboard, title: AppLocalizations.of(context)!.copyClipboard,
icon: Icons.copy_rounded, icon: Icons.copy_rounded,
action: copyValueClipboard action: () => copyValueClipboard(v)
) ),
], ],
onTapEntry: (v) => filterClientLogs(value: v), onTapEntry: (v) => filterClientLogs(value: v),
), ),
@ -193,11 +247,11 @@ class TopItemsLists extends StatelessWidget {
withChart: true, withChart: true,
withProgressBar: true, withProgressBar: true,
buildValue: (v) => v.toString(), buildValue: (v) => v.toString(),
menuOptions: [ menuOptions: (v) => [
MenuOption( MenuOption(
title: AppLocalizations.of(context)!.copyClipboard, title: AppLocalizations.of(context)!.copyClipboard,
icon: Icons.copy_rounded, icon: Icons.copy_rounded,
action: copyValueClipboard action: () => copyValueClipboard(v)
) )
], ],
), ),
@ -217,11 +271,11 @@ class TopItemsLists extends StatelessWidget {
withChart: false, withChart: false,
withProgressBar: false, withProgressBar: false,
buildValue: (v) => "${doubleFormat(v*1000, Platform.localeName)} ms", buildValue: (v) => "${doubleFormat(v*1000, Platform.localeName)} ms",
menuOptions: [ menuOptions: (v) => [
MenuOption( MenuOption(
title: AppLocalizations.of(context)!.copyClipboard, title: AppLocalizations.of(context)!.copyClipboard,
icon: Icons.copy_rounded, icon: Icons.copy_rounded,
action: copyValueClipboard action: () => copyValueClipboard(v)
) )
], ],
), ),

View file

@ -24,7 +24,7 @@ class TopItemsScreen extends StatefulWidget {
final List<Map<String, dynamic>> data; final List<Map<String, dynamic>> data;
final bool withProgressBar; final bool withProgressBar;
final String Function(dynamic) buildValue; final String Function(dynamic) buildValue;
final List<MenuOption> options; final List<MenuOption> Function(dynamic) options;
final void Function(dynamic)? onTapEntry; final void Function(dynamic)? onTapEntry;
final bool isFullscreen; final bool isFullscreen;
@ -263,7 +263,7 @@ class _TopItemsScreenState extends State<TopItemsScreen> {
class _Content extends StatelessWidget { class _Content extends StatelessWidget {
final List<Map<String, dynamic>> screenData; final List<Map<String, dynamic>> screenData;
final bool? isClient; final bool? isClient;
final List<MenuOption> options; final List<MenuOption> Function(dynamic) options;
final bool withProgressBar; final bool withProgressBar;
final void Function(dynamic)? onTapEntry; final void Function(dynamic)? onTapEntry;
final String Function(dynamic) buildValue; final String Function(dynamic) buildValue;

View file

@ -21,7 +21,7 @@ class TopItemsSection extends StatefulWidget {
final bool withChart; final bool withChart;
final bool withProgressBar; final bool withProgressBar;
final String Function(dynamic) buildValue; final String Function(dynamic) buildValue;
final List<MenuOption> menuOptions; final List<MenuOption> Function(dynamic) menuOptions;
final void Function(dynamic)? onTapEntry; final void Function(dynamic)? onTapEntry;
const TopItemsSection({ const TopItemsSection({
@ -350,7 +350,7 @@ class _ItemsList extends StatelessWidget {
final HomeTopItems type; final HomeTopItems type;
final bool showChart; final bool showChart;
final String Function(dynamic) buildValue; final String Function(dynamic) buildValue;
final List<MenuOption> menuOptions; final List<MenuOption> Function(dynamic) menuOptions;
final void Function(dynamic)? onTapEntry; final void Function(dynamic)? onTapEntry;
const _ItemsList({ const _ItemsList({

View file

@ -34,7 +34,7 @@ class _FilterStatusModalState extends State<FilterStatusModal> {
final logsProvider = Provider.of<LogsProvider>(context); final logsProvider = Provider.of<LogsProvider>(context);
void apply() async { void apply() async {
logsProvider.setSelectedResultStatus(selectedResultStatus); logsProvider.setSelectedResultStatus(value: selectedResultStatus);
Navigator.pop(context); Navigator.pop(context);
} }

View file

@ -262,13 +262,13 @@ class _FiltersList extends StatelessWidget {
FilterChip( FilterChip(
selected: logsProvider.selectedResultStatus == "all", selected: logsProvider.selectedResultStatus == "all",
label: Text(AppLocalizations.of(context)!.all), label: Text(AppLocalizations.of(context)!.all),
onSelected: (_) => logsProvider.setSelectedResultStatus("all") onSelected: (_) => logsProvider.setSelectedResultStatus(value: "all")
), ),
FilterChip( FilterChip(
selected: logsProvider.selectedResultStatus == "processed" || selected: logsProvider.selectedResultStatus == "processed" ||
logsProvider.selectedResultStatus == "whitelisted", logsProvider.selectedResultStatus == "whitelisted",
label: Text(AppLocalizations.of(context)!.allowed), label: Text(AppLocalizations.of(context)!.allowed),
onSelected: (_) => logsProvider.setSelectedResultStatus("processed") onSelected: (_) => logsProvider.setSelectedResultStatus(value: "processed")
), ),
FilterChip( FilterChip(
selected: logsProvider.selectedResultStatus == "blocked" || selected: logsProvider.selectedResultStatus == "blocked" ||
@ -276,7 +276,7 @@ class _FiltersList extends StatelessWidget {
logsProvider.selectedResultStatus == "blocked_parental" || logsProvider.selectedResultStatus == "blocked_parental" ||
logsProvider.selectedResultStatus == "safe_search", logsProvider.selectedResultStatus == "safe_search",
label: Text(AppLocalizations.of(context)!.blocked), label: Text(AppLocalizations.of(context)!.blocked),
onSelected: (_) => logsProvider.setSelectedResultStatus("blocked") onSelected: (_) => logsProvider.setSelectedResultStatus(value: "blocked")
), ),
], ],
), ),

View file

@ -128,7 +128,7 @@ class LogTile extends StatelessWidget {
child: OptionsMenu( child: OptionsMenu(
onTap: (_) => onLogTap(log), onTap: (_) => onLogTap(log),
borderRadius: BorderRadius.circular(28), borderRadius: BorderRadius.circular(28),
options: [ options: (_) => [
if (log.question.name != null) MenuOption( if (log.question.name != null) MenuOption(
title: domainBlocked == true title: domainBlocked == true
? AppLocalizations.of(context)!.unblockDomain ? AppLocalizations.of(context)!.unblockDomain
@ -136,15 +136,15 @@ class LogTile extends StatelessWidget {
icon: domainBlocked == true icon: domainBlocked == true
? Icons.check_rounded ? Icons.check_rounded
: Icons.block_rounded, : Icons.block_rounded,
action: (_) => blockUnblock( action: () => blockUnblock(
domain: log.question.name!, domain: log.question.name!,
newStatus: domainBlocked == true ? 'unblock' : 'block' newStatus: domainBlocked == true ? 'unblock' : 'block'
) )
), ),
MenuOption( if (log.question.name != null) MenuOption(
title: AppLocalizations.of(context)!.copyClipboard, title: AppLocalizations.of(context)!.copyClipboard,
icon: Icons.copy_rounded, 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( child: Container(
@ -306,7 +306,7 @@ class LogTile extends StatelessWidget {
color: Colors.transparent, color: Colors.transparent,
child: OptionsMenu( child: OptionsMenu(
onTap: (_) => onLogTap(log), onTap: (_) => onLogTap(log),
options: [ options: (_) => [
if (log.question.name != null) MenuOption( if (log.question.name != null) MenuOption(
title: domainBlocked == true title: domainBlocked == true
? AppLocalizations.of(context)!.unblockDomain ? AppLocalizations.of(context)!.unblockDomain
@ -314,7 +314,7 @@ class LogTile extends StatelessWidget {
icon: domainBlocked == true icon: domainBlocked == true
? Icons.check_rounded ? Icons.check_rounded
: Icons.block_rounded, : Icons.block_rounded,
action: (_) => blockUnblock( action: () => blockUnblock(
domain: log.question.name!, domain: log.question.name!,
newStatus: domainBlocked == true ? 'unblock' : 'block' newStatus: domainBlocked == true ? 'unblock' : 'block'
) )
@ -322,7 +322,7 @@ class LogTile extends StatelessWidget {
if (log.question.name != null) MenuOption( if (log.question.name != null) MenuOption(
title: AppLocalizations.of(context)!.copyClipboard, title: AppLocalizations.of(context)!.copyClipboard,
icon: Icons.copy_rounded, icon: Icons.copy_rounded,
action: (_) => copyToClipboard( action: () => copyToClipboard(
value: log.question.name!, value: log.question.name!,
successMessage: AppLocalizations.of(context)!.copiedClipboard successMessage: AppLocalizations.of(context)!.copiedClipboard
) )

View file

@ -22,11 +22,11 @@ class LogsListWidget extends StatefulWidget {
final void Function(Log) onLogSelected; final void Function(Log) onLogSelected;
const LogsListWidget({ const LogsListWidget({
Key? key, super.key,
required this.twoColumns, required this.twoColumns,
required this.selectedLog, required this.selectedLog,
required this.onLogSelected, required this.onLogSelected,
}) : super(key: key); });
@override @override
State<LogsListWidget> createState() => _LogsListWidgetState(); State<LogsListWidget> createState() => _LogsListWidgetState();

View file

@ -256,7 +256,7 @@ class LogsListAppBar extends StatelessWidget {
clients: logsProvider.appliedFilters.clients clients: logsProvider.appliedFilters.clients
) )
); );
logsProvider.setSelectedResultStatus('all'); logsProvider.setSelectedResultStatus(value: 'all');
logsProvider.fetchLogs( logsProvider.fetchLogs(
inOffset: 0, inOffset: 0,
responseStatus: 'all' responseStatus: 'all'

View file

@ -156,7 +156,7 @@ class _DnsServerSettingsScreenState extends State<DnsServerSettingsScreen> {
"blocking_mode": blockingMode, "blocking_mode": blockingMode,
"blocking_ipv4": ipv4controller.text, "blocking_ipv4": ipv4controller.text,
"blocking_ipv6": ipv6controller.text, "blocking_ipv6": ipv6controller.text,
"blocked_response_ttl": int.parse(_ttlController.text) "blocked_response_ttl": int.tryParse(_ttlController.text)
}); });
processModal.close(); processModal.close();

View file

@ -39,10 +39,17 @@ class ApiClientV2 {
Future<ApiResponse> getServerVersion() async { Future<ApiResponse> getServerVersion() async {
final result = await HttpRequestClient.get(urlPath: '/status', server: server); final result = await HttpRequestClient.get(urlPath: '/status', server: server);
if (result.successful == true) { if (result.successful == true) {
return ApiResponse( try {
successful: true, return ApiResponse(
content: jsonDecode(result.body!)['version'] 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 { else {
return const ApiResponse(successful: false); return const ApiResponse(successful: false);

View file

@ -11,7 +11,7 @@ import 'package:adguard_home_manager/models/menu_option.dart';
class OptionsMenu extends StatelessWidget { class OptionsMenu extends StatelessWidget {
final Widget child; final Widget child;
final List<MenuOption> options; final List<MenuOption> Function(dynamic) options;
final dynamic value; final dynamic value;
final BorderRadius? borderRadius; final BorderRadius? borderRadius;
final void Function(dynamic)? onTap; final void Function(dynamic)? onTap;
@ -40,11 +40,11 @@ class OptionsMenu extends StatelessWidget {
return Material( return Material(
color: Colors.transparent, color: Colors.transparent,
child: ContextMenuArea( child: ContextMenuArea(
builder: (context) => options.map((opt) => CustomListTile( builder: (context) => options(value).map((opt) => CustomListTile(
title: opt.title, title: opt.title,
icon: opt.icon, icon: opt.icon,
onTap: () { onTap: () {
opt.action(value); opt.action();
Navigator.pop(context); Navigator.pop(context);
}, },
)).toList(), )).toList(),
@ -64,7 +64,7 @@ class OptionsMenu extends StatelessWidget {
} }
class _OptionsModal extends StatelessWidget { class _OptionsModal extends StatelessWidget {
final List<MenuOption> options; final List<MenuOption> Function(dynamic) options;
final dynamic value; final dynamic value;
const _OptionsModal({ const _OptionsModal({
@ -98,12 +98,12 @@ class _OptionsModal extends StatelessWidget {
), ),
child: SingleChildScrollView( child: SingleChildScrollView(
child: Wrap( child: Wrap(
children: options.map((opt) => CustomListTileDialog( children: options(value).map((opt) => CustomListTileDialog(
title: opt.title, title: opt.title,
icon: opt.icon, icon: opt.icon,
onTap: () { onTap: () {
Navigator.pop(context); Navigator.pop(context);
opt.action(value); opt.action();
}, },
)).toList() )).toList()
), ),

View file

@ -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 # 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 # 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. # 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: environment:
sdk: '>=2.18.1 <3.0.0' sdk: '>=2.18.1 <3.0.0'