Merge branch 'beta'

This commit is contained in:
Juan Gilsanz Polo 2023-12-14 15:24:25 +01:00
commit c78e5704bd
23 changed files with 940 additions and 83 deletions

View file

@ -11,28 +11,28 @@ PODS:
- FMDB/standard (2.7.5)
- package_info_plus (0.4.5):
- Flutter
- Sentry/HybridSDK (8.9.1):
- SentryPrivate (= 8.9.1)
- Sentry/HybridSDK (8.15.2):
- SentryPrivate (= 8.15.2)
- sentry_flutter (0.0.1):
- Flutter
- FlutterMacOS
- Sentry/HybridSDK (= 8.9.1)
- SentryPrivate (8.9.1)
- Sentry/HybridSDK (= 8.15.2)
- SentryPrivate (8.15.2)
- sqflite (0.0.3):
- Flutter
- FMDB (>= 2.7.5)
- sqlite3 (3.43.1):
- sqlite3/common (= 3.43.1)
- sqlite3/common (3.43.1)
- sqlite3/fts5 (3.43.1):
- sqlite3 (3.44.0):
- sqlite3/common (= 3.44.0)
- sqlite3/common (3.44.0)
- sqlite3/fts5 (3.44.0):
- sqlite3/common
- sqlite3/perf-threadsafe (3.43.1):
- sqlite3/perf-threadsafe (3.44.0):
- sqlite3/common
- sqlite3/rtree (3.43.1):
- sqlite3/rtree (3.44.0):
- sqlite3/common
- sqlite3_flutter_libs (0.0.1):
- Flutter
- sqlite3 (~> 3.43.0)
- sqlite3 (~> 3.44.0)
- sqlite3/fts5
- sqlite3/perf-threadsafe
- sqlite3/rtree
@ -83,21 +83,21 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/url_launcher_ios/ios"
SPEC CHECKSUMS:
device_info_plus: 7545d84d8d1b896cb16a4ff98c19f07ec4b298ea
device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef
flutter_web_browser: 7bccaafbb0c5b8862afe7bcd158f15557109f61f
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7
Sentry: e3203780941722a1fcfee99e351de14244c7f806
sentry_flutter: 8f0ffd53088e6a4d50c095852c5cad9e4405025c
SentryPrivate: 5e3683390f66611fc7c6215e27645873adb55d13
package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85
Sentry: 6f5742b4c47c17c9adcf265f6f328cf4a0ed1923
sentry_flutter: 2c309a1d4b45e59d02cfa15795705687f1e2081b
SentryPrivate: b2f7996f37781080f04a946eb4e377ff63c64195
sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a
sqlite3: e0a0623a33a20a47cb5921552aebc6e9e437dc91
sqlite3_flutter_libs: 878ccbdcfd7b7cb41a774ec238223d876880c5ec
sqlite3: 6e2d4a4879854d0ec86b476bf3c3e30870bac273
sqlite3_flutter_libs: eb769059df0356dc52ddda040f09cacc9391a7cf
store_checker: 359c5051d9ec30ff0a8fa39eb5ec9df021bb745d
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
url_launcher_ios: bf5ce03e0e2088bad9cc378ea97fa0ed5b49673b
PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3
COCOAPODS: 1.12.1
COCOAPODS: 1.14.3

View file

@ -690,5 +690,28 @@
"topUpstreams": "Top upstreams",
"averageUpstreamResponseTime": "Average upstream response time",
"dhcpNotAvailable": "The DHCP server is not available.",
"osServerInstalledIncompatible": "The OS where the server is installed is not compatible with this feature."
"osServerInstalledIncompatible": "The OS where the server is installed is not compatible with this feature.",
"resetSettings": "Reset settings",
"resetEncryptionSettingsDescription": "Are you sure you want to reset to default values the encryption settings?",
"resettingConfig": "Resetting configuration...",
"configurationResetSuccessfully": "Configuration resetted successfully",
"configurationResetError": "The configuration couldn't be resetted",
"testUpstreamDnsServers": "Test upstream DNS servers",
"errorTestUpstreamDns": "Error when testing upstream DNS servers.",
"useCustomIpEdns": "Use custom IP for EDNS",
"useCustomIpEdnsDescription": "Allow to use custom IP for EDNS",
"sortingOptions": "Sorting options",
"fromHighestToLowest": "From highest to lowest",
"fromLowestToHighest": "From lowest to highest",
"queryLogsAndStatistics": "Query logs and statistics",
"ignoreClientQueryLog": "Ignore this client in query log",
"ignoreClientStatistics": "Ignore this client in statistics",
"savingChanges": "Saving changes...",
"fallbackDnsServers": "Fallback DNS servers",
"fallbackDnsServersDescription": "Configure fallback DNS servers",
"fallbackDnsServersInfo": "List of fallback DNS servers used when upstream DNS servers are not responding. The syntax is the same as in the main upstreams field above.",
"noFallbackDnsAdded": "No fallback DNS servers added.",
"blockedResponseTtl": "Blocked response TTL",
"blockedResponseTtlDescription": "Specifies for how many seconds the clients should cache a filtered response",
"invalidValue": "Invalid value"
}

View file

@ -690,5 +690,28 @@
"topUpstreams": "DNS de subida más frecuentes",
"averageUpstreamResponseTime": "Tiempo promedio de respuesta upstream",
"dhcpNotAvailable": "El servidor DHCP no está disponible.",
"osServerInstalledIncompatible": "El SO donde el servidor está instalado no es compatible con esta característica."
"osServerInstalledIncompatible": "El SO donde el servidor está instalado no es compatible con esta característica.",
"resetSettings": "Resetear configuración",
"resetEncryptionSettingsDescription": "Estás seguro que deseas restaurar a valores por defecto la configuración de encriptación?",
"resettingConfig": "Reseteando configuración...",
"configurationResetSuccessfully": "Configuración reseteada correctamente",
"configurationResetError": "La configuración no ha podido ser reseteada",
"testUpstreamDnsServers": "Probar servidores DNS de subida",
"errorTestUpstreamDns": "Error al probar los servidores DNS de subida.",
"useCustomIpEdns": "Usar IP personalizada para EDNS",
"useCustomIpEdnsDescription": "Permitir usar IP personalizada para EDNS",
"sortingOptions": "Opciones de ordenación",
"fromHighestToLowest": "De mayor a menor",
"fromLowestToHighest": "De menor a mayor",
"queryLogsAndStatistics": "Registro de consultas y estadísticas",
"ignoreClientQueryLog": "Ignorar este cliente en el registro de consultas",
"ignoreClientStatistics": "Ignorar este cliente en las estadísticas",
"savingChanges": "Guardando cambios...",
"fallbackDnsServers": "Servidores DNS alternativos",
"fallbackDnsServersDescription": "Configura los servidores DNS alternativos",
"fallbackDnsServersInfo": "Lista de servidores DNS alternativos utilizados cuando los servidores DNS de subida no responden. La sintaxis es la misma que en el campo de los principales DNS de subida anterior.",
"noFallbackDnsAdded": "No hay servidores DNS alternativos añadidos.",
"blockedResponseTtl": "Respuesta TTL bloqueada",
"blockedResponseTtlDescription": "Especifica durante cuántos segundos los clientes deben almacenar en cache una respuesta filtrada",
"invalidValue": "Valor no válido"
}

View file

@ -87,6 +87,8 @@ class Client {
final bool useGlobalBlockedServices;
final bool useGlobalSettings;
final SafeSearch? safeSearch;
final bool? ignoreQuerylog;
final bool? ignoreStatistics;
Client({
required this.name,
@ -100,6 +102,8 @@ class Client {
required this.useGlobalBlockedServices,
required this.useGlobalSettings,
required this.safeSearch,
required this.ignoreQuerylog,
required this.ignoreStatistics,
});
factory Client.fromJson(Map<String, dynamic> json) => Client(
@ -115,7 +119,9 @@ class Client {
useGlobalSettings: json["use_global_settings"],
safeSearch: json["safe_search"] != null
? SafeSearch.fromJson(json["safe_search"])
: null
: null,
ignoreQuerylog: json["ignore_querylog"],
ignoreStatistics: json["ignore_statistics"]
);
Map<String, dynamic> toJson() => {
@ -130,5 +136,7 @@ class Client {
"safe_search": safeSearch,
"use_global_blocked_services": useGlobalBlockedServices,
"use_global_settings": useGlobalSettings,
"ignore_querylog": ignoreQuerylog,
"ignore_statistics": ignoreStatistics,
};
}

View file

@ -2,10 +2,13 @@ class DnsInfo {
List<String> upstreamDns;
String? upstreamDnsFile;
List<String> bootstrapDns;
List<String>? fallbackDns;
bool protectionEnabled;
int ratelimit;
String blockingMode;
bool ednsCsEnabled;
bool? ednsCsUseCustom;
String? ednsCsCustomIp;
bool dnssecEnabled;
bool disableIpv6;
String? upstreamMode;
@ -19,15 +22,19 @@ class DnsInfo {
String blockingIpv4;
String blockingIpv6;
List<String> defaultLocalPtrUpstreams;
int? blockedResponseTtl;
DnsInfo({
required this.upstreamDns,
required this.upstreamDnsFile,
required this.bootstrapDns,
required this.fallbackDns,
required this.protectionEnabled,
required this.ratelimit,
required this.blockingMode,
required this.ednsCsEnabled,
required this.ednsCsUseCustom,
required this.ednsCsCustomIp,
required this.dnssecEnabled,
required this.disableIpv6,
required this.upstreamMode,
@ -41,16 +48,20 @@ class DnsInfo {
required this.blockingIpv4,
required this.blockingIpv6,
required this.defaultLocalPtrUpstreams,
required this.blockedResponseTtl,
});
factory DnsInfo.fromJson(Map<String, dynamic> json) => DnsInfo(
upstreamDns: json["upstream_dns"] != null ? List<String>.from(json["upstream_dns"].map((x) => x)) : [],
upstreamDnsFile: json["upstream_dns_file"],
bootstrapDns: json["bootstrap_dns"] != null ? List<String>.from(json["bootstrap_dns"].map((x) => x)) : [],
fallbackDns: json["fallback_dns"] != null ? List<String>.from(json["fallback_dns"].map((x) => x)) : [],
protectionEnabled: json["protection_enabled"],
ratelimit: json["ratelimit"],
blockingMode: json["blocking_mode"],
ednsCsEnabled: json["edns_cs_enabled"],
ednsCsUseCustom: json["edns_cs_use_custom"],
ednsCsCustomIp: json["edns_cs_custom_ip"],
dnssecEnabled: json["dnssec_enabled"],
disableIpv6: json["disable_ipv6"],
upstreamMode: json["upstream_mode"],
@ -64,16 +75,20 @@ class DnsInfo {
blockingIpv4: json["blocking_ipv4"],
blockingIpv6: json["blocking_ipv6"],
defaultLocalPtrUpstreams: json["default_local_ptr_upstreams"] != null ? List<String>.from(json["default_local_ptr_upstreams"].map((x) => x)) : [],
blockedResponseTtl: json["blocked_response_ttl"]
);
Map<String, dynamic> toJson() => {
"upstream_dns": List<dynamic>.from(upstreamDns.map((x) => x)),
"upstream_dns_file": upstreamDnsFile,
"bootstrap_dns": List<dynamic>.from(bootstrapDns.map((x) => x)),
"fallback_dns": List<dynamic>.from(bootstrapDns.map((x) => x)),
"protection_enabled": protectionEnabled,
"ratelimit": ratelimit,
"blocking_mode": blockingMode,
"edns_cs_enabled": ednsCsEnabled,
"edns_cs_use_custom": ednsCsUseCustom,
"edns_cs_custom_ip": ednsCsCustomIp,
"dnssec_enabled": dnssecEnabled,
"disable_ipv6": disableIpv6,
"upstream_mode": upstreamMode,
@ -87,5 +102,6 @@ class DnsInfo {
"blocking_ipv4": blockingIpv4,
"blocking_ipv6": blockingIpv6,
"default_local_ptr_upstreams": List<dynamic>.from(defaultLocalPtrUpstreams.map((x) => x)),
"blocked_response_ttl": blockedResponseTtl
};
}

View file

@ -112,6 +112,22 @@ class DnsProvider with ChangeNotifier {
}
}
Future<ApiResponse> saveFallbackDnsConfig(Map<String, dynamic> value) async {
final result = await _serversProvider!.apiClient2!.setDnsConfig(
data: value
);
if (result.successful == true) {
DnsInfo data = dnsInfo!;
data.bootstrapDns = List<String>.from(value['fallback_dns']);
setDnsInfoData(data);
return result;
}
else {
return result;
}
}
Future<ApiResponse> saveCacheCacheConfig(Map<String, dynamic> value) async {
final result = await _serversProvider!.apiClient2!.setDnsConfig(
data: value
@ -145,6 +161,7 @@ class DnsProvider with ChangeNotifier {
data.blockingMode = value['blocking_mode'];
data.blockingIpv4 = value['blocking_ipv4'];
data.blockingIpv6 = value['blocking_ipv6'];
data.blockedResponseTtl = value['blocked_response_ttl'];
setDnsInfoData(data);
return result;
}

View file

@ -29,13 +29,13 @@ class AddedList extends StatefulWidget {
final bool splitView;
const AddedList({
Key? key,
super.key,
required this.scrollController,
required this.data,
required this.onClientSelected,
this.selectedClient,
required this.splitView
}) : super(key: key);
});
@override
State<AddedList> createState() => _AddedListState();
@ -75,7 +75,7 @@ class _AddedListState extends State<AddedList> {
void confirmEditClient(Client client) async {
ProcessModal processModal = ProcessModal();
processModal.open(AppLocalizations.of(context)!.addingClient);
processModal.open(AppLocalizations.of(context)!.savingChanges);
final result = await clientsProvider.editClient(client);

View file

@ -1,5 +1,6 @@
import 'dart:io';
import 'package:adguard_home_manager/widgets/custom_switch_list_tile.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
@ -45,6 +46,10 @@ class ClientForm extends StatelessWidget {
final void Function(bool) updateEnableSafeSearch;
final void Function(SafeSearch) updateSafeSearch;
final void Function(bool) updateUseGlobalSettingsServices;
final bool ignoreClientQueryLog;
final void Function(bool) updateIgnoreClientQueryLog;
final bool ignoreClientStatistics;
final void Function(bool) updateIgnoreClientStatistics;
const ClientForm({
super.key,
@ -75,6 +80,10 @@ class ClientForm extends StatelessWidget {
required this.updateEnableSafeSearch,
required this.updateSafeSearch,
required this.updateUseGlobalSettingsServices,
required this.ignoreClientQueryLog,
required this.ignoreClientStatistics,
required this.updateIgnoreClientQueryLog,
required this.updateIgnoreClientStatistics,
});
@override
@ -217,6 +226,28 @@ class ClientForm extends StatelessWidget {
)
: null,
),
SectionLabel(
label: AppLocalizations.of(context)!.queryLogsAndStatistics,
padding: const EdgeInsets.all(24),
),
CustomSwitchListTile(
title: AppLocalizations.of(context)!.ignoreClientQueryLog,
value: ignoreClientQueryLog,
onChanged: updateIgnoreClientQueryLog,
padding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 4
),
),
CustomSwitchListTile(
title: AppLocalizations.of(context)!.ignoreClientStatistics,
value: ignoreClientStatistics,
onChanged: updateIgnoreClientStatistics,
padding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 4
),
),
SectionLabel(
label: AppLocalizations.of(context)!.blockedServices,
padding: const EdgeInsets.all(24),

View file

@ -73,6 +73,9 @@ class _ClientScreenState extends State<ClientScreen> {
List<ControllerListItem> upstreamServers = [];
bool _ignoreClientQueryLog = false;
bool _ignoreClientStatistics = false;
void enableDisableGlobalSettingsFiltering() {
if (useGlobalSettingsFiltering == true) {
setState(() {
@ -120,6 +123,8 @@ class _ClientScreenState extends State<ClientScreen> {
id: uuid.v4(),
controller: TextEditingController(text: e)
)).toList();
_ignoreClientQueryLog = widget.client!.ignoreQuerylog ?? false;
_ignoreClientStatistics = widget.client!.ignoreStatistics ?? false;
}
super.initState();
}
@ -140,7 +145,9 @@ class _ClientScreenState extends State<ClientScreen> {
useGlobalBlockedServices: useGlobalSettingsServices,
blockedServices: blockedServices,
upstreams: List<String>.from(upstreamServers.map((e) => e.controller.text)),
tags: selectedTags
tags: selectedTags,
ignoreQuerylog: _ignoreClientQueryLog,
ignoreStatistics: _ignoreClientStatistics
);
widget.onConfirm(client);
}
@ -214,6 +221,10 @@ class _ClientScreenState extends State<ClientScreen> {
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),
),
),
),
@ -281,6 +292,10 @@ class _ClientScreenState extends State<ClientScreen> {
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),
),
)
],

View file

@ -1,3 +1,5 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
@ -16,9 +18,9 @@ class HomeAppBar extends StatelessWidget {
final bool innerBoxScrolled;
const HomeAppBar({
Key? key,
super.key,
required this.innerBoxScrolled
}) : super(key: key);
});
@override
Widget build(BuildContext context) {
@ -118,6 +120,14 @@ class HomeAppBar extends StatelessWidget {
],
),
actions: [
if (!(Platform.isAndroid || Platform.isIOS)) ...[
IconButton(
onPressed: () => statusProvider.getServerStatus(),
icon: const Icon(Icons.refresh_rounded),
tooltip: AppLocalizations.of(context)!.refresh,
),
const SizedBox(width: 8),
],
PopupMenuButton(
itemBuilder: (context) => [
PopupMenuItem(

View file

@ -15,6 +15,8 @@ import 'package:adguard_home_manager/constants/enums.dart';
import 'package:adguard_home_manager/functions/number_format.dart';
import 'package:adguard_home_manager/providers/status_provider.dart';
enum _SortingOptions { highestToLowest, lowestToHighest }
class TopItemsScreen extends StatefulWidget {
final HomeTopItems type;
final String title;
@ -44,6 +46,7 @@ class TopItemsScreen extends StatefulWidget {
}
class _TopItemsScreenState extends State<TopItemsScreen> {
_SortingOptions _sortingOptions = _SortingOptions.highestToLowest;
bool searchActive = false;
final TextEditingController searchController = TextEditingController();
@ -68,6 +71,10 @@ class _TopItemsScreenState extends State<TopItemsScreen> {
for (var element in data) {
total = total + double.parse(element.values.toList()[0].toString());
}
final sortedValues = _sortingOptions == _SortingOptions.lowestToHighest
? screenData.reversed.toList()
: screenData.toList();
if (widget.isFullscreen == true) {
return Dialog.fullscreen(
@ -119,6 +126,53 @@ class _TopItemsScreenState extends State<TopItemsScreen> {
icon: const Icon(Icons.clear_rounded),
tooltip: AppLocalizations.of(context)!.clearSearch,
),
PopupMenuButton(
icon: const Icon(Icons.sort_rounded),
itemBuilder: (context) => [
PopupMenuItem(
onTap: () => setState(() => _sortingOptions = _SortingOptions.highestToLowest),
child: Row(
children: [
const Icon(Icons.arrow_downward_rounded),
const SizedBox(width: 8),
Expanded(
child: Text(AppLocalizations.of(context)!.fromHighestToLowest)
),
const SizedBox(width: 16),
Icon(
_sortingOptions == _SortingOptions.highestToLowest
? Icons.radio_button_checked_rounded
: Icons.radio_button_unchecked_rounded,
color: _sortingOptions == _SortingOptions.highestToLowest
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.onSurfaceVariant,
)
],
)
),
PopupMenuItem(
onTap: () => setState(() => _sortingOptions = _SortingOptions.lowestToHighest),
child: Row(
children: [
const Icon(Icons.arrow_upward_rounded),
const SizedBox(width: 8),
Expanded(
child: Text(AppLocalizations.of(context)!.fromLowestToHighest)
),
const SizedBox(width: 16),
Icon(
_sortingOptions == _SortingOptions.lowestToHighest
? Icons.radio_button_checked_rounded
: Icons.radio_button_unchecked_rounded,
color: _sortingOptions == _SortingOptions.lowestToHighest
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.onSurfaceVariant,
)
],
)
),
],
),
const SizedBox(width: 8)
],
),
@ -128,7 +182,7 @@ class _TopItemsScreenState extends State<TopItemsScreen> {
isClient: widget.isClient,
onTapEntry: widget.onTapEntry,
options: widget.options,
screenData: screenData,
screenData: sortedValues,
total: total,
withProgressBar: widget.withProgressBar,
),
@ -193,7 +247,7 @@ class _TopItemsScreenState extends State<TopItemsScreen> {
isClient: widget.isClient,
onTapEntry: widget.onTapEntry,
options: widget.options,
screenData: screenData,
screenData: sortedValues,
total: total,
withProgressBar: widget.withProgressBar,
),

View file

@ -49,6 +49,7 @@ class _AccessSettingsState extends State<AccessSettings> with TickerProviderStat
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
sliver: SliverSafeArea(
top: false,
bottom: false,
sliver: SliverAppBar(
title: Text(AppLocalizations.of(context)!.accessSettings),
pinned: true,
@ -56,10 +57,7 @@ class _AccessSettingsState extends State<AccessSettings> with TickerProviderStat
centerTitle: false,
forceElevated: innerBoxIsScrolled,
surfaceTintColor: isDesktop(width) ? Colors.transparent : null,
bottom: PreferredSize(
preferredSize: const Size(double.maxFinite, 50),
child: _Tabs(tabController: _tabController)
)
bottom: _Tabs(tabController: _tabController)
),
),
)
@ -92,7 +90,7 @@ class _AccessSettingsState extends State<AccessSettings> with TickerProviderStat
}
}
class _Tabs extends StatelessWidget {
class _Tabs extends StatelessWidget implements PreferredSizeWidget {
final TabController tabController;
const _Tabs({
@ -137,6 +135,9 @@ class _Tabs extends StatelessWidget {
]
);
}
@override
Size get preferredSize => const Size.fromHeight(kToolbarHeight);
}
class _TabsView extends StatelessWidget {

View file

@ -5,6 +5,8 @@ import 'package:flutter_split_view/flutter_split_view.dart';
import 'package:provider/provider.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:adguard_home_manager/screens/settings/dns/fallback_dns.dart';
import 'package:adguard_home_manager/screens/settings/dns/test_upstream_dns_modal.dart';
import 'package:adguard_home_manager/screens/settings/dns/clear_dns_cache_dialog.dart';
import 'package:adguard_home_manager/screens/settings/dns/cache_config.dart';
import 'package:adguard_home_manager/screens/settings/dns/dns_server_settings.dart';
@ -84,6 +86,14 @@ class _DnsSettingsState extends State<DnsSettings> {
title: Text(AppLocalizations.of(context)!.dnsSettings),
surfaceTintColor: isDesktop(width) ? Colors.transparent : null,
actions: [
IconButton(
onPressed: () => showDialog(
context: context,
builder: (ctx) => const TestUpstreamDnsModal()
),
icon: const Icon(Icons.upload_rounded),
tooltip: AppLocalizations.of(context)!.testUpstreamDnsServers,
),
PopupMenuButton(
itemBuilder: (context) => [
PopupMenuItem(
@ -158,6 +168,12 @@ class _DnsSettingsState extends State<DnsSettings> {
onTap: () => navigate(const BootstrapDnsScreen()),
icon: Icons.dns_rounded,
),
if (dnsProvider.dnsInfo!.fallbackDns != null) CustomListTile(
title: AppLocalizations.of(context)!.fallbackDnsServers,
subtitle: AppLocalizations.of(context)!.fallbackDnsServersDescription,
onTap: () => navigate(const FallbackDnsScreen()),
icon: Icons.alt_route_rounded,
),
CustomListTile(
title: AppLocalizations.of(context)!.privateReverseDnsServers,
subtitle: AppLocalizations.of(context)!.privateReverseDnsDescription,

View file

@ -1,5 +1,6 @@
// ignore_for_file: use_build_context_synchronously
import 'package:expandable/expandable.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
@ -24,7 +25,12 @@ class DnsServerSettingsScreen extends StatefulWidget {
class _DnsServerSettingsScreenState extends State<DnsServerSettingsScreen> {
final TextEditingController limitRequestsController = TextEditingController();
String? limitRequestsError;
final _expandableCustomEdns = ExpandableController();
final _expandableEdnsIp = ExpandableController();
bool enableEdns = false;
bool useCustomIpEdns = false;
final _customIpEdnsController = TextEditingController();
String? ednsIpError;
bool enableDnssec = false;
bool disableIpv6Resolving = false;
@ -35,6 +41,9 @@ class _DnsServerSettingsScreenState extends State<DnsServerSettingsScreen> {
final TextEditingController ipv6controller = TextEditingController();
String? ipv6error;
final _ttlController = TextEditingController();
String? _ttlError;
bool isDataValid = false;
void validateIpv4(String value) {
@ -48,6 +57,17 @@ class _DnsServerSettingsScreenState extends State<DnsServerSettingsScreen> {
validateData();
}
void validateEdns(String value) {
RegExp ipAddress = RegExp(r'^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)(\.(?!$)|$)){4}$');
if (ipAddress.hasMatch(value) == true) {
setState(() => ednsIpError = null);
}
else {
setState(() => ednsIpError = AppLocalizations.of(context)!.ipNotValid);
}
validateData();
}
void validateIpv6(String value) {
RegExp ipAddress = RegExp(r'(?:^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}$)|(?:^(?:(?:[a-fA-F\d]{1,4}:){7}(?:[a-fA-F\d]{1,4}|:)|(?:[a-fA-F\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|:[a-fA-F\d]{1,4}|:)|(?:[a-fA-F\d]{1,4}:){5}(?::(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,2}|:)|(?:[a-fA-F\d]{1,4}:){4}(?:(?::[a-fA-F\d]{1,4}){0,1}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,3}|:)|(?:[a-fA-F\d]{1,4}:){3}(?:(?::[a-fA-F\d]{1,4}){0,2}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,4}|:)|(?:[a-fA-F\d]{1,4}:){2}(?:(?::[a-fA-F\d]{1,4}){0,3}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,5}|:)|(?:[a-fA-F\d]{1,4}:){1}(?:(?::[a-fA-F\d]{1,4}){0,4}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,6}|:)|(?::(?:(?::[a-fA-F\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,7}|:)))(?:%[0-9a-zA-Z]{1,})?$)');
if (ipAddress.hasMatch(value) == true) {
@ -72,7 +92,9 @@ class _DnsServerSettingsScreenState extends State<DnsServerSettingsScreen> {
ipv6controller.text != '' &&
ipv6error == null
)
) == true
) == true &&
ednsIpError == null &&
_ttlError == null
) {
setState(() => isDataValid = true);
}
@ -81,18 +103,36 @@ class _DnsServerSettingsScreenState extends State<DnsServerSettingsScreen> {
}
}
void validateNumber(String value) {
final regex = RegExp(r'^(\d)+$');
if (regex.hasMatch(value) == true) {
setState(() => _ttlError = null);
}
else {
setState(() => _ttlError = AppLocalizations.of(context)!.invalidValue);
}
validateData();
}
@override
void initState() {
final dnsProvider = Provider.of<DnsProvider>(context, listen: false);
limitRequestsController.text = dnsProvider.dnsInfo!.ratelimit.toString();
enableEdns = dnsProvider.dnsInfo!.ednsCsEnabled;
useCustomIpEdns = dnsProvider.dnsInfo!.ednsCsUseCustom ?? false;
_customIpEdnsController.text = dnsProvider.dnsInfo!.ednsCsCustomIp ?? "";
if (dnsProvider.dnsInfo!.ednsCsEnabled == true) _expandableCustomEdns.toggle();
if (dnsProvider.dnsInfo!.ednsCsUseCustom == true) _expandableEdnsIp.toggle();
enableDnssec = dnsProvider.dnsInfo!.dnssecEnabled;
disableIpv6Resolving = dnsProvider.dnsInfo!.disableIpv6;
blockingMode = dnsProvider.dnsInfo!.blockingMode;
ipv4controller.text = dnsProvider.dnsInfo!.blockingIpv4;
ipv6controller.text = dnsProvider.dnsInfo!.blockingIpv6;
isDataValid = true;
_ttlController.text = dnsProvider.dnsInfo!.blockedResponseTtl != null
? dnsProvider.dnsInfo!.blockedResponseTtl.toString()
: "";
super.initState();
}
@ -109,11 +149,14 @@ class _DnsServerSettingsScreenState extends State<DnsServerSettingsScreen> {
final result = await dnsProvider.saveDnsServerConfig({
"ratelimit": int.parse(limitRequestsController.text),
"edns_cs_enabled": enableEdns,
"edns_cs_use_custom": useCustomIpEdns,
"edns_cs_custom_ip": _customIpEdnsController.text,
"dnssec_enabled": enableDnssec,
"disable_ipv6": disableIpv6Resolving,
"blocking_mode": blockingMode,
"blocking_ipv4": ipv4controller.text,
"blocking_ipv6": ipv6controller.text
"blocking_ipv6": ipv6controller.text,
"blocked_response_ttl": int.parse(_ttlController.text)
});
processModal.close();
@ -200,10 +243,78 @@ class _DnsServerSettingsScreenState extends State<DnsServerSettingsScreen> {
const SizedBox(height: 10),
CustomSwitchListTile(
value: enableEdns,
onChanged: (value) => setState(() => enableEdns = value),
onChanged: (value) => setState(() {
enableEdns = value;
_expandableCustomEdns.toggle();
if (value == false) {
useCustomIpEdns = false;
if (_expandableEdnsIp.expanded == true) _expandableEdnsIp.toggle();
_customIpEdnsController.text = "";
ednsIpError = null;
}
validateData();
}),
title: AppLocalizations.of(context)!.enableEdns,
subtitle: AppLocalizations.of(context)!.enableEdnsDescription,
),
ExpandableNotifier(
controller: _expandableCustomEdns,
child: Expandable(
collapsed: const SizedBox(),
expanded: Column(
children: [
CustomSwitchListTile(
padding: const EdgeInsets.only(
left: 50,
top: 12,
bottom: 12,
right: 16
),
value: useCustomIpEdns,
onChanged: (value) => setState(() {
useCustomIpEdns = value;
_expandableEdnsIp.toggle();
if (useCustomIpEdns == false) {
_customIpEdnsController.text = "";
ednsIpError = null;
}
validateData();
}),
title: AppLocalizations.of(context)!.useCustomIpEdns,
subtitle: AppLocalizations.of(context)!.useCustomIpEdnsDescription,
),
ExpandableNotifier(
controller: _expandableEdnsIp,
child: Expandable(
collapsed: const SizedBox(),
expanded: Padding(
padding: const EdgeInsets.only(
top: 16,
bottom: 16,
right: 16,
left: 70
),
child: TextFormField(
controller: _customIpEdnsController,
onChanged: validateEdns,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.link_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
errorText: ednsIpError,
labelText: AppLocalizations.of(context)!.ipAddress,
),
),
),
)
),
],
),
)
),
CustomSwitchListTile(
value: enableDnssec,
onChanged: (value) => setState(() => enableDnssec = value),
@ -300,8 +411,28 @@ class _DnsServerSettingsScreenState extends State<DnsServerSettingsScreen> {
keyboardType: TextInputType.number,
),
),
const SizedBox(height: 30)
]
const SizedBox(height: 30),
],
Padding(
padding: const EdgeInsets.all(16),
child: TextFormField(
controller: _ttlController,
onChanged: validateNumber,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.timer_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
errorText: _ttlError,
labelText: AppLocalizations.of(context)!.blockedResponseTtl,
helperText: AppLocalizations.of(context)!.blockedResponseTtlDescription,
helperMaxLines: 2,
),
keyboardType: TextInputType.number,
),
),
],
),
),

View file

@ -0,0 +1,224 @@
// ignore_for_file: use_build_context_synchronously
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:adguard_home_manager/functions/desktop_mode.dart';
import 'package:adguard_home_manager/classes/process_modal.dart';
import 'package:adguard_home_manager/providers/dns_provider.dart';
import 'package:adguard_home_manager/functions/snackbar.dart';
import 'package:adguard_home_manager/providers/app_config_provider.dart';
class FallbackDnsScreen extends StatefulWidget {
const FallbackDnsScreen({super.key});
@override
State<FallbackDnsScreen> createState() => _FallbackDnsScreenState();
}
class _FallbackDnsScreenState extends State<FallbackDnsScreen> {
List<Map<String, dynamic>> fallbackControllers = [];
bool validValues = false;
void validateIp(Map<String, dynamic> field, String value) {
RegExp ipAddress = RegExp(r'(?:^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}$)|(?:^(?:(?:[a-fA-F\d]{1,4}:){7}(?:[a-fA-F\d]{1,4}|:)|(?:[a-fA-F\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|:[a-fA-F\d]{1,4}|:)|(?:[a-fA-F\d]{1,4}:){5}(?::(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,2}|:)|(?:[a-fA-F\d]{1,4}:){4}(?:(?::[a-fA-F\d]{1,4}){0,1}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,3}|:)|(?:[a-fA-F\d]{1,4}:){3}(?:(?::[a-fA-F\d]{1,4}){0,2}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,4}|:)|(?:[a-fA-F\d]{1,4}:){2}(?:(?::[a-fA-F\d]{1,4}){0,3}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,5}|:)|(?:[a-fA-F\d]{1,4}:){1}(?:(?::[a-fA-F\d]{1,4}){0,4}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,6}|:)|(?::(?:(?::[a-fA-F\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,7}|:)))(?:%[0-9a-zA-Z]{1,})?$)');
if (ipAddress.hasMatch(value) == true) {
setState(() => field['error'] = null);
}
else {
setState(() => field['error'] = AppLocalizations.of(context)!.invalidIp);
}
checkValidValues();
}
void checkValidValues() {
if (
fallbackControllers.isNotEmpty &&
fallbackControllers.every((element) => element['controller'].text != '') &&
fallbackControllers.every((element) => element['error'] == null)
) {
setState(() => validValues = true);
}
else {
setState(() => validValues = false);
}
}
@override
void initState() {
final dnsProvider = Provider.of<DnsProvider>(context, listen: false);
for (var item in dnsProvider.dnsInfo!.fallbackDns!) {
final controller = TextEditingController();
controller.text = item;
fallbackControllers.add({
'controller': controller,
'error': null
});
}
validValues = true;
super.initState();
}
@override
Widget build(BuildContext context) {
final dnsProvider = Provider.of<DnsProvider>(context);
final appConfigProvider = Provider.of<AppConfigProvider>(context);
final width = MediaQuery.of(context).size.width;
void saveData() async {
ProcessModal processModal = ProcessModal();
processModal.open(AppLocalizations.of(context)!.savingConfig);
final result = await dnsProvider.saveFallbackDnsConfig({
"fallback_dns": fallbackControllers.map((e) => e['controller'].text).toList(),
});
processModal.close();
if (result.successful == true) {
showSnacbkar(
appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.dnsConfigSaved,
color: Colors.green
);
}
else if (result.successful == false && result.statusCode == 400) {
showSnacbkar(
appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.someValueNotValid,
color: Colors.red
);
}
else {
showSnacbkar(
appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.dnsConfigNotSaved,
color: Colors.red
);
}
}
return Scaffold(
appBar: AppBar(
title: Text(AppLocalizations.of(context)!.fallbackDnsServers),
surfaceTintColor: isDesktop(width) ? Colors.transparent : null,
actions: [
IconButton(
onPressed: validValues == true
? () => saveData()
: null,
icon: const Icon(Icons.save_rounded),
tooltip: AppLocalizations.of(context)!.save,
),
const SizedBox(width: 10)
],
),
body: SafeArea(
child: ListView(
padding: const EdgeInsets.only(top: 10),
children: [
Card(
margin: const EdgeInsets.only(
left: 16, right: 16, bottom: 20
),
child: Padding(
padding: const EdgeInsets.all(20),
child: Row(
children: [
Icon(
Icons.info_rounded,
color: Theme.of(context).listTileTheme.iconColor,
),
const SizedBox(width: 20),
Flexible(
child: Text(
AppLocalizations.of(context)!.fallbackDnsServersInfo,
style: TextStyle(
color: Theme.of(context).colorScheme.onSurface
),
)
)
],
),
),
),
const SizedBox(height: 10),
if (fallbackControllers.isEmpty) Column(
children: [
Padding(
padding: const EdgeInsets.all(10),
child: Center(
child: Text(
AppLocalizations.of(context)!.noFallbackDnsAdded,
style: TextStyle(
color: Theme.of(context).colorScheme.onSurfaceVariant,
fontSize: 16
),
),
),
),
const SizedBox(height: 20),
],
),
...fallbackControllers.map((c) => Padding(
padding: const EdgeInsets.only(
left: 16, right: 6, bottom: 20
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: TextFormField(
controller: c['controller'],
onChanged: (value) => validateIp(c, value),
decoration: InputDecoration(
prefixIcon: const Icon(Icons.dns_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
errorText: c['error'],
labelText: AppLocalizations.of(context)!.dnsServer,
)
),
),
const SizedBox(width: 8),
IconButton(
onPressed: () {
setState(() => fallbackControllers = fallbackControllers.where((con) => con != c).toList());
checkValidValues();
},
icon: const Icon(Icons.remove_circle_outline)
)
],
),
)),
Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton.icon(
onPressed: () {
setState(() => fallbackControllers.add({
'controller': TextEditingController(),
'error': null
}));
checkValidValues();
},
icon: const Icon(Icons.add),
label: Text(AppLocalizations.of(context)!.addItem)
),
],
),
const SizedBox(height: 20)
],
),
),
);
}
}

View file

@ -0,0 +1,168 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:adguard_home_manager/constants/enums.dart';
import 'package:adguard_home_manager/providers/dns_provider.dart';
import 'package:adguard_home_manager/providers/servers_provider.dart';
class _Item {
final String url;
final bool value;
const _Item({
required this.url,
required this.value
});
}
class TestUpstreamDnsModal extends StatefulWidget {
const TestUpstreamDnsModal({super.key});
@override
State<TestUpstreamDnsModal> createState() => _TestUpstreamDnsModalState();
}
class _TestUpstreamDnsModalState extends State<TestUpstreamDnsModal> {
LoadStatus loadStatus = LoadStatus.loading;
List<_Item>? values;
void checkDns() async {
final dnsProvider = Provider.of<DnsProvider>(context, listen: false);
final result = await Provider.of<ServersProvider>(context, listen: false).apiClient2!.testUpstreamDns(
body: {
"bootstrap_dns": dnsProvider.dnsInfo!.bootstrapDns,
"fallback_dns": [],
"private_upstream": dnsProvider.dnsInfo!.defaultLocalPtrUpstreams,
"upstream_dns": dnsProvider.dnsInfo!.upstreamDns
}
);
if (!mounted) return;
if (result.successful == true) {
setState(() {
values = List<_Item>.from(
(result.content as Map<String, dynamic>).entries.map((e) => _Item(
url: e.key,
value: e.value == "OK" ? true : false
))
);
loadStatus = LoadStatus.loaded;
});
}
else {
setState(() => loadStatus = LoadStatus.error);
}
}
@override
void initState() {
checkDns();
super.initState();
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Column(
children: [
Icon(
Icons.upload_rounded,
size: 24,
color: Theme.of(context).colorScheme.onSurfaceVariant
),
const SizedBox(height: 16),
Text(
AppLocalizations.of(context)!.testUpstreamDnsServers,
textAlign: TextAlign.center,
style: TextStyle(
color: Theme.of(context).colorScheme.onSurface
),
)
],
),
content: ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 500
),
child: Builder(
builder: (context) {
switch (loadStatus) {
case LoadStatus.loading:
return const Padding(
padding: EdgeInsets.symmetric(vertical: 16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator()
],
),
);
case LoadStatus.loaded:
return SingleChildScrollView(
child: Wrap(
children: values!.map((v) => Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
v.url,
overflow: TextOverflow.ellipsis,
),
),
...[
const SizedBox(width: 8),
if (v.value == true) const Icon(
Icons.check_circle_rounded,
color: Colors.green,
size: 16,
),
if (v.value == false) const Icon(
Icons.cancel_rounded,
color: Colors.red,
)
]
],
),
)).toList(),
),
);
case LoadStatus.error:
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.error_rounded,
color: Theme.of(context).colorScheme.onSurfaceVariant,
size: 30,
),
const SizedBox(height: 16),
Text(
AppLocalizations.of(context)!.errorTestUpstreamDns,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 18,
color: Theme.of(context).colorScheme.onSurfaceVariant
),
)
],
);
default:
return const SizedBox();
}
},
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(AppLocalizations.of(context)!.close)
),
],
);
}
}

View file

@ -186,7 +186,7 @@ class _UpstreamDnsScreenState extends State<UpstreamDnsScreen> {
icon: const Icon(Icons.save_rounded),
tooltip: AppLocalizations.of(context)!.save,
),
const SizedBox(width: 10)
const SizedBox(width: 8)
],
),
body: SafeArea(

View file

@ -6,8 +6,8 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:adguard_home_manager/widgets/section_label.dart';
import 'package:adguard_home_manager/widgets/custom_switch_list_tile.dart';
import 'package:adguard_home_manager/screens/settings/encryption/config_error_modal.dart';
import 'package:adguard_home_manager/screens/settings/encryption/status.dart';
import 'package:adguard_home_manager/screens/settings/encryption/reset_settings_modal.dart';
import 'package:adguard_home_manager/screens/settings/encryption/custom_text_field.dart';
import 'package:adguard_home_manager/screens/settings/encryption/master_switch.dart';
import 'package:adguard_home_manager/screens/settings/encryption/encryption_functions.dart';
@ -254,6 +254,45 @@ class _EncryptionSettingsState extends State<EncryptionSettings> {
}
}
void resetSettings() async {
ProcessModal processModal = ProcessModal();
processModal.open(AppLocalizations.of(context)!.resettingConfig);
final result = await serversProvider.apiClient2!.saveEncryptionSettings(
data: {
"enabled": false,
"server_name": "",
"force_https": false,
"port_https": 443,
"port_dns_over_tls": 853,
"port_dns_over_quic": 853,
"certificate_chain": "",
"private_key": "",
"private_key_saved": false,
"certificate_path": "",
"private_key_path": "",
}
);
if (!mounted) return;
processModal.close();
if (result.successful == true) {
showSnacbkar(
appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.configurationResetSuccessfully,
color: Colors.green
);
}
else {
showSnacbkar(
appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.configurationResetError,
color: Colors.red
);
}
}
return Scaffold(
appBar: AppBar(
title: Text(AppLocalizations.of(context)!.encryptionSettings),
@ -261,17 +300,12 @@ class _EncryptionSettingsState extends State<EncryptionSettings> {
centerTitle: false,
actions: [
IconButton(
onPressed: certKeyValidApi == 2 && (validDataError != null || encryptionResultMessage != null)
? () => {
showDialog(
context: context,
builder: (context) => EncryptionErrorModal(
error: validDataError ?? encryptionResultMessage ?? AppLocalizations.of(context)!.unknownError
)
)
} : null,
icon: generateStatus(context, appConfigProvider, localValidationValid, certKeyValidApi, formEdited),
tooltip: generateStatusString(context, localValidationValid, certKeyValidApi)
onPressed: () => showDialog(
context: context,
builder: (ctx) => ResetSettingsModal(onConfirm: resetSettings)
),
icon: const Icon(Icons.restore_rounded),
tooltip: AppLocalizations.of(context)!.resetSettings,
),
IconButton(
onPressed: localValidationValid ?
@ -311,6 +345,26 @@ class _EncryptionSettingsState extends State<EncryptionSettings> {
case LoadStatus.loaded:
return ListView(
children: [
if (certKeyValidApi == 2 && (validDataError != null || encryptionResultMessage != null)) Card(
margin: const EdgeInsets.all(16),
color: Colors.red.withOpacity(0.2),
elevation: 0,
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Icon(
Icons.error_rounded,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
const SizedBox(width: 16),
Expanded(
child: Text(validDataError ?? encryptionResultMessage ?? AppLocalizations.of(context)!.unknownError)
)
],
),
),
),
EncryptionMasterSwitch(
value: enabled,
onChange: (value) {

View file

@ -0,0 +1,52 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class ResetSettingsModal extends StatelessWidget {
final void Function() onConfirm;
const ResetSettingsModal({
super.key,
required this.onConfirm,
});
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Column(
children: [
Icon(
Icons.restore_rounded,
size: 24,
color: Theme.of(context).colorScheme.onSurfaceVariant
),
const SizedBox(height: 16),
Text(
AppLocalizations.of(context)!.resetSettings,
style: TextStyle(
color: Theme.of(context).colorScheme.onSurface
),
)
],
),
content: Text(
AppLocalizations.of(context)!.resetEncryptionSettingsDescription,
style: TextStyle(
color: Theme.of(context).colorScheme.onSurfaceVariant
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(AppLocalizations.of(context)!.cancel)
),
TextButton(
onPressed: () {
onConfirm();
Navigator.pop(context);
},
child: Text(AppLocalizations.of(context)!.confirm)
),
],
);
}
}

View file

@ -853,4 +853,18 @@ class ApiClientV2 {
);
return ApiResponse(successful: result.successful);
}
Future<ApiResponse> testUpstreamDns({
required Map<String, dynamic> body
}) async {
final result = await HttpRequestClient.post(
urlPath: '/test_upstream_dns',
server: server,
body: body
);
return ApiResponse(
successful: result.successful,
content: result.body != null ? jsonDecode(result.body!) : null
);
}
}

View file

@ -88,10 +88,10 @@ SPEC CHECKSUMS:
SentryPrivate: b2f7996f37781080f04a946eb4e377ff63c64195
sqflite: a5789cceda41d54d23f31d6de539d65bb14100ea
sqlite3: 6e2d4a4879854d0ec86b476bf3c3e30870bac273
sqlite3_flutter_libs: 5b7e226d522d67be60d7ade93f5aa11ebc0cd796
sqlite3_flutter_libs: a25f3a0f522fdcd8fef6a4a50a3d681dd43d8dea
url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95
window_size: 339dafa0b27a95a62a843042038fa6c3c48de195
PODFILE CHECKSUM: 353c8bcc5d5b0994e508d035b5431cfe18c1dea7
COCOAPODS: 1.12.1
COCOAPODS: 1.14.3

View file

@ -13,10 +13,10 @@ packages:
dependency: "direct main"
description:
name: animations
sha256: ef57563eed3620bd5d75ad96189846aca1e033c0c45fc9a7d26e80ab02b88a70
sha256: "708e4b68c23228c264b038fe7003a2f5d01ce85fc64d8cae090e86b27fcea6c5"
url: "https://pub.dev"
source: hosted
version: "2.0.8"
version: "2.0.10"
ansicolor:
dependency: transitive
description:
@ -271,10 +271,10 @@ packages:
dependency: "direct dev"
description:
name: flutter_native_splash
sha256: c4d899312b36e7454bedfd0a4740275837b99e532d81c8477579d8183db1de6c
sha256: "141b20f15a2c4fe6e33c49257ca1bc114fc5c500b04fcbc8d75016bb86af672f"
url: "https://pub.dev"
source: hosted
version: "2.3.6"
version: "2.3.8"
flutter_reorderable_list:
dependency: "direct main"
description:
@ -330,10 +330,10 @@ packages:
dependency: "direct main"
description:
name: http
sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525"
sha256: d4872660c46d929f6b8a9ef4e7a7eff7e49bbf0c4ec3f385ee32df5119175139
url: "https://pub.dev"
source: hosted
version: "1.1.0"
version: "1.1.2"
http_parser:
dependency: transitive
description:
@ -474,10 +474,10 @@ packages:
dependency: transitive
description:
name: petitparser
sha256: eeb2d1428ee7f4170e2bd498827296a18d4e7fc462b71727d111c0ac7707cfa6
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
url: "https://pub.dev"
source: hosted
version: "6.0.1"
version: "6.0.2"
pie_chart:
dependency: "direct main"
description:
@ -575,18 +575,18 @@ packages:
dependency: "direct main"
description:
name: sqflite_common_ffi
sha256: "35d2fce1e971707c227cc4775cc017d5eafe06c2654c3435ebd5c3ad6c170f5f"
sha256: "873677ee78738a723d1ded4ccb23980581998d873d30ee9c331f6a81748663ff"
url: "https://pub.dev"
source: hosted
version: "2.3.0+4"
version: "2.3.1"
sqlite3:
dependency: transitive
description:
name: sqlite3
sha256: db65233e6b99e99b2548932f55a987961bc06d82a31a0665451fa0b4fff4c3fb
sha256: "8922805564b78eb7aa9386c10056d377a541ac7270dc6a1589176277ebb4d15d"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
version: "2.2.0"
sqlite3_flutter_libs:
dependency: "direct main"
description:
@ -671,10 +671,10 @@ packages:
dependency: "direct main"
description:
name: url_launcher
sha256: b1c9e98774adf8820c96fbc7ae3601231d324a7d5ebd8babe27b6dfac91357ba
sha256: e9aa5ea75c84cf46b3db4eea212523591211c3cf2e13099ee4ec147f54201c86
url: "https://pub.dev"
source: hosted
version: "6.2.1"
version: "6.2.2"
url_launcher_android:
dependency: transitive
description:
@ -719,10 +719,10 @@ packages:
dependency: transitive
description:
name: url_launcher_web
sha256: "7fd2f55fe86cea2897b963e864dc01a7eb0719ecc65fcef4c1cc3d686d718bb2"
sha256: "7286aec002c8feecc338cc33269e96b73955ab227456e9fb2a91f7fab8a358e9"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
version: "2.2.2"
url_launcher_windows:
dependency: transitive
description:
@ -783,10 +783,10 @@ packages:
dependency: transitive
description:
name: win32
sha256: "7c99c0e1e2fa190b48d25c81ca5e42036d5cac81430ef249027d97b0935c553f"
sha256: b0f37db61ba2f2e9b7a78a1caece0052564d1bc70668156cf3a29d676fe4e574
url: "https://pub.dev"
source: hosted
version: "5.1.0"
version: "5.1.1"
win32_registry:
dependency: transitive
description:
@ -808,10 +808,10 @@ packages:
dependency: transitive
description:
name: xml
sha256: af5e77e9b83f2f4adc5d3f0a4ece1c7f45a2467b695c2540381bac793e34e556
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
url: "https://pub.dev"
source: hosted
version: "6.4.2"
version: "6.5.0"
yaml:
dependency: transitive
description:
@ -822,4 +822,4 @@ packages:
version: "3.1.2"
sdks:
dart: ">=3.2.0 <4.0.0"
flutter: ">=3.13.0"
flutter: ">=3.16.0"

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
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 2.12.4+115
version: 2.13.0+117
environment:
sdk: '>=2.18.1 <3.0.0'
@ -44,7 +44,7 @@ dependencies:
package_info_plus: ^5.0.1
flutter_displaymode: ^0.6.0
dynamic_color: ^1.6.8
animations: ^2.0.8
animations: ^2.0.10
device_info_plus: ^9.1.1
uuid: ^4.2.1
expandable: ^5.0.1
@ -58,7 +58,7 @@ dependencies:
html: ^0.15.4
flutter_html: ^3.0.0-beta.2
sqlite3_flutter_libs: ^0.5.18
sqflite_common_ffi: ^2.3.0+4
sqflite_common_ffi: ^2.3.1
window_size:
git:
url: https://github.com/google/flutter-desktop-embedding
@ -67,7 +67,7 @@ dependencies:
git:
url: https://github.com/JGeek00/flutter_split_view
ref: master-alt
url_launcher: ^6.1.11
url_launcher: ^6.2.2
contextmenu: ^3.0.0
async: ^2.11.0
sentry_flutter: ^7.13.2
@ -75,7 +75,7 @@ dependencies:
flutter_reorderable_list: ^1.3.1
pie_chart: ^5.4.0
segmented_button_slide: ^1.0.4
http: ^1.1.0
http: ^1.1.2
dev_dependencies:
flutter_test:
@ -88,7 +88,7 @@ dev_dependencies:
# rules and activating additional ones.
flutter_lints: ^3.0.1
flutter_launcher_icons: ^0.13.1
flutter_native_splash: ^2.3.6
flutter_native_splash: ^2.3.8
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec