Merge branch 'beta'

This commit is contained in:
Juan Gilsanz Polo 2024-02-29 15:34:55 +01:00
commit 2a075816b3
12 changed files with 422 additions and 249 deletions

16
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,16 @@
{
"editor.formatOnSave": false,
"editor.formatOnPaste": false,
"editor.formatOnType": false,
"editor.defaultFormatter": "Dart-Code.flutter",
"dart.lineLength": 120,
"[dart]": {
"editor.rulers": [
120
],
"editor.defaultFormatter": "Dart-Code.dart-code",
"editor.formatOnSave": false,
"editor.formatOnPaste": false,
"editor.formatOnType": false
}
}

View file

@ -29,7 +29,7 @@ if (keystorePropertiesFile.exists()) {
} }
android { android {
compileSdkVersion 33 compileSdkVersion 34
ndkVersion flutter.ndkVersion ndkVersion flutter.ndkVersion
compileOptions { compileOptions {
@ -51,7 +51,7 @@ android {
// You can update the following values to match your application needs. // You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
minSdkVersion 26 minSdkVersion 26
targetSdkVersion 33 targetSdkVersion 34
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
} }

View file

@ -754,6 +754,8 @@
"statisticsSettingsDescription": "Configure data collection for statistics", "statisticsSettingsDescription": "Configure data collection for statistics",
"loadingStatisticsSettings": "Loading statistics settings...", "loadingStatisticsSettings": "Loading statistics settings...",
"statisticsSettingsLoadError": "An error occured when loading statistics settings.", "statisticsSettingsLoadError": "An error occured when loading statistics settings.",
"statisticsConfigUpdated": "Statistics settings updated successfully",
"statisticsConfigNotUpdated": "Statistics settings couldn't be updated",
"customTimeInHours": "Custom time (in hours)", "customTimeInHours": "Custom time (in hours)",
"invalidTime": "Invalid time", "invalidTime": "Invalid time",
"removeDomain": "Remove domain", "removeDomain": "Remove domain",
@ -776,5 +778,6 @@
"enablePlainDns": "Enable plain DNS", "enablePlainDns": "Enable plain DNS",
"enablePlainDnsDescription": "Plain DNS is enabled by default. You can disable it to force all devices to use encrypted DNS. To do this, you must enable at least one encrypted DNS protocol.", "enablePlainDnsDescription": "Plain DNS is enabled by default. You can disable it to force all devices to use encrypted DNS. To do this, you must enable at least one encrypted DNS protocol.",
"date": "Date", "date": "Date",
"loadingChangelog": "Loading changelog..." "loadingChangelog": "Loading changelog...",
"invalidIpOrUrl": "Invalid IP address or URL"
} }

View file

@ -754,6 +754,8 @@
"statisticsSettingsDescription": "Configura la recolección de datos para estadísticas", "statisticsSettingsDescription": "Configura la recolección de datos para estadísticas",
"loadingStatisticsSettings": "Cargando ajustes de estadísticas...", "loadingStatisticsSettings": "Cargando ajustes de estadísticas...",
"statisticsSettingsLoadError": "Ocurrió un error al cargar los ajustes de estadísticas.", "statisticsSettingsLoadError": "Ocurrió un error al cargar los ajustes de estadísticas.",
"statisticsConfigNotUpdated": "La configuración de estadísticas no pudo ser actualizada.",
"statisticsConfigUpdated": "Configuración de estadísticas actualizada correctamente.",
"customTimeInHours": "Tiempo personalizado (en horas)", "customTimeInHours": "Tiempo personalizado (en horas)",
"invalidTime": "Tiempo no válido", "invalidTime": "Tiempo no válido",
"removeDomain": "Eliminar dominio", "removeDomain": "Eliminar dominio",
@ -776,5 +778,6 @@
"enablePlainDns": "Activar DNS simple (sin cifrado)", "enablePlainDns": "Activar DNS simple (sin cifrado)",
"enablePlainDnsDescription": "El DNS simple (sin cifrado) está activado de forma predeterminada. Puedes desactivarlo para obligar a todos los dispositivos a utilizar DNS cifrado. Para ello, debes habilitar al menos un protocolo DNS cifrado.", "enablePlainDnsDescription": "El DNS simple (sin cifrado) está activado de forma predeterminada. Puedes desactivarlo para obligar a todos los dispositivos a utilizar DNS cifrado. Para ello, debes habilitar al menos un protocolo DNS cifrado.",
"date": "Fecha", "date": "Fecha",
"loadingChangelog": "Cargando registro de cambios..." "loadingChangelog": "Cargando registro de cambios...",
"invalidIpOrUrl": "Dirección IP o URL no válida"
} }

View file

@ -43,7 +43,7 @@ class _HomeChartState extends State<HomeChart> {
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: widget.hoursInterval*widget.data.length+1)); DateTime currentDate = DateTime.now().subtract(Duration(hours: widget.hoursInterval*widget.data.length));
for (var i = 0; i < widget.data.length; i++) { for (var i = 0; i < widget.data.length; i++) {
currentDate = currentDate.add(Duration(hours: widget.hoursInterval)); currentDate = currentDate.add(Duration(hours: widget.hoursInterval));
dateTimes.add(currentDate); dateTimes.add(currentDate);

View file

@ -33,6 +33,7 @@ class LogsListWidget extends StatefulWidget {
} }
class _LogsListWidgetState extends State<LogsListWidget> { class _LogsListWidgetState extends State<LogsListWidget> {
final _scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
bool showDivider = true; bool showDivider = true;
void fetchFilteringRules() async { void fetchFilteringRules() async {
@ -93,194 +94,197 @@ class _LogsListWidgetState extends State<LogsListWidget> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final logsProvider = Provider.of<LogsProvider>(context); final logsProvider = Provider.of<LogsProvider>(context);
return Scaffold( return ScaffoldMessenger(
body: NestedScrollView( key: widget.twoColumns ? _scaffoldMessengerKey : null,
headerSliverBuilder: (context, innerBoxIsScrolled) => [ child: Scaffold(
SliverOverlapAbsorber( body: NestedScrollView(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), headerSliverBuilder: (context, innerBoxIsScrolled) => [
sliver: LogsListAppBar( SliverOverlapAbsorber(
innerBoxIsScrolled: innerBoxIsScrolled, handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
showDivider: showDivider, sliver: LogsListAppBar(
innerBoxIsScrolled: innerBoxIsScrolled,
showDivider: showDivider,
)
) )
) ],
], body: Builder(
body: Builder( builder: (context) {
builder: (context) { switch (logsProvider.loadStatus) {
switch (logsProvider.loadStatus) { case LoadStatus.loading:
case LoadStatus.loading: return SafeArea(
return SafeArea( top: false,
top: false, bottom: false,
bottom: false, child: Builder(
child: Builder( builder: (context) => CustomScrollView(
builder: (context) => CustomScrollView( slivers: [
slivers: [ SliverOverlapInjector(
SliverOverlapInjector( handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), ),
), SliverFillRemaining(
SliverFillRemaining( child: SizedBox(
child: SizedBox( width: double.maxFinite,
width: double.maxFinite, child: Column(
child: Column( mainAxisAlignment: MainAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center, children: [
children: [ const CircularProgressIndicator(),
const CircularProgressIndicator(), const SizedBox(height: 30),
const SizedBox(height: 30), Text(
Text( AppLocalizations.of(context)!.loadingLogs,
AppLocalizations.of(context)!.loadingLogs, style: TextStyle(
style: TextStyle( fontSize: 22,
fontSize: 22, color: Theme.of(context).colorScheme.onSurfaceVariant,
color: Theme.of(context).colorScheme.onSurfaceVariant, ),
), )
) ],
], ),
), )
) )
) ],
], ),
), )
) );
);
case LoadStatus.loaded: case LoadStatus.loaded:
return SafeArea( return SafeArea(
top: false, top: false,
bottom: false, bottom: false,
child: Builder( child: Builder(
builder: (context) => RefreshIndicator( builder: (context) => RefreshIndicator(
onRefresh: () async { onRefresh: () async {
await logsProvider.fetchLogs(inOffset: 0); await logsProvider.fetchLogs(inOffset: 0);
}, },
displacement: 95, displacement: 95,
child: NotificationListener( child: NotificationListener(
onNotification: scrollListener, onNotification: scrollListener,
child: CustomScrollView( child: CustomScrollView(
slivers: [ slivers: [
SliverOverlapInjector( SliverOverlapInjector(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
), ),
if (logsProvider.logsData!.data.isNotEmpty) SliverList.builder( if (logsProvider.logsData!.data.isNotEmpty) SliverList.builder(
itemCount: logsProvider.isLoadingMore itemCount: logsProvider.isLoadingMore
? logsProvider.logsData!.data.length + 1 ? logsProvider.logsData!.data.length + 1
: logsProvider.logsData!.data.length, : logsProvider.logsData!.data.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
if (logsProvider.isLoadingMore == true && index == logsProvider.logsData!.data.length) { if (logsProvider.isLoadingMore == true && index == logsProvider.logsData!.data.length) {
return const Padding( return const Padding(
padding: EdgeInsets.symmetric(vertical: 20), padding: EdgeInsets.symmetric(vertical: 20),
child: Center( child: Center(
child: CircularProgressIndicator(), child: CircularProgressIndicator(),
), ),
); );
} }
else if (logsProvider.logsData!.data[index].question.name != null) { else if (logsProvider.logsData!.data[index].question.name != null) {
return LogTile( return LogTile(
log: logsProvider.logsData!.data[index], log: logsProvider.logsData!.data[index],
index: index, index: index,
length: logsProvider.logsData!.data.length, length: logsProvider.logsData!.data.length,
isLogSelected: widget.selectedLog != null && widget.selectedLog == logsProvider.logsData!.data[index], isLogSelected: widget.selectedLog != null && widget.selectedLog == logsProvider.logsData!.data[index],
onLogTap: (log) { onLogTap: (log) {
if (!widget.twoColumns) { if (!widget.twoColumns) {
Navigator.of(context).push( Navigator.of(context).push(
MaterialPageRoute( MaterialPageRoute(
builder: (context) => LogDetailsScreen( builder: (context) => LogDetailsScreen(
log: log, log: log,
dialog: false, dialog: false,
)
) )
) );
); }
} widget.onLogSelected(log);
widget.onLogSelected(log); },
}, twoColumns: widget.twoColumns,
twoColumns: widget.twoColumns, );
); }
else {
return null;
}
} }
else { ),
return null; if (logsProvider.logsData!.data.isEmpty) SliverFillRemaining(
} child: Center(
} child: Column(
), mainAxisSize: MainAxisSize.min,
if (logsProvider.logsData!.data.isEmpty) SliverFillRemaining( children: [
child: Center( Text(
child: Column( AppLocalizations.of(context)!.noLogsDisplay,
mainAxisSize: MainAxisSize.min,
children: [
Text(
AppLocalizations.of(context)!.noLogsDisplay,
style: TextStyle(
fontSize: 24,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
if (logsProvider.logsOlderThan != null) Padding(
padding: const EdgeInsets.only(
top: 30,
left: 20,
right: 20
),
child: Text(
AppLocalizations.of(context)!.noLogsThatOld,
textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 24,
color: Theme.of(context).colorScheme.onSurfaceVariant, color: Theme.of(context).colorScheme.onSurfaceVariant,
), ),
), ),
), if (logsProvider.logsOlderThan != null) Padding(
] padding: const EdgeInsets.only(
top: 30,
left: 20,
right: 20
),
child: Text(
AppLocalizations.of(context)!.noLogsThatOld,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
),
]
),
), ),
), )
) ],
], ),
), ),
), ),
), )
) );
);
case LoadStatus.error: case LoadStatus.error:
return SafeArea( return SafeArea(
top: false, top: false,
bottom: false, bottom: false,
child: Builder( child: Builder(
builder: (context) => CustomScrollView( builder: (context) => CustomScrollView(
slivers: [ slivers: [
SliverOverlapInjector( SliverOverlapInjector(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
), ),
SliverFillRemaining( SliverFillRemaining(
child: SizedBox( child: SizedBox(
width: double.maxFinite, width: double.maxFinite,
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
const Icon( const Icon(
Icons.error, Icons.error,
color: Colors.red, color: Colors.red,
size: 50, size: 50,
),
const SizedBox(height: 30),
Text(
AppLocalizations.of(context)!.logsNotLoaded,
style: TextStyle(
fontSize: 22,
color: Theme.of(context).colorScheme.onSurfaceVariant,
), ),
) const SizedBox(height: 30),
], Text(
), AppLocalizations.of(context)!.logsNotLoaded,
style: TextStyle(
fontSize: 22,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
)
],
),
)
) )
) ],
], ),
), )
) );
);
default: default:
return const SizedBox(); return const SizedBox();
} }
}, },
) )
),
), ),
); );
} }

View file

@ -1,5 +1,6 @@
// ignore_for_file: use_build_context_synchronously import 'dart:io';
import 'package:adguard_home_manager/screens/settings/dns/comment_modal.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
@ -24,11 +25,12 @@ class _FallbackDnsScreenState extends State<FallbackDnsScreen> {
void validateIp(Map<String, dynamic> field, String value) { 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,})?$)'); 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) { RegExp url = RegExp(r'(https?|tls):\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)');
if (ipAddress.hasMatch(value) == true || url.hasMatch(value) == true) {
setState(() => field['error'] = null); setState(() => field['error'] = null);
} }
else { else {
setState(() => field['error'] = AppLocalizations.of(context)!.invalidIp); setState(() => field['error'] = AppLocalizations.of(context)!.invalidIpOrUrl);
} }
checkValidValues(); checkValidValues();
} }
@ -47,12 +49,20 @@ class _FallbackDnsScreenState extends State<FallbackDnsScreen> {
final dnsProvider = Provider.of<DnsProvider>(context, listen: false); final dnsProvider = Provider.of<DnsProvider>(context, listen: false);
for (var item in dnsProvider.dnsInfo!.fallbackDns!) { for (var item in dnsProvider.dnsInfo!.fallbackDns!) {
final controller = TextEditingController(); if (item.contains("#")) {
controller.text = item; fallbackControllers.add({
fallbackControllers.add({ 'comment': item
'controller': controller, });
'error': null }
}); else {
final controller = TextEditingController();
controller.text = item;
fallbackControllers.add({
'controller': controller,
'error': null,
'isComment': item.contains("#")
});
}
} }
validValues = true; validValues = true;
super.initState(); super.initState();
@ -70,11 +80,16 @@ class _FallbackDnsScreenState extends State<FallbackDnsScreen> {
processModal.open(AppLocalizations.of(context)!.savingConfig); processModal.open(AppLocalizations.of(context)!.savingConfig);
final result = await dnsProvider.saveFallbackDnsConfig({ final result = await dnsProvider.saveFallbackDnsConfig({
"fallback_dns": fallbackControllers.map((e) => e['controller'].text).toList(), "fallback_dns": fallbackControllers.map(
(e) => e['controller'] != null
? e['controller'].text
: e['comment']
).toList(),
}); });
processModal.close(); processModal.close();
if (!context.mounted) return;
if (result.successful == true) { if (result.successful == true) {
showSnacbkar( showSnacbkar(
appConfigProvider: appConfigProvider, appConfigProvider: appConfigProvider,
@ -98,6 +113,75 @@ class _FallbackDnsScreenState extends State<FallbackDnsScreen> {
} }
} }
void openAddCommentModal() {
if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) {
showDialog(
context: context,
builder: (context) => CommentModal(
onConfirm: (value) {
setState(() {
fallbackControllers.add({
'comment': value
});
});
},
dialog: true,
),
);
}
else {
showModalBottomSheet(
context: context,
useRootNavigator: true,
builder: (context) => CommentModal(
onConfirm: (value) {
setState(() {
fallbackControllers.add({
'comment': value
});
});
},
dialog: false,
),
backgroundColor: Colors.transparent,
isScrollControlled: true,
isDismissible: true
);
}
}
void openEditCommentModal(Map<String, dynamic> item, int position) {
if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) {
showDialog(
context: context,
builder: (context) => CommentModal(
comment: item['comment'],
onConfirm: (value) {
setState(() => fallbackControllers[position] = { 'comment': value });
},
dialog: true,
),
);
}
else {
showModalBottomSheet(
context: context,
useRootNavigator: true,
builder: (context) => CommentModal(
comment: item['comment'],
onConfirm: (value) {
setState(() => fallbackControllers[position] = { 'comment': value });
},
dialog: false,
),
backgroundColor: Colors.transparent,
isScrollControlled: true,
isDismissible: true
);
}
}
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(AppLocalizations.of(context)!.fallbackDnsServers), title: Text(AppLocalizations.of(context)!.fallbackDnsServers),
@ -167,37 +251,68 @@ class _FallbackDnsScreenState extends State<FallbackDnsScreen> {
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Expanded( if (c['controller'] != null) Expanded(
child: TextFormField( child: TextFormField(
controller: c['controller'], controller: c['controller'],
onChanged: (value) => validateIp(c, value), onChanged: (value) => validateIp(c, value),
decoration: InputDecoration( decoration: InputDecoration(
prefixIcon: const Icon(Icons.dns_rounded), prefixIcon: Icon(
c['isComment'] == true
? Icons.comment_rounded
: Icons.dns_rounded
),
border: const OutlineInputBorder( border: const OutlineInputBorder(
borderRadius: BorderRadius.all( borderRadius: BorderRadius.all(
Radius.circular(10) Radius.circular(10)
) )
), ),
errorText: c['error'], errorText: c['error'],
labelText: AppLocalizations.of(context)!.dnsServer, labelText: c['isComment'] == true
? AppLocalizations.of(context)!.comment
: AppLocalizations.of(context)!.dnsServer,
) )
), ),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
if (c['comment'] != null) Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
c['comment'],
style: TextStyle(
fontSize: 16,
color: Theme.of(context).listTileTheme.iconColor
),
),
IconButton(
onPressed: () => openEditCommentModal(c, fallbackControllers.indexOf(c)),
icon: const Icon(Icons.edit),
tooltip: AppLocalizations.of(context)!.edit,
)
],
),
),
IconButton( IconButton(
onPressed: () { onPressed: () {
setState(() => fallbackControllers = fallbackControllers.where((con) => con != c).toList()); setState(() => fallbackControllers = fallbackControllers.where((con) => con != c).toList());
checkValidValues(); checkValidValues();
}, },
icon: const Icon(Icons.remove_circle_outline) icon: const Icon(Icons.remove_circle_outline),
tooltip: AppLocalizations.of(context)!.remove,
) )
], ],
), ),
)), )),
Row( Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
ElevatedButton.icon(
onPressed: openAddCommentModal,
icon: const Icon(Icons.add),
label: Text(AppLocalizations.of(context)!.comment)
),
ElevatedButton.icon( ElevatedButton.icon(
onPressed: () { onPressed: () {
setState(() => fallbackControllers.add({ setState(() => fallbackControllers.add({
@ -207,7 +322,7 @@ class _FallbackDnsScreenState extends State<FallbackDnsScreen> {
checkValidValues(); checkValidValues();
}, },
icon: const Icon(Icons.add), icon: const Icon(Icons.add),
label: Text(AppLocalizations.of(context)!.addItem) label: Text(AppLocalizations.of(context)!.address)
), ),
], ],
), ),

View file

@ -47,7 +47,7 @@ class _UpstreamDnsScreenState extends State<UpstreamDnsScreen> {
final dnsProvider = Provider.of<DnsProvider>(context, listen: false); final dnsProvider = Provider.of<DnsProvider>(context, listen: false);
for (var item in dnsProvider.dnsInfo!.upstreamDns) { for (var item in dnsProvider.dnsInfo!.upstreamDns) {
if (item == '#') { if (item.contains("#")) {
dnsServers.add({ dnsServers.add({
'comment': item 'comment': item
}); });
@ -263,7 +263,7 @@ class _UpstreamDnsScreenState extends State<UpstreamDnsScreen> {
const SizedBox(width: 4), const SizedBox(width: 4),
], ],
), ),
)).toList(), )),
const SizedBox(height: 12), const SizedBox(height: 12),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,

View file

@ -83,7 +83,7 @@ class _SettingsWidget extends StatefulWidget {
} }
class _SettingsWidgetState extends State<_SettingsWidget> { class _SettingsWidgetState extends State<_SettingsWidget> {
final scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>(); final _scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
@override @override
void initState() { void initState() {
@ -104,7 +104,7 @@ class _SettingsWidgetState extends State<_SettingsWidget> {
} }
return ScaffoldMessenger( return ScaffoldMessenger(
key: widget.twoColumns ? scaffoldMessengerKey : null, key: widget.twoColumns ? _scaffoldMessengerKey : null,
child: Scaffold( child: Scaffold(
body: NestedScrollView( body: NestedScrollView(
headerSliverBuilder: (context, innerBoxIsScrolled) => [ headerSliverBuilder: (context, innerBoxIsScrolled) => [

View file

@ -145,19 +145,19 @@ class _StatisticsSettingsState extends State<StatisticsSettings> {
processModal.close(); processModal.close();
if (!mounted) return; if (!context.mounted) return;
if (result.successful == true) { if (result.successful == true) {
showSnacbkar( showSnacbkar(
appConfigProvider: appConfigProvider, appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.logsConfigUpdated, label: AppLocalizations.of(context)!.statisticsConfigUpdated,
color: Colors.green color: Colors.green
); );
} }
else { else {
showSnacbkar( showSnacbkar(
appConfigProvider: appConfigProvider, appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.logsConfigNotUpdated, label: AppLocalizations.of(context)!.statisticsConfigNotUpdated,
color: Colors.red color: Colors.red
); );
} }
@ -209,7 +209,7 @@ class _StatisticsSettingsState extends State<StatisticsSettings> {
if (value != null && value != "custom") { if (value != null && value != "custom") {
_customTimeError = null; _customTimeError = null;
_customTimeController.text = ""; _customTimeController.text = "";
}; }
_retentionTime = value; _retentionTime = value;
}), }),
decoration: InputDecoration( decoration: InputDecoration(

View file

@ -189,10 +189,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: ffi name: ffi
sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.0" version: "2.1.2"
file: file:
dependency: transitive dependency: transitive
description: description:
@ -213,10 +213,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: fl_chart name: fl_chart
sha256: b5e2b0f13d93f8c532b5a2786bfb44580de1f50b927bf95813fa1af617e9caf8 sha256: "00b74ae680df6b1135bdbea00a7d1fc072a9180b7c3f3702e4b19a9943f5ed7d"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.66.1" version: "0.66.2"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@ -311,10 +311,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_markdown name: flutter_markdown
sha256: "30088ce826b5b9cfbf9e8bece34c716c8a59fa54461dcae1e4ac01a94639e762" sha256: a64c5323ac83ed2b7940d2b6288d160aa1753ff271ba9d9b2a86770414aa3eab
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.6.18+3" version: "0.6.20+1"
flutter_native_splash: flutter_native_splash:
dependency: "direct dev" dependency: "direct dev"
description: description:
@ -344,10 +344,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_svg name: flutter_svg
sha256: d39e7f95621fc84376bc0f7d504f05c3a41488c562f4a8ad410569127507402c sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.9" version: "2.0.10+1"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@ -386,10 +386,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: image name: image
sha256: "49a0d4b0c12402853d3f227fe7c315601b238d126aa4caa5dbb2dcf99421aa4a" sha256: "4c68bfd5ae83e700b5204c1e74451e7bf3cf750e6843c6e158289cf56bda018e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.1.6" version: "4.1.7"
intl: intl:
dependency: "direct main" dependency: "direct main"
description: description:
@ -414,6 +414,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.8.1" version: "4.8.1"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
url: "https://pub.dev"
source: hosted
version: "10.0.0"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
url: "https://pub.dev"
source: hosted
version: "2.0.1"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
url: "https://pub.dev"
source: hosted
version: "2.0.1"
lints: lints:
dependency: transitive dependency: transitive
description: description:
@ -442,26 +466,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.16" version: "0.12.16+1"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
name: material_color_utilities name: material_color_utilities
sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.0" version: "0.8.0"
meta: meta:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.10.0" version: "1.11.0"
nested: nested:
dependency: transitive dependency: transitive
description: description:
@ -490,10 +514,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: path name: path
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.8.3" version: "1.9.0"
path_parsing: path_parsing:
dependency: transitive dependency: transitive
description: description:
@ -578,10 +602,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: provider name: provider
sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096" sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.1.1" version: "6.1.2"
segmented_button_slide: segmented_button_slide:
dependency: "direct main" dependency: "direct main"
description: description:
@ -594,18 +618,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: sentry name: sentry
sha256: a7946f4a90b0feb47214981d881b98149e05f6c576da9f2a2f33945bf561de25 sha256: d2ee9c850d876d285f22e2e662f400ec2438df9939fe4acd5d780df9841794ce
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.16.0" version: "7.16.1"
sentry_flutter: sentry_flutter:
dependency: "direct main" dependency: "direct main"
description: description:
name: sentry_flutter name: sentry_flutter
sha256: "6db7fa1b076faf2f5dd77d8cc9ef206171f32a290cc638842d78e5d62b441a27" sha256: "5b428c189c825f16fb14e9166529043f06b965d5b59bfc3a1415e39c082398c0"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.16.0" version: "7.16.1"
shared_preferences: shared_preferences:
dependency: "direct main" dependency: "direct main"
description: description:
@ -711,10 +735,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: sqlite3 name: sqlite3
sha256: c4a4c5a4b2a32e2d0f6837b33d7c91a67903891a5b7dbe706cf4b1f6b0c798c5 sha256: "072128763f1547e3e9b4735ce846bfd226d68019ccda54db4cd427b12dfdedc9"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.0" version: "2.4.0"
sqlite3_flutter_libs: sqlite3_flutter_libs:
dependency: "direct main" dependency: "direct main"
description: description:
@ -807,26 +831,26 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: url_launcher name: url_launcher
sha256: c512655380d241a337521703af62d2c122bf7b77a46ff7dd750092aa9433499c sha256: "0ecc004c62fd3ed36a2ffcbe0dd9700aee63bd7532d0b642a488b1ec310f492e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.2.4" version: "6.2.5"
url_launcher_android: url_launcher_android:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_android name: url_launcher_android
sha256: "507dc655b1d9cb5ebc756032eb785f114e415f91557b73bf60b7e201dfedeb2f" sha256: d4ed0711849dd8e33eb2dd69c25db0d0d3fdc37e0a62e629fe32f57a22db2745
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.2.2" version: "6.3.0"
url_launcher_ios: url_launcher_ios:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_ios name: url_launcher_ios
sha256: "75bb6fe3f60070407704282a2d295630cab232991eb52542b18347a8a941df03" sha256: "9149d493b075ed740901f3ee844a38a00b33116c7c5c10d7fb27df8987fb51d5"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.2.4" version: "6.2.5"
url_launcher_linux: url_launcher_linux:
dependency: transitive dependency: transitive
description: description:
@ -847,10 +871,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_platform_interface name: url_launcher_platform_interface
sha256: a932c3a8082e118f80a475ce692fde89dc20fddb24c57360b96bc56f7035de1f sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.1" version: "2.3.2"
url_launcher_web: url_launcher_web:
dependency: transitive dependency: transitive
description: description:
@ -879,26 +903,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: vector_graphics name: vector_graphics
sha256: "4ac59808bbfca6da38c99f415ff2d3a5d7ca0a6b4809c71d9cf30fba5daf9752" sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.10+1" version: "1.1.11+1"
vector_graphics_codec: vector_graphics_codec:
dependency: transitive dependency: transitive
description: description:
name: vector_graphics_codec name: vector_graphics_codec
sha256: f3247e7ab0ec77dc759263e68394990edc608fb2b480b80db8aa86ed09279e33 sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.10+1" version: "1.1.11+1"
vector_graphics_compiler: vector_graphics_compiler:
dependency: transitive dependency: transitive
description: description:
name: vector_graphics_compiler name: vector_graphics_compiler
sha256: "18489bdd8850de3dd7ca8a34e0c446f719ec63e2bab2e7a8cc66a9028dd76c5a" sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.10+1" version: "1.1.11+1"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
@ -907,14 +931,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.4" version: "2.1.4"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
url: "https://pub.dev"
source: hosted
version: "13.0.0"
web: web:
dependency: transitive dependency: transitive
description: description:
name: web name: web
sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 sha256: "4188706108906f002b3a293509234588823c8c979dc83304e229ff400c996b05"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.3.0" version: "0.4.2"
win32: win32:
dependency: transitive dependency: transitive
description: description:
@ -965,5 +997,5 @@ packages:
source: hosted source: hosted
version: "3.1.2" version: "3.1.2"
sdks: sdks:
dart: ">=3.2.0 <4.0.0" dart: ">=3.3.0 <4.0.0"
flutter: ">=3.16.0" flutter: ">=3.19.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 # 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.16.3+133 version: 2.16.4+134
environment: environment:
sdk: '>=2.18.1 <3.0.0' sdk: '>=2.18.1 <3.0.0'