From 2389e345714cd0df7078f696c4dcb955f6b7b67e Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sat, 7 Oct 2023 18:37:16 +0200 Subject: [PATCH 01/14] Refactor add server form --- lib/l10n/app_en.arb | 1 + lib/l10n/app_es.arb | 1 + lib/screens/connect/fab.dart | 35 +- lib/screens/servers/servers.dart | 35 +- .../add_server/add_server_functions.dart | 120 +++ lib/widgets/add_server/add_server_modal.dart | 561 +++++++++++++ lib/widgets/add_server/form_text_field.dart | 54 ++ lib/widgets/add_server_modal.dart | 766 ------------------ .../servers_list/servers_list_item.dart | 41 +- .../servers_list/servers_tile_item.dart | 41 +- 10 files changed, 749 insertions(+), 906 deletions(-) create mode 100644 lib/widgets/add_server/add_server_functions.dart create mode 100644 lib/widgets/add_server/add_server_modal.dart create mode 100644 lib/widgets/add_server/form_text_field.dart delete mode 100644 lib/widgets/add_server_modal.dart diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 37e0acb..84a22f6 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -4,6 +4,7 @@ "connect": "Connect", "servers": "Servers", "createConnection": "Create connection", + "editConnection": "Edit connection", "name": "Name", "ipDomain": "IP address or domain", "path": "Path", diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 4b2af05..9b97a3a 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -4,6 +4,7 @@ "connect": "Conectar", "servers": "Servidores", "createConnection": "Crear conexión", + "editConnection": "Editar conexión", "name": "Nombre", "ipDomain": "Dirección IP o dominio", "path": "Ruta", diff --git a/lib/screens/connect/fab.dart b/lib/screens/connect/fab.dart index 47cb65e..05ed1a2 100644 --- a/lib/screens/connect/fab.dart +++ b/lib/screens/connect/fab.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:adguard_home_manager/widgets/add_server_modal.dart'; -import 'package:adguard_home_manager/widgets/version_warning_modal.dart'; +import 'package:adguard_home_manager/widgets/add_server/add_server_functions.dart'; class FabConnect extends StatelessWidget { const FabConnect({Key? key}) : super(key: key); @@ -12,37 +11,7 @@ class FabConnect extends StatelessWidget { void openAddServerModal() async { await Future.delayed(const Duration(seconds: 0), (() => { - if (width > 700) { - showDialog( - context: context, - barrierDismissible: false, - builder: (context) => AddServerModal( - window: true, - onUnsupportedVersion: (version) => showDialog( - context: context, - builder: (ctx) => VersionWarningModal( - version: version - ), - barrierDismissible: false - ), - ), - ) - } - else { - Navigator.push(context, MaterialPageRoute( - fullscreenDialog: true, - builder: (BuildContext context) => AddServerModal( - window: false, - onUnsupportedVersion: (version) => showDialog( - context: context, - builder: (ctx) => VersionWarningModal( - version: version - ), - barrierDismissible: false - ), - ) - )) - } + openServerFormModal(context: context, width: width) })); } diff --git a/lib/screens/servers/servers.dart b/lib/screens/servers/servers.dart index 8a557fb..180af77 100644 --- a/lib/screens/servers/servers.dart +++ b/lib/screens/servers/servers.dart @@ -6,9 +6,8 @@ import 'package:flutter/rendering.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:provider/provider.dart'; -import 'package:adguard_home_manager/widgets/version_warning_modal.dart'; +import 'package:adguard_home_manager/widgets/add_server/add_server_functions.dart'; import 'package:adguard_home_manager/widgets/servers_list/servers_list.dart'; -import 'package:adguard_home_manager/widgets/add_server_modal.dart'; import 'package:adguard_home_manager/providers/servers_provider.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; @@ -69,37 +68,7 @@ class _ServersState extends State { void openAddServerModal() async { await Future.delayed(const Duration(seconds: 0), (() => { - if (width > 700) { - showDialog( - context: context, - barrierDismissible: false, - builder: (context) => AddServerModal( - window: true, - onUnsupportedVersion: (version) => showDialog( - context: context, - builder: (ctx) => VersionWarningModal( - version: version - ), - barrierDismissible: false - ), - ), - ) - } - else { - Navigator.push(context, MaterialPageRoute( - fullscreenDialog: true, - builder: (BuildContext context) => AddServerModal( - window: false, - onUnsupportedVersion: (version) => showDialog( - context: context, - builder: (ctx) => VersionWarningModal( - version: version - ), - barrierDismissible: false - ), - ) - )) - } + openServerFormModal(context: context, width: width) })); } diff --git a/lib/widgets/add_server/add_server_functions.dart b/lib/widgets/add_server/add_server_functions.dart new file mode 100644 index 0000000..16a0149 --- /dev/null +++ b/lib/widgets/add_server/add_server_functions.dart @@ -0,0 +1,120 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +import 'package:adguard_home_manager/models/server.dart'; +import 'package:adguard_home_manager/widgets/add_server/add_server_modal.dart'; +import 'package:adguard_home_manager/widgets/version_warning_modal.dart'; + +bool checkDataValid({ + required TextEditingController nameController, + required TextEditingController ipDomainController, + required String? ipDomainError, + required String? pathError, + required String? portError, +}) { + if ( + nameController.text != '' && + ipDomainController.text != '' && + ipDomainError == null && + pathError == null && + portError == null + ) { + return true; + } + else { + return false; + } +} + + +String? validatePort({ + required String? value, + required BuildContext context +}) { + if (value != null && value != '') { + if (int.tryParse(value) != null && int.parse(value) <= 65535) { + return null; + } + else { + return AppLocalizations.of(context)!.invalidPort; + } + } + else { + return null; + } +} + +String? validateSubroute({ + required BuildContext context, + required String? value +}) { + if (value != null && value != '') { + RegExp subrouteRegexp = RegExp(r'^\/\b([A-Za-z0-9_\-~/]*)[^\/|\.|\:]$'); + if (subrouteRegexp.hasMatch(value) == true) { + return null; + } + else { + return AppLocalizations.of(context)!.invalidPath; + } + } + else { + return null; + } +} + +String? validateAddress({ + required BuildContext context, + required String? value +}) { + if (value != null && value != '') { + RegExp ipAddress = RegExp(r'^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)(\.(?!$)|$)){4}$'); + RegExp domain = RegExp(r'^(([a-z0-9|-]+\.)*[a-z0-9|-]+\.[a-z]+)|((\w|-)+)$'); + if (ipAddress.hasMatch(value) == true || domain.hasMatch(value) == true) { + return null; + } + else { + return AppLocalizations.of(context)!.invalidIpDomain; + } + } + else { + return AppLocalizations.of(context)!.ipDomainNotEmpty; + } +} + +void openServerFormModal({ + required BuildContext context, + required double width, + Server? server, +}) { + showGeneralDialog( + context: context, + barrierColor: width <= 700 + ?Colors.transparent + : Colors.black54, + transitionBuilder: (context, anim1, anim2, child) { + return SlideTransition( + position: Tween( + begin: const Offset(0, 1), + end: const Offset(0, 0) + ).animate( + CurvedAnimation( + parent: anim1, + curve: Curves.easeInOutCubicEmphasized + ) + ), + child: child, + ); + }, + pageBuilder: (context, animation, secondaryAnimation) => AddServerModal( + fullScreen: width <= 700, + server: server, + onUnsupportedVersion: (version) => showDialog( + context: context, + builder: (ctx) => VersionWarningModal( + version: version + ), + barrierDismissible: false + ), + ), + ); +} \ No newline at end of file diff --git a/lib/widgets/add_server/add_server_modal.dart b/lib/widgets/add_server/add_server_modal.dart new file mode 100644 index 0000000..7eb8949 --- /dev/null +++ b/lib/widgets/add_server/add_server_modal.dart @@ -0,0 +1,561 @@ +// ignore_for_file: use_build_context_synchronously + +import 'package:provider/provider.dart'; +import 'package:flutter/material.dart'; +import 'package:uuid/uuid.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +import 'package:adguard_home_manager/widgets/add_server/form_text_field.dart'; +import 'package:adguard_home_manager/widgets/add_server/add_server_functions.dart'; + +import 'package:adguard_home_manager/providers/app_config_provider.dart'; +import 'package:adguard_home_manager/functions/snackbar.dart'; +import 'package:adguard_home_manager/constants/urls.dart'; +import 'package:adguard_home_manager/functions/open_url.dart'; +import 'package:adguard_home_manager/constants/enums.dart'; +import 'package:adguard_home_manager/providers/status_provider.dart'; +import 'package:adguard_home_manager/functions/base64.dart'; +import 'package:adguard_home_manager/services/http_requests.dart'; +import 'package:adguard_home_manager/models/app_log.dart'; +import 'package:adguard_home_manager/providers/servers_provider.dart'; +import 'package:adguard_home_manager/models/server.dart'; + +enum ConnectionType { http, https} + +class AddServerModal extends StatefulWidget { + final Server? server; + final bool fullScreen; + final void Function(String version) onUnsupportedVersion; + + const AddServerModal({ + Key? key, + this.server, + required this.fullScreen, + required this.onUnsupportedVersion + }) : super(key: key); + + @override + State createState() => _AddServerModalState(); +} + +class _AddServerModalState extends State { + final uuid = const Uuid(); + + final TextEditingController nameController = TextEditingController(); + String? nameError; + + ConnectionType connectionType = ConnectionType.http; + + final TextEditingController ipDomainController = TextEditingController(); + String? ipDomainError; + + final TextEditingController pathController = TextEditingController(); + String? pathError; + + final TextEditingController portController = TextEditingController(); + String? portError; + + final TextEditingController userController = TextEditingController(); + + final TextEditingController passwordController = TextEditingController(); + + bool defaultServer = false; + + bool homeAssistant = false; + + bool allDataValid = false; + + bool isConnecting = false; + + Widget sectionLabel(String label) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 24, + vertical: 24 + ), + child: Text( + label, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.primary + ), + ), + ); + } + + @override + void initState() { + if (widget.server != null) { + nameController.text = widget.server!.name; + connectionType = widget.server!.connectionMethod == 'https' ? ConnectionType.https : ConnectionType.http; + ipDomainController.text = widget.server!.domain; + pathController.text = widget.server!.path ?? ''; + portController.text = widget.server!.port != null ? widget.server!.port.toString() : ""; + userController.text = widget.server!.user ?? ""; + passwordController.text = widget.server!.password ?? ""; + defaultServer = widget.server!.defaultServer; + homeAssistant = widget.server!.runningOnHa; + } + setState(() => allDataValid = checkDataValid( + ipDomainController: ipDomainController, + nameController: nameController, + ipDomainError: ipDomainError, + pathError: pathError, + portError: portError + )); + super.initState(); + } + + @override + Widget build(BuildContext context) { + final serversProvider = Provider.of(context, listen: false); + final statusProvider = Provider.of(context, listen: false); + final appConfigProvider = Provider.of(context, listen: false); + + void cancelConnecting() { + if (mounted) { + setState(() => isConnecting = false); + } + else { + isConnecting = false; + } + } + + void validateData() { + setState(() => allDataValid = checkDataValid( + ipDomainController: ipDomainController, + nameController: nameController, + ipDomainError: ipDomainError, + pathError: pathError, + portError: portError + )); + } + + String getErrorMessage(String message) { + if (message == 'invalid_username_password') return AppLocalizations.of(context)!.invalidUsernamePassword; + if (message == 'many_attempts') return AppLocalizations.of(context)!.tooManyAttempts; + if (message == 'no_connection') return AppLocalizations.of(context)!.cantReachServer; + if (message == 'server_error') return AppLocalizations.of(context)!.serverError; + return AppLocalizations.of(context)!.unknownError; + } + + void connect() async { + Server serverObj = Server( + id: uuid.v4(), + name: nameController.text, + connectionMethod: connectionType.name, + domain: ipDomainController.text, + port: portController.text != '' ? int.parse(portController.text) : null, + user: userController.text != "" ? userController.text : null, + password: passwordController.text != "" ? passwordController.text : null, + defaultServer: defaultServer, + authToken: homeAssistant == true + ? encodeBase64UserPass(userController.text, passwordController.text) + : null, + runningOnHa: homeAssistant + ); + setState(() => isConnecting = true); + + final result = homeAssistant == true + ? await loginHA(serverObj) + : await login(serverObj); + + // If something goes wrong with the connection + if (result['result'] != 'success') { + cancelConnecting(); + appConfigProvider.addLog(result['log']); + if (mounted) { + return showSnacbkar( + appConfigProvider: appConfigProvider, + label: getErrorMessage(result['result']), + color: Colors.red + ); + } + } + + if (serverObj.user != null && serverObj.password != null) { + serverObj.authToken = encodeBase64UserPass(serverObj.user!, serverObj.password!); + } + + final serverCreated = await serversProvider.createServer(serverObj); + + // If something goes wrong when saving the connection on the db + if (serverCreated == null) { + if (mounted) setState(() => isConnecting = false); + appConfigProvider.addLog( + AppLog( + type: 'save_connection_db', + dateTime: DateTime.now(), + message: serverCreated.toString() + ) + ); + if (mounted) { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.connectionNotCreated, + color: Colors.red + ); + } + } + + statusProvider.setServerStatusLoad(LoadStatus.loading); + final ApiClient apiClient = ApiClient(server: serverObj); + final serverStatus = await apiClient.getServerStatus(); + + // If something goes wrong when fetching server status + if (serverStatus['result'] != 'success') { + appConfigProvider.addLog(serverStatus['log']); + statusProvider.setServerStatusLoad(LoadStatus.error); + Navigator.pop(context); + } + + // If everything is successful + statusProvider.setServerStatusData( + data: serverStatus['data'] + ); + serversProvider.setApiClient(apiClient); + statusProvider.setServerStatusLoad(LoadStatus.loaded); + if (serverStatus['data'].serverVersion.contains('a') || serverStatus['data'].serverVersion.contains('b')) { + Navigator.pop(context); + widget.onUnsupportedVersion(serverStatus['data'].serverVersion); + } + else { + Navigator.pop(context); + } + } + + void edit() async { + final Server serverObj = Server( + id: widget.server!.id, + name: nameController.text, + connectionMethod: connectionType.name, + domain: ipDomainController.text, + port: portController.text != '' ? int.parse(portController.text) : null, + user: userController.text != "" ? userController.text : null, + password: passwordController.text != "" ? passwordController.text : null, + defaultServer: defaultServer, + authToken: homeAssistant == true + ? encodeBase64UserPass(userController.text, passwordController.text) + : null, + runningOnHa: homeAssistant + ); + + final result = homeAssistant == true + ? await loginHA(serverObj) + : await login(serverObj); + + // If something goes wrong with the connection + if (result['result'] != 'success') { + cancelConnecting(); + appConfigProvider.addLog(result['log']); + if (mounted) { + return showSnacbkar( + appConfigProvider: appConfigProvider, + label: getErrorMessage(result['result']), + color: Colors.red + ); + } + } + + if (serverObj.user != null && serverObj.password != null) { + serverObj.authToken = encodeBase64UserPass(serverObj.user!, serverObj.password!); + } + final serverSaved = await serversProvider.editServer(serverObj); + + // If something goes wrong when saving the connection on the db + if (serverSaved == null) { + if (mounted) setState(() => isConnecting = false); + appConfigProvider.addLog( + AppLog( + type: 'save_connection_db', + dateTime: DateTime.now(), + message: serverSaved.toString() + ) + ); + if (mounted) { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.connectionNotCreated, + color: Colors.red + ); + } + } + + // If everything is successful + final ApiClient apiClient = ApiClient(server: serverObj); + final version = await apiClient.getServerVersion(); + if ( + version['result'] == 'success' && + (version['data'].contains('a') || version['data'].contains('b')) // alpha or beta + ) { + Navigator.pop(context); + widget.onUnsupportedVersion(version['data']); + } + else { + Navigator.pop(context); + } + } + + Widget actions() { + return Row( + children: [ + IconButton( + onPressed: () => openUrl(Urls.connectionInstructions), + icon: const Icon(Icons.help_outline_outlined) + ), + IconButton( + tooltip: widget.server == null + ? AppLocalizations.of(context)!.connect + : AppLocalizations.of(context)!.save, + onPressed: allDataValid == true && isConnecting == false + ? widget.server == null + ? () => connect() + : () => edit() + : null, + icon: isConnecting + ? const CircularProgressIndicator() + : Icon( + widget.server == null + ? Icons.login_rounded + : Icons.save_rounded + ) + ), + ], + ); + } + + List form() { + return [ + Container( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + margin: const EdgeInsets.only( + top: 24, + left: 24, + right: 24 + ), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primary.withOpacity(0.05), + borderRadius: BorderRadius.circular(30), + border: Border.all( + color: Theme.of(context).colorScheme.primary + ) + ), + child: Text( + "${connectionType.name}://${ipDomainController.text}${portController.text != '' ? ':${portController.text}' : ""}${pathController.text}", + textAlign: TextAlign.center, + style: TextStyle( + color: Theme.of(context).colorScheme.primary, + fontWeight: FontWeight.w500 + ), + ), + ), + sectionLabel(AppLocalizations.of(context)!.general), + FormTextField( + label: AppLocalizations.of(context)!.name, + controller: nameController, + icon: Icons.badge_rounded, + error: nameError, + onChanged: (value) { + if (value != '') { + setState(() => nameError = null); + } + else { + setState(() => nameError = AppLocalizations.of(context)!.nameNotEmpty); + } + validateData(); + }, + isConnecting: isConnecting, + ), + sectionLabel(AppLocalizations.of(context)!.connection), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: SegmentedButton( + segments: const [ + ButtonSegment( + value: ConnectionType.http, + label: Text("HTTP") + ), + ButtonSegment( + value: ConnectionType.https, + label: Text("HTTPS") + ), + ], + selected: {connectionType}, + onSelectionChanged: (value) => setState(() => connectionType = value.first), + ), + ), + const SizedBox(height: 30), + FormTextField( + label: AppLocalizations.of(context)!.ipDomain, + controller: ipDomainController, + icon: Icons.link_rounded, + error: ipDomainError, + keyboardType: TextInputType.url, + onChanged: (v) { + setState(() => ipDomainError = validateAddress(context: context, value: v)); + validateData(); + }, + isConnecting: isConnecting, + ), + const SizedBox(height: 20), + FormTextField( + label: AppLocalizations.of(context)!.path, + controller: pathController, + icon: Icons.route_rounded, + error: pathError, + onChanged: (v) { + setState(() => pathError = validateSubroute(context: context, value: v)); + validateData(); + }, + hintText: AppLocalizations.of(context)!.examplePath, + helperText: AppLocalizations.of(context)!.helperPath, + isConnecting: isConnecting, + ), + const SizedBox(height: 20), + FormTextField( + label: AppLocalizations.of(context)!.port, + controller: portController, + icon: Icons.numbers_rounded, + error: portError, + keyboardType: TextInputType.number, + onChanged: (v) { + setState(() => portError = validatePort(context: context, value: v)); + validateData(); + }, + isConnecting: isConnecting, + ), + sectionLabel(AppLocalizations.of(context)!.authentication), + FormTextField( + label: AppLocalizations.of(context)!.username, + controller: userController, + icon: Icons.person_rounded, + isConnecting: isConnecting, + ), + const SizedBox(height: 20), + FormTextField( + label: AppLocalizations.of(context)!.password, + controller: passwordController, + icon: Icons.lock_rounded, + keyboardType: TextInputType.visiblePassword, + obscureText: true, + isConnecting: isConnecting, + ), + sectionLabel(AppLocalizations.of(context)!.other), + Material( + color: Colors.transparent, + child: InkWell( + onTap: widget.server == null + ? () => setState(() => defaultServer = !defaultServer) + : null, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + AppLocalizations.of(context)!.defaultServer, + style: const TextStyle( + fontSize: 15, + ), + ), + Switch( + value: defaultServer, + onChanged: widget.server == null + ? (value) => setState(() => defaultServer = value) + : null, + ) + ], + ), + ), + ), + ), + const SizedBox(height: 20), + Material( + color: Colors.transparent, + child: InkWell( + onTap: () => setState(() => homeAssistant = !homeAssistant), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + AppLocalizations.of(context)!.runningHomeAssistant, + style: const TextStyle( + fontSize: 15, + ), + ), + Switch( + value: homeAssistant, + onChanged: (value) => setState(() => homeAssistant = value), + ) + ], + ), + ), + ), + ), + const SizedBox(height: 20), + ]; + } + + if (widget.fullScreen == true) { + return Dialog.fullscreen( + child: Scaffold( + appBar: AppBar( + title: widget.server != null + ? Text(AppLocalizations.of(context)!.createConnection) + : Text(AppLocalizations.of(context)!.editConnection), + actions: [ + actions(), + const SizedBox(width: 8) + ], + ), + body: ListView( + children: form() + ), + ), + ); + } + else { + return Dialog( + child: SizedBox( + width: 400, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + IconButton( + onPressed: () => Navigator.pop(context), + icon: const Icon(Icons.clear_rounded) + ), + const SizedBox(width: 8), + Text( + AppLocalizations.of(context)!.createConnection, + style: const TextStyle( + fontSize: 20 + ), + ), + ], + ), + actions() + ], + ), + ), + Expanded( + child: ListView( + children: form() + ), + ) + ], + ), + ), + ); + } + } +} \ No newline at end of file diff --git a/lib/widgets/add_server/form_text_field.dart b/lib/widgets/add_server/form_text_field.dart new file mode 100644 index 0000000..0fb6f94 --- /dev/null +++ b/lib/widgets/add_server/form_text_field.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; + +class FormTextField extends StatelessWidget { + final TextEditingController controller; + final String label; + final String? error; + final IconData icon; + final TextInputType? keyboardType; + final Function(String)? onChanged; + final bool? obscureText; + final String? hintText; + final String? helperText; + final bool isConnecting; + + const FormTextField({ + Key? key, + required this.label, + required this.controller, + this.error, + required this.icon, + this.keyboardType, + this.onChanged, + this.obscureText, + this.hintText, + this.helperText, + required this.isConnecting + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: TextFormField( + controller: controller, + onChanged: onChanged, + obscureText: obscureText ?? false, + enabled: !isConnecting, + decoration: InputDecoration( + prefixIcon: Icon(icon), + errorText: error, + hintText: hintText, + helperText: helperText, + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + labelText: label, + ), + keyboardType: keyboardType, + ), + ); + } +} \ No newline at end of file diff --git a/lib/widgets/add_server_modal.dart b/lib/widgets/add_server_modal.dart deleted file mode 100644 index c7604a1..0000000 --- a/lib/widgets/add_server_modal.dart +++ /dev/null @@ -1,766 +0,0 @@ -// ignore_for_file: use_build_context_synchronously - -import 'package:provider/provider.dart'; -import 'package:flutter/material.dart'; -import 'package:uuid/uuid.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; - -import 'package:adguard_home_manager/providers/app_config_provider.dart'; -import 'package:adguard_home_manager/functions/snackbar.dart'; -import 'package:adguard_home_manager/constants/urls.dart'; -import 'package:adguard_home_manager/functions/open_url.dart'; -import 'package:adguard_home_manager/constants/enums.dart'; -import 'package:adguard_home_manager/providers/status_provider.dart'; -import 'package:adguard_home_manager/functions/base64.dart'; -import 'package:adguard_home_manager/services/http_requests.dart'; -import 'package:adguard_home_manager/models/app_log.dart'; -import 'package:adguard_home_manager/providers/servers_provider.dart'; -import 'package:adguard_home_manager/models/server.dart'; - -enum ConnectionType { http, https} - -class AddServerModal extends StatefulWidget { - final Server? server; - final bool window; - final void Function(String version) onUnsupportedVersion; - - const AddServerModal({ - Key? key, - this.server, - required this.window, - required this.onUnsupportedVersion - }) : super(key: key); - - @override - State createState() => _AddServerModalState(); -} - -class _AddServerModalState extends State { - final uuid = const Uuid(); - - final TextEditingController nameController = TextEditingController(); - String? nameError; - - ConnectionType connectionType = ConnectionType.http; - - final TextEditingController ipDomainController = TextEditingController(); - String? ipDomainError; - - final TextEditingController pathController = TextEditingController(); - String? pathError; - - final TextEditingController portController = TextEditingController(); - String? portError; - - final TextEditingController userController = TextEditingController(); - - final TextEditingController passwordController = TextEditingController(); - - bool defaultServer = false; - - bool homeAssistant = false; - - bool allDataValid = false; - - bool isConnecting = false; - - Widget sectionLabel(String label) { - return Padding( - padding: const EdgeInsets.symmetric( - horizontal: 24, - vertical: 24 - ), - child: Text( - label, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.primary - ), - ), - ); - } - - Widget textField({ - required String label, - required TextEditingController controller, - String? error, - required IconData icon, - TextInputType? keyboardType, - Function(String)? onChanged, - bool? obscureText, - String? hintText, - String? helperText - }) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: TextFormField( - controller: controller, - onChanged: onChanged, - obscureText: obscureText ?? false, - decoration: InputDecoration( - prefixIcon: Icon(icon), - errorText: error, - hintText: hintText, - helperText: helperText, - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - labelText: label, - ), - keyboardType: keyboardType, - ), - ); - } - - void checkDataValid() { - if ( - nameController.text != '' && - ipDomainController.text != '' && - ipDomainError == null && - pathError == null && - portError == null - ) { - setState(() { - allDataValid = true; - }); - } - else { - setState(() { - allDataValid = false; - }); - } - } - - - void validatePort(String? value) { - if (value != null && value != '') { - if (int.tryParse(value) != null && int.parse(value) <= 65535) { - setState(() { - portError = null; - }); - } - else { - setState(() { - portError = AppLocalizations.of(context)!.invalidPort; - }); - } - } - else { - setState(() { - portError = null; - }); - } - checkDataValid(); - } - - void validateSubroute(String? value) { - if (value != null && value != '') { - RegExp subrouteRegexp = RegExp(r'^\/\b([A-Za-z0-9_\-~/]*)[^\/|\.|\:]$'); - if (subrouteRegexp.hasMatch(value) == true) { - setState(() { - pathError = null; - }); - } - else { - setState(() { - pathError = AppLocalizations.of(context)!.invalidPath; - }); - } - } - else { - setState(() { - pathError = null; - }); - } - checkDataValid(); - } - - void validateAddress(String? value) { - if (value != null && value != '') { - RegExp ipAddress = RegExp(r'^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)(\.(?!$)|$)){4}$'); - RegExp domain = RegExp(r'^(([a-z0-9|-]+\.)*[a-z0-9|-]+\.[a-z]+)|((\w|-)+)$'); - if (ipAddress.hasMatch(value) == true || domain.hasMatch(value) == true) { - setState(() { - ipDomainError = null; - }); - } - else { - setState(() { - ipDomainError = AppLocalizations.of(context)!.invalidIpDomain; - }); - } - } - else { - setState(() { - ipDomainError = AppLocalizations.of(context)!.ipDomainNotEmpty; - }); - } - checkDataValid(); - } - - @override - void initState() { - if (widget.server != null) { - nameController.text = widget.server!.name; - connectionType = widget.server!.connectionMethod == 'https' ? ConnectionType.https : ConnectionType.http; - ipDomainController.text = widget.server!.domain; - pathController.text = widget.server!.path ?? ''; - portController.text = widget.server!.port != null ? widget.server!.port.toString() : ""; - userController.text = widget.server!.user ?? ""; - passwordController.text = widget.server!.password ?? ""; - defaultServer = widget.server!.defaultServer; - homeAssistant = widget.server!.runningOnHa; - } - checkDataValid(); - super.initState(); - } - - @override - Widget build(BuildContext context) { - final serversProvider = Provider.of(context, listen: false); - final statusProvider = Provider.of(context, listen: false); - final appConfigProvider = Provider.of(context, listen: false); - - final mediaQuery = MediaQuery.of(context); - - void cancelConnecting() { - if (mounted) { - setState(() => isConnecting = false); - } - else { - isConnecting = false; - } - } - - void connect() async { - Server serverObj = Server( - id: uuid.v4(), - name: nameController.text, - connectionMethod: connectionType.name, - domain: ipDomainController.text, - port: portController.text != '' ? int.parse(portController.text) : null, - user: userController.text != "" ? userController.text : null, - password: passwordController.text != "" ? passwordController.text : null, - defaultServer: defaultServer, - authToken: homeAssistant == true - ? encodeBase64UserPass(userController.text, passwordController.text) - : null, - runningOnHa: homeAssistant - ); - setState(() => isConnecting = true); - - final result = homeAssistant == true - ? await loginHA(serverObj) - : await login(serverObj); - - if (!mounted) return; - - if (result['result'] == 'success') { - if (serverObj.user != null && serverObj.password != null) { - serverObj.authToken = encodeBase64UserPass(serverObj.user!, serverObj.password!); - } - final serverCreated = await serversProvider.createServer(serverObj); - if (serverCreated == null) { - statusProvider.setServerStatusLoad(LoadStatus.loading); - - final ApiClient apiClient = ApiClient(server: serverObj); - - final serverStatus = await apiClient.getServerStatus(); - - if (!mounted) return; - - if (serverStatus['result'] == 'success') { - statusProvider.setServerStatusData( - data: serverStatus['data'] - ); - serversProvider.setApiClient(apiClient); - statusProvider.setServerStatusLoad(LoadStatus.loaded); - if (serverStatus['data'].serverVersion.contains('a') || serverStatus['data'].serverVersion.contains('b')) { - Navigator.pop(context); - widget.onUnsupportedVersion(serverStatus['data'].serverVersion); - } - else { - Navigator.pop(context); - } - } - else { - appConfigProvider.addLog(serverStatus['log']); - statusProvider.setServerStatusLoad(LoadStatus.error); - Navigator.pop(context); - } - } - else { - setState(() => isConnecting = false); - appConfigProvider.addLog( - AppLog( - type: 'save_connection_db', - dateTime: DateTime.now(), - message: serverCreated.toString() - ) - ); - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.connectionNotCreated, - color: Colors.red - ); - } - } - else if (result['result'] == 'invalid_username_password') { - cancelConnecting(); - appConfigProvider.addLog(result['log']); - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.invalidUsernamePassword, - color: Colors.red - ); - } - else if (result['result'] == 'many_attempts') { - cancelConnecting(); - appConfigProvider.addLog(result['log']); - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.tooManyAttempts, - color: Colors.red - ); - } - else if (result['result'] == 'no_connection') { - cancelConnecting(); - appConfigProvider.addLog(result['log']); - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.cantReachServer, - color: Colors.red - ); - } - else if (result['result'] == 'ssl_error') { - cancelConnecting(); - appConfigProvider.addLog(result['log']); - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.sslError, - color: Colors.red - ); - } - else if (result['result'] == 'server_error') { - cancelConnecting(); - appConfigProvider.addLog(result['log']); - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.serverError, - color: Colors.red - ); - } - else { - cancelConnecting(); - appConfigProvider.addLog(result['log']); - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.unknownError, - color: Colors.red - ); - } - } - - void edit() async { - final Server serverObj = Server( - id: widget.server!.id, - name: nameController.text, - connectionMethod: connectionType.name, - domain: ipDomainController.text, - port: portController.text != '' ? int.parse(portController.text) : null, - user: userController.text != "" ? userController.text : null, - password: passwordController.text != "" ? passwordController.text : null, - defaultServer: defaultServer, - authToken: homeAssistant == true - ? encodeBase64UserPass(userController.text, passwordController.text) - : null, - runningOnHa: homeAssistant - ); - - final result = homeAssistant == true - ? await loginHA(serverObj) - : await login(serverObj); - - if (!mounted) return; - if (result['result'] == 'success') { - if (serverObj.user != null && serverObj.password != null) { - serverObj.authToken = encodeBase64UserPass(serverObj.user!, serverObj.password!); - } - final serverSaved = await serversProvider.editServer(serverObj); - - if (!mounted) return; - if (serverSaved == null) { - final ApiClient apiClient = ApiClient(server: serverObj); - final version = await apiClient.getServerVersion(); - if ( - version['result'] == 'success' && - (version['data'].contains('a') || version['data'].contains('b')) // alpha or beta - ) { - Navigator.pop(context); - widget.onUnsupportedVersion(version['data']); - } - else { - Navigator.pop(context); - } - } - else { - appConfigProvider.addLog( - AppLog( - type: 'edit_connection_db', - dateTime: DateTime.now(), - message: serverSaved.toString() - ) - ); - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.connectionNotCreated, - color: Colors.red - ); - } - } - else if (result['result'] == 'invalid_username_password') { - appConfigProvider.addLog(result['log']); - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.invalidUsernamePassword, - color: Colors.red - ); - } - else if (result['result'] == 'many_attempts') { - appConfigProvider.addLog(result['log']); - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.tooManyAttempts, - color: Colors.red - ); - } - else if (result['result'] == 'no_connection') { - appConfigProvider.addLog(result['log']); - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.cantReachServer, - color: Colors.red - ); - } - else if (result['result'] == 'ssl_error') { - appConfigProvider.addLog(result['log']); - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.sslError, - color: Colors.red - ); - } - else if (result['result'] == 'server_error') { - appConfigProvider.addLog(result['log']); - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.serverError, - color: Colors.red - ); - } - else { - appConfigProvider.addLog(result['log']); - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.unknownError, - color: Colors.red - ); - } - } - - List form() { - return [ - Container( - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), - margin: const EdgeInsets.only( - top: 24, - left: 24, - right: 24 - ), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.primary.withOpacity(0.05), - borderRadius: BorderRadius.circular(30), - border: Border.all( - color: Theme.of(context).colorScheme.primary - ) - ), - child: Text( - "${connectionType.name}://${ipDomainController.text}${portController.text != '' ? ':${portController.text}' : ""}${pathController.text}", - textAlign: TextAlign.center, - style: TextStyle( - color: Theme.of(context).colorScheme.primary, - fontWeight: FontWeight.w500 - ), - ), - ), - sectionLabel(AppLocalizations.of(context)!.general), - textField( - label: AppLocalizations.of(context)!.name, - controller: nameController, - icon: Icons.badge_rounded, - error: nameError, - onChanged: (value) { - if (value != '') { - setState(() => nameError = null); - } - else { - setState(() => nameError = AppLocalizations.of(context)!.nameNotEmpty); - } - checkDataValid(); - } - ), - sectionLabel(AppLocalizations.of(context)!.connection), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: SegmentedButton( - segments: const [ - ButtonSegment( - value: ConnectionType.http, - label: Text("HTTP") - ), - ButtonSegment( - value: ConnectionType.https, - label: Text("HTTPS") - ), - ], - selected: {connectionType}, - onSelectionChanged: (value) => setState(() => connectionType = value.first), - ), - ), - const SizedBox(height: 30), - textField( - label: AppLocalizations.of(context)!.ipDomain, - controller: ipDomainController, - icon: Icons.link_rounded, - error: ipDomainError, - keyboardType: TextInputType.url, - onChanged: validateAddress - ), - const SizedBox(height: 20), - textField( - label: AppLocalizations.of(context)!.path, - controller: pathController, - icon: Icons.route_rounded, - error: pathError, - onChanged: validateSubroute, - hintText: AppLocalizations.of(context)!.examplePath, - helperText: AppLocalizations.of(context)!.helperPath, - ), - const SizedBox(height: 20), - textField( - label: AppLocalizations.of(context)!.port, - controller: portController, - icon: Icons.numbers_rounded, - error: portError, - keyboardType: TextInputType.number, - onChanged: validatePort - ), - sectionLabel(AppLocalizations.of(context)!.authentication), - textField( - label: AppLocalizations.of(context)!.username, - controller: userController, - icon: Icons.person_rounded, - ), - const SizedBox(height: 20), - textField( - label: AppLocalizations.of(context)!.password, - controller: passwordController, - icon: Icons.lock_rounded, - keyboardType: TextInputType.visiblePassword, - obscureText: true - ), - sectionLabel(AppLocalizations.of(context)!.other), - Material( - color: Colors.transparent, - child: InkWell( - onTap: widget.server == null - ? () => setState(() => defaultServer = !defaultServer) - : null, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - AppLocalizations.of(context)!.defaultServer, - style: const TextStyle( - fontSize: 15, - ), - ), - Switch( - value: defaultServer, - onChanged: widget.server == null - ? (value) => setState(() => defaultServer = value) - : null, - ) - ], - ), - ), - ), - ), - const SizedBox(height: 20), - Material( - color: Colors.transparent, - child: InkWell( - onTap: () => setState(() => homeAssistant = !homeAssistant), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - AppLocalizations.of(context)!.runningHomeAssistant, - style: const TextStyle( - fontSize: 15, - ), - ), - Switch( - value: homeAssistant, - onChanged: (value) => setState(() => homeAssistant = value), - ) - ], - ), - ), - ), - ), - const SizedBox(height: 20), - ]; - } - - if (widget.window == true) { - return Dialog( - child: SizedBox( - width: 400, - child: Column( - children: [ - Padding( - padding: const EdgeInsets.all(16), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - IconButton( - onPressed: () => Navigator.pop(context), - icon: const Icon(Icons.clear_rounded) - ), - const SizedBox(width: 8), - Text( - AppLocalizations.of(context)!.createConnection, - style: const TextStyle( - fontSize: 20 - ), - ), - ], - ), - Row( - children: [ - IconButton( - onPressed: () => openUrl(Urls.connectionInstructions), - icon: const Icon(Icons.help_outline_outlined) - ), - IconButton( - tooltip: widget.server == null - ? AppLocalizations.of(context)!.connect - : AppLocalizations.of(context)!.save, - onPressed: allDataValid == true - ? widget.server == null - ? () => connect() - : () => edit() - : null, - icon: Icon( - widget.server == null - ? Icons.login_rounded - : Icons.save_rounded - ) - ), - ], - ), - ], - ), - ), - Expanded( - child: ListView( - children: form() - ), - ) - ], - ), - ), - ); - } - else { - return Stack( - children: [ - Scaffold( - appBar: AppBar( - title: Text(AppLocalizations.of(context)!.createConnection), - actions: [ - IconButton( - onPressed: () => openUrl(Urls.connectionInstructions), - icon: const Icon(Icons.help_outline_outlined) - ), - IconButton( - tooltip: widget.server == null - ? AppLocalizations.of(context)!.connect - : AppLocalizations.of(context)!.save, - onPressed: allDataValid == true - ? widget.server == null - ? () => connect() - : () => edit() - : null, - icon: Icon( - widget.server == null - ? Icons.login_rounded - : Icons.save_rounded - ) - ), - const SizedBox(width: 10) - ], - toolbarHeight: 70, - ), - body: ListView( - children: form(), - ) - ), - AnimatedOpacity( - opacity: isConnecting == true ? 1 : 0, - duration: const Duration(milliseconds: 250), - curve: Curves.easeInOut, - child: IgnorePointer( - ignoring: isConnecting == true ? false : true, - child: Scaffold( - backgroundColor: Colors.transparent, - body: Container( - width: mediaQuery.size.width, - height: mediaQuery.size.height, - color: const Color.fromRGBO(0, 0, 0, 0.7), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const CircularProgressIndicator( - color: Colors.white, - ), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.connecting, - style: const TextStyle( - color: Colors.white, - fontWeight: FontWeight.w500, - fontSize: 26 - ), - ) - ], - ), - ), - ), - ), - ) - ], - ); - } - } -} \ No newline at end of file diff --git a/lib/widgets/servers_list/servers_list_item.dart b/lib/widgets/servers_list/servers_list_item.dart index 724be22..280ae7c 100644 --- a/lib/widgets/servers_list/servers_list_item.dart +++ b/lib/widgets/servers_list/servers_list_item.dart @@ -5,8 +5,7 @@ import 'package:expandable/expandable.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:adguard_home_manager/widgets/version_warning_modal.dart'; -import 'package:adguard_home_manager/widgets/add_server_modal.dart'; +import 'package:adguard_home_manager/widgets/add_server/add_server_functions.dart'; import 'package:adguard_home_manager/widgets/servers_list/delete_modal.dart'; import 'package:adguard_home_manager/classes/process_modal.dart'; @@ -88,41 +87,9 @@ class _ServersListItemState extends State with SingleTickerProv }); } - void openAddServerBottomSheet({Server? server}) async { + void openServerModal({Server? server}) async { await Future.delayed(const Duration(seconds: 0), (() => { - if (width > 700) { - showDialog( - context: context, - barrierDismissible: false, - builder: (context) => AddServerModal( - server: server, - window: true, - onUnsupportedVersion: (version) => showDialog( - context: context, - builder: (ctx) => VersionWarningModal( - version: version - ), - barrierDismissible: false - ), - ), - ) - } - else { - Navigator.push(context, MaterialPageRoute( - fullscreenDialog: true, - builder: (BuildContext context) => AddServerModal( - server: server, - window: false, - onUnsupportedVersion: (version) => showDialog( - context: context, - builder: (ctx) => VersionWarningModal( - version: version - ), - barrierDismissible: false - ), - ) - )) - } + openServerFormModal(context: context, width: width, server: server) })); } @@ -326,7 +293,7 @@ class _ServersListItemState extends State with SingleTickerProv ) ), PopupMenuItem( - onTap: (() => openAddServerBottomSheet(server: server)), + onTap: (() => openServerModal(server: server)), child: Row( children: [ const Icon(Icons.edit), diff --git a/lib/widgets/servers_list/servers_tile_item.dart b/lib/widgets/servers_list/servers_tile_item.dart index 1cbd622..8407526 100644 --- a/lib/widgets/servers_list/servers_tile_item.dart +++ b/lib/widgets/servers_list/servers_tile_item.dart @@ -4,8 +4,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:adguard_home_manager/widgets/version_warning_modal.dart'; -import 'package:adguard_home_manager/widgets/add_server_modal.dart'; +import 'package:adguard_home_manager/widgets/add_server/add_server_functions.dart'; import 'package:adguard_home_manager/widgets/servers_list/delete_modal.dart'; import 'package:adguard_home_manager/classes/process_modal.dart'; @@ -57,41 +56,9 @@ class _ServersTileItemState extends State with SingleTickerProv }); } - void openAddServerBottomSheet({Server? server}) async { + void openServerModal({Server? server}) async { await Future.delayed(const Duration(seconds: 0), (() => { - if (width > 700) { - showDialog( - context: context, - barrierDismissible: false, - builder: (context) => AddServerModal( - server: server, - window: true, - onUnsupportedVersion: (version) => showDialog( - context: context, - builder: (ctx) => VersionWarningModal( - version: version - ), - barrierDismissible: false - ), - ), - ) - } - else { - Navigator.push(context, MaterialPageRoute( - fullscreenDialog: true, - builder: (BuildContext context) => AddServerModal( - server: server, - window: false, - onUnsupportedVersion: (version) => showDialog( - context: context, - builder: (ctx) => VersionWarningModal( - version: version - ), - barrierDismissible: false - ), - ) - )) - } + openServerFormModal(context: context, width: width, server: server) })); } @@ -291,7 +258,7 @@ class _ServersTileItemState extends State with SingleTickerProv ) ), PopupMenuItem( - onTap: (() => openAddServerBottomSheet(server: server)), + onTap: (() => openServerModal(server: server)), child: Row( children: [ const Icon(Icons.edit), From 36bd7acfedf1560e7f7d9147b9febf590b3b3e5b Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sat, 7 Oct 2023 21:51:29 +0200 Subject: [PATCH 02/14] Improvements and fixes --- .../add_server/add_server_functions.dart | 2 +- lib/widgets/add_server/add_server_modal.dart | 134 ++++++++---------- 2 files changed, 59 insertions(+), 77 deletions(-) diff --git a/lib/widgets/add_server/add_server_functions.dart b/lib/widgets/add_server/add_server_functions.dart index 16a0149..6dd9c38 100644 --- a/lib/widgets/add_server/add_server_functions.dart +++ b/lib/widgets/add_server/add_server_functions.dart @@ -68,7 +68,7 @@ String? validateAddress({ }) { if (value != null && value != '') { RegExp ipAddress = RegExp(r'^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)(\.(?!$)|$)){4}$'); - RegExp domain = RegExp(r'^(([a-z0-9|-]+\.)*[a-z0-9|-]+\.[a-z]+)|((\w|-)+)$'); + RegExp domain = RegExp(r'^(([a-z0-9|-]+\.)*[a-z0-9|-]+\.[a-z]+)$'); if (ipAddress.hasMatch(value) == true || domain.hasMatch(value) == true) { return null; } diff --git a/lib/widgets/add_server/add_server_modal.dart b/lib/widgets/add_server/add_server_modal.dart index 7eb8949..294cd7d 100644 --- a/lib/widgets/add_server/add_server_modal.dart +++ b/lib/widgets/add_server/add_server_modal.dart @@ -6,6 +6,8 @@ import 'package:uuid/uuid.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/widgets/add_server/form_text_field.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/widgets/add_server/add_server_functions.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; @@ -67,23 +69,6 @@ class _AddServerModalState extends State { bool isConnecting = false; - Widget sectionLabel(String label) { - return Padding( - padding: const EdgeInsets.symmetric( - horizontal: 24, - vertical: 24 - ), - child: Text( - label, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.primary - ), - ), - ); - } - @override void initState() { if (widget.server != null) { @@ -166,12 +151,13 @@ class _AddServerModalState extends State { cancelConnecting(); appConfigProvider.addLog(result['log']); if (mounted) { - return showSnacbkar( + showSnacbkar( appConfigProvider: appConfigProvider, label: getErrorMessage(result['result']), color: Colors.red ); } + return; } if (serverObj.user != null && serverObj.password != null) { @@ -181,7 +167,7 @@ class _AddServerModalState extends State { final serverCreated = await serversProvider.createServer(serverObj); // If something goes wrong when saving the connection on the db - if (serverCreated == null) { + if (serverCreated != null) { if (mounted) setState(() => isConnecting = false); appConfigProvider.addLog( AppLog( @@ -197,6 +183,7 @@ class _AddServerModalState extends State { color: Colors.red ); } + return; } statusProvider.setServerStatusLoad(LoadStatus.loading); @@ -208,6 +195,7 @@ class _AddServerModalState extends State { appConfigProvider.addLog(serverStatus['log']); statusProvider.setServerStatusLoad(LoadStatus.error); Navigator.pop(context); + return; } // If everything is successful @@ -223,6 +211,7 @@ class _AddServerModalState extends State { else { Navigator.pop(context); } + return; } void edit() async { @@ -250,12 +239,13 @@ class _AddServerModalState extends State { cancelConnecting(); appConfigProvider.addLog(result['log']); if (mounted) { - return showSnacbkar( + showSnacbkar( appConfigProvider: appConfigProvider, label: getErrorMessage(result['result']), color: Colors.red ); } + return; } if (serverObj.user != null && serverObj.password != null) { @@ -264,7 +254,7 @@ class _AddServerModalState extends State { final serverSaved = await serversProvider.editServer(serverObj); // If something goes wrong when saving the connection on the db - if (serverSaved == null) { + if (serverSaved != null) { if (mounted) setState(() => isConnecting = false); appConfigProvider.addLog( AppLog( @@ -280,6 +270,7 @@ class _AddServerModalState extends State { color: Colors.red ); } + return; } // If everything is successful @@ -294,7 +285,8 @@ class _AddServerModalState extends State { } else { Navigator.pop(context); - } + } + return; } Widget actions() { @@ -314,7 +306,11 @@ class _AddServerModalState extends State { : () => edit() : null, icon: isConnecting - ? const CircularProgressIndicator() + ? const SizedBox( + width: 16, + height: 16, + child: CircularProgressIndicator() + ) : Icon( widget.server == null ? Icons.login_rounded @@ -350,7 +346,10 @@ class _AddServerModalState extends State { ), ), ), - sectionLabel(AppLocalizations.of(context)!.general), + SectionLabel( + label: AppLocalizations.of(context)!.general, + padding: const EdgeInsets.all(24), + ), FormTextField( label: AppLocalizations.of(context)!.name, controller: nameController, @@ -367,7 +366,10 @@ class _AddServerModalState extends State { }, isConnecting: isConnecting, ), - sectionLabel(AppLocalizations.of(context)!.connection), + SectionLabel( + label: AppLocalizations.of(context)!.connection, + padding: const EdgeInsets.all(24), + ), Padding( padding: const EdgeInsets.symmetric(horizontal: 24), child: SegmentedButton( @@ -425,7 +427,10 @@ class _AddServerModalState extends State { }, isConnecting: isConnecting, ), - sectionLabel(AppLocalizations.of(context)!.authentication), + SectionLabel( + label: AppLocalizations.of(context)!.authentication, + padding: const EdgeInsets.all(24), + ), FormTextField( label: AppLocalizations.of(context)!.username, controller: userController, @@ -441,58 +446,32 @@ class _AddServerModalState extends State { obscureText: true, isConnecting: isConnecting, ), - sectionLabel(AppLocalizations.of(context)!.other), - Material( - color: Colors.transparent, - child: InkWell( - onTap: widget.server == null - ? () => setState(() => defaultServer = !defaultServer) - : null, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - AppLocalizations.of(context)!.defaultServer, - style: const TextStyle( - fontSize: 15, - ), - ), - Switch( - value: defaultServer, - onChanged: widget.server == null - ? (value) => setState(() => defaultServer = value) - : null, - ) - ], - ), - ), + SectionLabel( + label: AppLocalizations.of(context)!.other, + padding: const EdgeInsets.only( + top: 32, + left: 24, + bottom: 12 ), ), - const SizedBox(height: 20), - Material( - color: Colors.transparent, - child: InkWell( - onTap: () => setState(() => homeAssistant = !homeAssistant), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - AppLocalizations.of(context)!.runningHomeAssistant, - style: const TextStyle( - fontSize: 15, - ), - ), - Switch( - value: homeAssistant, - onChanged: (value) => setState(() => homeAssistant = value), - ) - ], - ), - ), + CustomSwitchListTile( + value: defaultServer, + onChanged: (value) => setState(() => defaultServer = value), + title: AppLocalizations.of(context)!.defaultServer, + disabled: widget.server != null || isConnecting, + padding: const EdgeInsets.symmetric( + horizontal: 24, + vertical: 4 + ), + ), + CustomSwitchListTile( + value: homeAssistant, + onChanged: (value) => setState(() => homeAssistant = value), + title: AppLocalizations.of(context)!.runningHomeAssistant, + disabled: widget.server != null || isConnecting, + padding: const EdgeInsets.symmetric( + horizontal: 24, + vertical: 4 ), ), const SizedBox(height: 20), @@ -503,6 +482,9 @@ class _AddServerModalState extends State { return Dialog.fullscreen( child: Scaffold( appBar: AppBar( + leading: CloseButton( + onPressed: () => Navigator.pop(context), + ), title: widget.server != null ? Text(AppLocalizations.of(context)!.createConnection) : Text(AppLocalizations.of(context)!.editConnection), From adaa75e7173e3b8174ecd623501c8aac232dca4e Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sat, 7 Oct 2023 23:03:30 +0200 Subject: [PATCH 03/14] Refactor clients --- lib/screens/clients/added_list.dart | 7 +- .../{ => client}/active_client_tile.dart | 0 .../{ => client}/added_client_tile.dart | 14 +- .../client/blocked_services_section.dart | 114 +++ lib/screens/clients/client/client_screen.dart | 442 +++++++++ .../client/client_screen_functions.dart | 87 ++ .../clients/client/identifiers_section.dart | 103 +++ .../{ => client}/logs_list_client.dart | 0 .../{ => client}/remove_client_modal.dart | 0 .../{ => client}/safe_search_modal.dart | 0 .../clients/{ => client}/services_modal.dart | 0 lib/screens/clients/client/settings_tile.dart | 63 ++ .../clients/{ => client}/tags_modal.dart | 0 lib/screens/clients/client/tags_section.dart | 63 ++ .../client/upstream_servers_section.dart | 111 +++ lib/screens/clients/client_screen.dart | 838 ------------------ lib/screens/clients/clients.dart | 6 +- lib/screens/clients/clients_desktop_view.dart | 2 +- lib/screens/clients/clients_list.dart | 2 +- lib/screens/clients/fab.dart | 2 +- lib/screens/clients/search_clients.dart | 4 +- .../access_settings/clients_list.dart | 2 +- 22 files changed, 1009 insertions(+), 851 deletions(-) rename lib/screens/clients/{ => client}/active_client_tile.dart (100%) rename lib/screens/clients/{ => client}/added_client_tile.dart (96%) create mode 100644 lib/screens/clients/client/blocked_services_section.dart create mode 100644 lib/screens/clients/client/client_screen.dart create mode 100644 lib/screens/clients/client/client_screen_functions.dart create mode 100644 lib/screens/clients/client/identifiers_section.dart rename lib/screens/clients/{ => client}/logs_list_client.dart (100%) rename lib/screens/clients/{ => client}/remove_client_modal.dart (100%) rename lib/screens/clients/{ => client}/safe_search_modal.dart (100%) rename lib/screens/clients/{ => client}/services_modal.dart (100%) create mode 100644 lib/screens/clients/client/settings_tile.dart rename lib/screens/clients/{ => client}/tags_modal.dart (100%) create mode 100644 lib/screens/clients/client/tags_section.dart create mode 100644 lib/screens/clients/client/upstream_servers_section.dart delete mode 100644 lib/screens/clients/client_screen.dart diff --git a/lib/screens/clients/added_list.dart b/lib/screens/clients/added_list.dart index 6b7f33c..c49667d 100644 --- a/lib/screens/clients/added_list.dart +++ b/lib/screens/clients/added_list.dart @@ -9,9 +9,9 @@ import 'package:flutter_split_view/flutter_split_view.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:adguard_home_manager/screens/clients/client_screen.dart'; -import 'package:adguard_home_manager/screens/clients/added_client_tile.dart'; -import 'package:adguard_home_manager/screens/clients/remove_client_modal.dart'; +import 'package:adguard_home_manager/screens/clients/client/client_screen.dart'; +import 'package:adguard_home_manager/screens/clients/client/added_client_tile.dart'; +import 'package:adguard_home_manager/screens/clients/client/remove_client_modal.dart'; import 'package:adguard_home_manager/screens/clients/fab.dart'; import 'package:adguard_home_manager/screens/clients/options_modal.dart'; import 'package:adguard_home_manager/widgets/tab_content_list.dart'; @@ -205,6 +205,7 @@ class _AddedListState extends State { onTap: widget.onClientSelected, onLongPress: openOptionsModal, onEdit: openClientModal, + onDelete: openDeleteModal, splitView: widget.splitView, serverVersion: statusProvider.serverStatus!.serverVersion, ), diff --git a/lib/screens/clients/active_client_tile.dart b/lib/screens/clients/client/active_client_tile.dart similarity index 100% rename from lib/screens/clients/active_client_tile.dart rename to lib/screens/clients/client/active_client_tile.dart diff --git a/lib/screens/clients/added_client_tile.dart b/lib/screens/clients/client/added_client_tile.dart similarity index 96% rename from lib/screens/clients/added_client_tile.dart rename to lib/screens/clients/client/added_client_tile.dart index c63c816..356d7be 100644 --- a/lib/screens/clients/added_client_tile.dart +++ b/lib/screens/clients/client/added_client_tile.dart @@ -15,6 +15,7 @@ class AddedClientTile extends StatelessWidget { final void Function(Client) onTap; final void Function(Client) onLongPress; final void Function(Client) onEdit; + final void Function(Client) onDelete; final Client? selectedClient; final bool? splitView; final String serverVersion; @@ -25,6 +26,7 @@ class AddedClientTile extends StatelessWidget { required this.onTap, required this.onLongPress, required this.onEdit, + required this.onDelete, this.selectedClient, required this.splitView, required this.serverVersion @@ -43,13 +45,21 @@ class AddedClientTile extends StatelessWidget { child: ContextMenuArea( builder: (context) => [ CustomListTile( - title: AppLocalizations.of(context)!.seeDetails, - icon: Icons.file_open_rounded, + title: AppLocalizations.of(context)!.edit, + icon: Icons.edit_rounded, onTap: () { Navigator.pop(context); onEdit(client); } ), + CustomListTile( + title: AppLocalizations.of(context)!.delete, + icon: Icons.delete_rounded, + onTap: () { + Navigator.pop(context); + onDelete(client); + } + ), CustomListTile( title: AppLocalizations.of(context)!.copyClipboard, icon: Icons.copy_rounded, diff --git a/lib/screens/clients/client/blocked_services_section.dart b/lib/screens/clients/client/blocked_services_section.dart new file mode 100644 index 0000000..37c01d3 --- /dev/null +++ b/lib/screens/clients/client/blocked_services_section.dart @@ -0,0 +1,114 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +import 'package:adguard_home_manager/screens/clients/client/client_screen_functions.dart'; + +class BlockedServicesSection extends StatelessWidget { + final bool useGlobalSettingsServices; + final List blockedServices; + final void Function(List) onUpdatedBlockedServices; + final void Function(bool) onUpdateServicesGlobalSettings; + + const BlockedServicesSection({ + Key? key, + required this.useGlobalSettingsServices, + required this.blockedServices, + required this.onUpdatedBlockedServices, + required this.onUpdateServicesGlobalSettings + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Material( + color: Theme.of(context).colorScheme.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(28), + child: InkWell( + onTap: () => onUpdateServicesGlobalSettings(!useGlobalSettingsServices), + borderRadius: BorderRadius.circular(28), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 5 + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + AppLocalizations.of(context)!.useGlobalSettings, + style: TextStyle( + fontSize: 16, + color: Theme.of(context).colorScheme.onSurface + ), + ), + Switch( + value: useGlobalSettingsServices, + onChanged: (value) => onUpdateServicesGlobalSettings(value), + ) + ], + ), + ), + ), + ), + ), + const SizedBox(height: 10), + Material( + color: Colors.transparent, + child: InkWell( + onTap: useGlobalSettingsServices == false + ? () => openServicesModal( + context: context, + blockedServices: blockedServices, + onUpdateBlockedServices: onUpdatedBlockedServices + ) + : null, + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 8, horizontal: 24 + ), + child: Row( + children: [ + Icon( + Icons.public, + color: useGlobalSettingsServices == false + ? Theme.of(context).listTileTheme.iconColor + : Theme.of(context).colorScheme.onSurface.withOpacity(0.38), + ), + const SizedBox(width: 16), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + AppLocalizations.of(context)!.selectBlockedServices, + style: TextStyle( + fontSize: 16, + color: useGlobalSettingsServices == false + ? Theme.of(context).colorScheme.onSurface + : Theme.of(context).colorScheme.onSurface.withOpacity(0.38), + ), + ), + if (useGlobalSettingsServices == false) ...[ + const SizedBox(height: 5), + Text( + blockedServices.isNotEmpty + ? "${blockedServices.length} ${AppLocalizations.of(context)!.servicesBlocked}" + : AppLocalizations.of(context)!.noBlockedServicesSelected, + style: TextStyle( + color: Theme.of(context).listTileTheme.iconColor + ), + ) + ] + ], + ) + ], + ), + ), + ), + ), + ], + ); + } +} \ No newline at end of file diff --git a/lib/screens/clients/client/client_screen.dart b/lib/screens/clients/client/client_screen.dart new file mode 100644 index 0000000..5994767 --- /dev/null +++ b/lib/screens/clients/client/client_screen.dart @@ -0,0 +1,442 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:uuid/uuid.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +import 'package:adguard_home_manager/screens/clients/client/upstream_servers_section.dart'; +import 'package:adguard_home_manager/screens/clients/client/identifiers_section.dart'; +import 'package:adguard_home_manager/screens/clients/client/blocked_services_section.dart'; +import 'package:adguard_home_manager/screens/clients/client/tags_section.dart'; +import 'package:adguard_home_manager/screens/clients/client/settings_tile.dart'; +import 'package:adguard_home_manager/screens/clients/client/client_screen_functions.dart'; +import 'package:adguard_home_manager/widgets/section_label.dart'; +import 'package:adguard_home_manager/widgets/custom_list_tile.dart'; + +import 'package:adguard_home_manager/functions/compare_versions.dart'; +import 'package:adguard_home_manager/models/safe_search.dart'; +import 'package:adguard_home_manager/providers/clients_provider.dart'; +import 'package:adguard_home_manager/providers/status_provider.dart'; +import 'package:adguard_home_manager/models/clients.dart'; + +class ClientScreen extends StatefulWidget { + final Client? client; + final String serverVersion; + final void Function(Client) onConfirm; + final void Function(Client)? onDelete; + final bool dialog; + + const ClientScreen({ + Key? key, + this.client, + required this.serverVersion, + required this.onConfirm, + this.onDelete, + required this.dialog + }) : super(key: key); + + @override + State createState() => _ClientScreenState(); +} + +class _ClientScreenState extends State { + final Uuid uuid = const Uuid(); + + bool validValues = false; + + TextEditingController nameController = TextEditingController(); + + List selectedTags = []; + + List> identifiersControllers = [ + { + 'id': 0, + 'controller': TextEditingController() + } + ]; + + bool useGlobalSettingsFiltering = true; + bool? enableFiltering; + bool? enableSafeBrowsing; + bool? enableParentalControl; + bool? enableSafeSearch; + SafeSearch? safeSearch; + + final SafeSearch defaultSafeSearch = SafeSearch( + enabled: false, + bing: false, + duckduckgo: false, + google: false, + pixabay: false, + yandex: false, + youtube: false + ); + + bool useGlobalSettingsServices = true; + List blockedServices = []; + + List> upstreamServers = []; + + bool version = false; + + @override + void initState() { + version = serverVersionIsAhead( + currentVersion: widget.serverVersion, + referenceVersion: 'v0.107.28', + referenceVersionBeta: 'v0.108.0-b.33' + ); + + if (widget.client != null) { + validValues = true; + + nameController.text = widget.client!.name; + selectedTags = widget.client!.tags; + identifiersControllers = widget.client!.ids.map((e) => { + 'id': uuid.v4(), + 'controller': TextEditingController(text: e) + }).toList(); + useGlobalSettingsFiltering = widget.client!.useGlobalSettings; + enableFiltering = widget.client!.filteringEnabled; + enableParentalControl = widget.client!.parentalEnabled; + enableSafeBrowsing = widget.client!.safebrowsingEnabled; + if (version == true) { + safeSearch = widget.client!.safeSearch; + } + else { + enableSafeSearch = widget.client!.safesearchEnabled ?? false; + } + useGlobalSettingsServices = widget.client!.useGlobalBlockedServices; + blockedServices = widget.client!.blockedServices; + upstreamServers = widget.client!.upstreams.map((e) => { + 'id': uuid.v4(), + 'controller': TextEditingController(text: e) + }).toList(); + } + super.initState(); + } + + @override + Widget build(BuildContext context) { + final clientsProvider = Provider.of(context); + final statusProvider = Provider.of(context); + + void createClient() { + final Client client = Client( + name: nameController.text, + ids: List.from(identifiersControllers.map((e) => e['controller'].text)), + useGlobalSettings: useGlobalSettingsFiltering, + filteringEnabled: enableFiltering ?? false, + parentalEnabled: enableParentalControl ?? false, + safebrowsingEnabled: enableSafeBrowsing ?? false, + safesearchEnabled: version == false ? enableSafeSearch : null, + safeSearch: version == true ? safeSearch : null, + useGlobalBlockedServices: useGlobalSettingsServices, + blockedServices: blockedServices, + upstreams: List.from(upstreamServers.map((e) => e['controller'].text)), + tags: selectedTags + ); + widget.onConfirm(client); + } + + void enableDisableGlobalSettingsFiltering() { + if (useGlobalSettingsFiltering == true) { + setState(() { + useGlobalSettingsFiltering = false; + + enableFiltering = false; + enableSafeBrowsing = false; + enableParentalControl = false; + enableSafeSearch = false; + safeSearch = defaultSafeSearch; + }); + } + else if (useGlobalSettingsFiltering == false) { + setState(() { + useGlobalSettingsFiltering = true; + + enableFiltering = null; + enableSafeBrowsing = null; + enableParentalControl = null; + enableSafeSearch = null; + safeSearch = null; + }); + } + } + + void updateServicesGlobalSettings(bool value) { + if (value == true) { + setState(() { + blockedServices = []; + useGlobalSettingsServices = true; + }); + } + else if (value == false) { + setState(() { + useGlobalSettingsServices = false; + }); + } + } + + List actions() { + return [ + IconButton( + onPressed: validValues == true + ? () { + createClient(); + Navigator.pop(context); + } + : null, + icon: const Icon(Icons.save_rounded), + tooltip: AppLocalizations.of(context)!.save, + ), + if (widget.client != null) IconButton( + onPressed: () => openDeleteClientScreen( + context: context, + onDelete: () => clientsProvider.deleteClient(widget.client!), + ), + icon: const Icon(Icons.delete_rounded), + tooltip: AppLocalizations.of(context)!.delete, + ), + const SizedBox(width: 10), + ]; + } + + Widget content(bool withPaddingTop) { + return ListView( + padding: const EdgeInsets.only(top: 0), + children: [ + if (withPaddingTop == true) const SizedBox(height: 24), + if (withPaddingTop == false) const SizedBox(height: 6), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: TextFormField( + enabled: widget.client != null ? false : true, + controller: nameController, + onChanged: (_) => setState(() { + validValues = checkValidValues( + identifiersControllers: identifiersControllers, + nameController: nameController + ); + }), + decoration: InputDecoration( + prefixIcon: const Icon(Icons.badge_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + labelText: AppLocalizations.of(context)!.name, + ), + ), + ), + SectionLabel( + label: AppLocalizations.of(context)!.tags, + padding: const EdgeInsets.all(24), + ), + TagsSection( + selectedTags: selectedTags, + onTagsSelected: (tags) => setState(() => selectedTags = tags) + ), + IdentifiersSection( + identifiersControllers: identifiersControllers, + onUpdateIdentifiersControllers: (c) => setState(() { + identifiersControllers = c; + validValues = checkValidValues( + nameController: nameController, + identifiersControllers: identifiersControllers + ); + }), + onCheckValidValues: () => setState(() { + validValues = checkValidValues( + identifiersControllers: identifiersControllers, + nameController: nameController + ); + }), + ), + SectionLabel( + label: AppLocalizations.of(context)!.settings, + padding: const EdgeInsets.only( + left: 24, right: 24, top: 12, bottom: 24 + ) + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Material( + color: Theme.of(context).colorScheme.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(28), + child: InkWell( + onTap: () => enableDisableGlobalSettingsFiltering(), + borderRadius: BorderRadius.circular(28), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 5 + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + AppLocalizations.of(context)!.useGlobalSettings, + style: TextStyle( + fontSize: 16, + color: Theme.of(context).colorScheme.onSurface + ), + ), + Switch( + value: useGlobalSettingsFiltering, + onChanged: (value) => enableDisableGlobalSettingsFiltering() + ) + ], + ), + ), + ), + ), + ), + const SizedBox(height: 10), + SettingsTile( + label: AppLocalizations.of(context)!.enableFiltering, + value: enableFiltering, + onChange: (value) => setState(() => enableFiltering = value), + useGlobalSettingsFiltering: useGlobalSettingsFiltering, + ), + SettingsTile( + label: AppLocalizations.of(context)!.enableSafeBrowsing, + value: enableSafeBrowsing, + onChange: (value) => setState(() => enableSafeBrowsing = value), + useGlobalSettingsFiltering: useGlobalSettingsFiltering, + ), + SettingsTile( + label: AppLocalizations.of(context)!.enableParentalControl, + value: enableParentalControl, + onChange: (value) => setState(() => enableParentalControl = value), + useGlobalSettingsFiltering: useGlobalSettingsFiltering, + ), + if ( + serverVersionIsAhead( + currentVersion: statusProvider.serverStatus!.serverVersion, + referenceVersion: 'v0.107.28', + referenceVersionBeta: 'v0.108.0-b.33' + ) == true + ) CustomListTile( + title: AppLocalizations.of(context)!.safeSearch, + padding: const EdgeInsets.symmetric( + horizontal: 42, + vertical: 16 + ), + trailing: Padding( + padding: const EdgeInsets.only(right: 16), + child: Icon( + Icons.chevron_right_rounded, + color: useGlobalSettingsFiltering == true + ? Colors.grey + : Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + onTap: useGlobalSettingsFiltering == false + ? () => openSafeSearchModal( + context: context, + blockedServices: blockedServices, + defaultSafeSearch: defaultSafeSearch, + safeSearch: safeSearch, + onUpdateSafeSearch: (s) => setState(() => safeSearch = s) + ) + : null, + ), + if ( + serverVersionIsAhead( + currentVersion: statusProvider.serverStatus!.serverVersion, + referenceVersion: 'v0.107.28', + referenceVersionBeta: 'v0.108.0-b.33' + ) == false + ) SettingsTile( + label: AppLocalizations.of(context)!.enableSafeSearch, + value: enableSafeSearch, + onChange: (value) => setState(() => enableSafeSearch = value), + useGlobalSettingsFiltering: useGlobalSettingsFiltering, + ), + SectionLabel( + label: AppLocalizations.of(context)!.blockedServices, + padding: const EdgeInsets.all(24), + ), + BlockedServicesSection( + useGlobalSettingsServices: useGlobalSettingsServices, + blockedServices: blockedServices, + onUpdatedBlockedServices: (s) => setState(() => blockedServices = s), + onUpdateServicesGlobalSettings: (v) => setState(() => useGlobalSettingsServices = v), + ), + UpstreamServersSection( + upstreamServers: upstreamServers, + onCheckValidValues: () => setState(() { + validValues = checkValidValues( + identifiersControllers: identifiersControllers, + nameController: nameController + ); + }), + onUpdateUpstreamServers: (v) => setState(() => upstreamServers = v) + ), + const SizedBox(height: 20) + ], + ); + } + + + if (widget.dialog == true) { + return Dialog( + child: ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 500 + ), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + CloseButton(onPressed: () => Navigator.pop(context)), + const SizedBox(width: 8), + Text( + widget.client != null + ? AppLocalizations.of(context)!.client + : AppLocalizations.of(context)!.addClient, + style: const TextStyle( + fontSize: 22 + ), + ), + ], + ), + Row( + children: actions() + ) + ], + ), + ), + Flexible( + child: content(false) + ) + ], + ), + ), + ); + } + else { + return Scaffold( + appBar: AppBar( + leading: IconButton( + onPressed: () => Navigator.pop(context), + icon: const Icon(Icons.close) + ), + title: Text( + widget.client != null + ? AppLocalizations.of(context)!.client + : AppLocalizations.of(context)!.addClient + ), + actions: actions(), + ), + body: content(true) + ); + } + } +} \ No newline at end of file diff --git a/lib/screens/clients/client/client_screen_functions.dart b/lib/screens/clients/client/client_screen_functions.dart new file mode 100644 index 0000000..1bbfd24 --- /dev/null +++ b/lib/screens/clients/client/client_screen_functions.dart @@ -0,0 +1,87 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +import 'package:adguard_home_manager/screens/clients/client/remove_client_modal.dart'; +import 'package:adguard_home_manager/screens/clients/client/safe_search_modal.dart'; +import 'package:adguard_home_manager/screens/clients/client/services_modal.dart'; +import 'package:adguard_home_manager/screens/clients/client/tags_modal.dart'; + +import 'package:adguard_home_manager/providers/clients_provider.dart'; +import 'package:adguard_home_manager/models/safe_search.dart'; + +void openTagsModal({ + required BuildContext context, + required List selectedTags, + required void Function(List) onSelectedTags +}) { + showDialog( + context: context, + builder: (context) => TagsModal( + selectedTags: selectedTags, + tags: Provider.of(context, listen: false).clients!.supportedTags, + onConfirm: onSelectedTags, + ) + ); +} + +void openServicesModal({ + required BuildContext context, + required List blockedServices, + required void Function(List) onUpdateBlockedServices +}) { + showDialog( + context: context, + builder: (context) => ServicesModal( + blockedServices: blockedServices, + onConfirm: onUpdateBlockedServices, + ) + ); +} + +void openDeleteClientScreen({ + required BuildContext context, + required void Function() onDelete +}) { + showDialog( + context: context, + builder: (ctx) => RemoveClientModal( + onConfirm: () { + Navigator.pop(context); + onDelete(); + } + ) + ); +} + +void openSafeSearchModal({ + required BuildContext context, + required List blockedServices, + required void Function(SafeSearch) onUpdateSafeSearch, + required SafeSearch? safeSearch, + required SafeSearch defaultSafeSearch +}) { + showDialog( + context: context, + builder: (context) => SafeSearchModal( + safeSearch: safeSearch ?? defaultSafeSearch, + disabled: false, + onConfirm: onUpdateSafeSearch + ) + ); +} + +bool checkValidValues({ + required TextEditingController nameController, + required List> identifiersControllers +}) { + if ( + nameController.text != '' && + identifiersControllers.isNotEmpty && + identifiersControllers[0]['controller']!.text != '' + ) { + return true; + } + else { + return false; + } +} diff --git a/lib/screens/clients/client/identifiers_section.dart b/lib/screens/clients/client/identifiers_section.dart new file mode 100644 index 0000000..08b044b --- /dev/null +++ b/lib/screens/clients/client/identifiers_section.dart @@ -0,0 +1,103 @@ +import 'package:adguard_home_manager/screens/clients/client/client_screen_functions.dart'; +import 'package:flutter/material.dart'; +import 'package:uuid/uuid.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +import 'package:adguard_home_manager/widgets/section_label.dart'; + +class IdentifiersSection extends StatefulWidget { + final List> identifiersControllers; + final void Function(List>) onUpdateIdentifiersControllers; + final void Function() onCheckValidValues; + + const IdentifiersSection({ + Key? key, + required this.identifiersControllers, + required this.onUpdateIdentifiersControllers, + required this.onCheckValidValues + }) : super(key: key); + + @override + State createState() => _IdentifiersSectionState(); +} + +class _IdentifiersSectionState extends State { + final Uuid uuid = const Uuid(); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SectionLabel( + label: AppLocalizations.of(context)!.identifiers, + padding: const EdgeInsets.only( + left: 24, right: 24, top: 24, bottom: 12 + ) + ), + Padding( + padding: const EdgeInsets.only(right: 20), + child: IconButton( + onPressed: () => widget.onUpdateIdentifiersControllers([ + ...widget.identifiersControllers, + Map.from({ + 'id': uuid.v4(), + 'controller': TextEditingController() + }) + ]), + icon: const Icon(Icons.add) + ), + ) + ], + ), + if (widget.identifiersControllers.isNotEmpty) ...widget.identifiersControllers.map((controller) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: TextFormField( + controller: controller['controller'], + onChanged: (_) => widget.onCheckValidValues(), + decoration: InputDecoration( + prefixIcon: const Icon(Icons.tag), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + helperText: AppLocalizations.of(context)!.identifierHelper, + labelText: AppLocalizations.of(context)!.identifier, + ), + ), + ), + const SizedBox(width: 20), + Padding( + padding: const EdgeInsets.only(bottom: 25), + child: IconButton( + onPressed: () => widget.onUpdateIdentifiersControllers( + widget.identifiersControllers.where((e) => e['id'] != controller['id']).toList() + ), + icon: const Icon(Icons.remove_circle_outline_outlined) + ), + ) + ], + ), + )).toList(), + if (widget.identifiersControllers.isEmpty) Container( + padding: const EdgeInsets.only(top: 10), + child: Text( + AppLocalizations.of(context)!.noIdentifiers, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 18, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + ), + ], + ); + } +} \ No newline at end of file diff --git a/lib/screens/clients/logs_list_client.dart b/lib/screens/clients/client/logs_list_client.dart similarity index 100% rename from lib/screens/clients/logs_list_client.dart rename to lib/screens/clients/client/logs_list_client.dart diff --git a/lib/screens/clients/remove_client_modal.dart b/lib/screens/clients/client/remove_client_modal.dart similarity index 100% rename from lib/screens/clients/remove_client_modal.dart rename to lib/screens/clients/client/remove_client_modal.dart diff --git a/lib/screens/clients/safe_search_modal.dart b/lib/screens/clients/client/safe_search_modal.dart similarity index 100% rename from lib/screens/clients/safe_search_modal.dart rename to lib/screens/clients/client/safe_search_modal.dart diff --git a/lib/screens/clients/services_modal.dart b/lib/screens/clients/client/services_modal.dart similarity index 100% rename from lib/screens/clients/services_modal.dart rename to lib/screens/clients/client/services_modal.dart diff --git a/lib/screens/clients/client/settings_tile.dart b/lib/screens/clients/client/settings_tile.dart new file mode 100644 index 0000000..81f0c5c --- /dev/null +++ b/lib/screens/clients/client/settings_tile.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; + +class SettingsTile extends StatelessWidget { + final String label; + final bool? value; + final void Function(bool)? onChange; + final bool useGlobalSettingsFiltering; + + const SettingsTile({ + Key? key, + required this.label, + required this.value, + this.onChange, + required this.useGlobalSettingsFiltering + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Material( + color: Colors.transparent, + child: InkWell( + onTap: onChange != null + ? value != null ? () => onChange!(!value!) : null + : null, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 42, + vertical: 5 + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + label, + style: TextStyle( + fontSize: 16, + color: Theme.of(context).colorScheme.onSurface, + ), + ), + useGlobalSettingsFiltering == false + ? Switch( + value: value!, + onChanged: onChange, + ) + : Padding( + padding: const EdgeInsets.symmetric( + vertical: 14, + horizontal: 12 + ), + child: Text( + "Global", + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface, + ), + ), + ) + ], + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/screens/clients/tags_modal.dart b/lib/screens/clients/client/tags_modal.dart similarity index 100% rename from lib/screens/clients/tags_modal.dart rename to lib/screens/clients/client/tags_modal.dart diff --git a/lib/screens/clients/client/tags_section.dart b/lib/screens/clients/client/tags_section.dart new file mode 100644 index 0000000..a6fa67c --- /dev/null +++ b/lib/screens/clients/client/tags_section.dart @@ -0,0 +1,63 @@ +import 'package:adguard_home_manager/screens/clients/client/client_screen_functions.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +class TagsSection extends StatelessWidget { + final List selectedTags; + final void Function(List) onTagsSelected; + + const TagsSection({ + Key? key, + required this.selectedTags, + required this.onTagsSelected + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Material( + color: Colors.transparent, + child: InkWell( + onTap: () => openTagsModal( + context: context, + selectedTags: selectedTags, + onSelectedTags: onTagsSelected + ) , + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 0, horizontal: 24 + ), + child: Row( + children: [ + Icon( + Icons.label_rounded, + color: Theme.of(context).listTileTheme.iconColor + ), + const SizedBox(width: 16), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + AppLocalizations.of(context)!.selectTags, + style: TextStyle( + fontSize: 16, + color: Theme.of(context).colorScheme.onSurface + ), + ), + const SizedBox(height: 3), + Text( + selectedTags.isNotEmpty + ? "${selectedTags.length} ${AppLocalizations.of(context)!.tagsSelected}" + : AppLocalizations.of(context)!.noTagsSelected, + style: TextStyle( + color: Theme.of(context).listTileTheme.iconColor + ), + ) + ], + ) + ], + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/screens/clients/client/upstream_servers_section.dart b/lib/screens/clients/client/upstream_servers_section.dart new file mode 100644 index 0000000..47dd41d --- /dev/null +++ b/lib/screens/clients/client/upstream_servers_section.dart @@ -0,0 +1,111 @@ +import 'package:flutter/material.dart'; +import 'package:uuid/uuid.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +import 'package:adguard_home_manager/widgets/section_label.dart'; + +class UpstreamServersSection extends StatefulWidget { + final List> upstreamServers; + final void Function() onCheckValidValues; + final void Function(List>) onUpdateUpstreamServers; + + const UpstreamServersSection({ + Key? key, + required this.upstreamServers, + required this.onCheckValidValues, + required this.onUpdateUpstreamServers + }) : super(key: key); + + @override + State createState() => _UpstreamServersSectionState(); +} + +class _UpstreamServersSectionState extends State { + final Uuid uuid = const Uuid(); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SectionLabel( + label: AppLocalizations.of(context)!.upstreamServers, + padding: const EdgeInsets.all(24), + ), + Padding( + padding: const EdgeInsets.only(right: 20), + child: IconButton( + onPressed: () => setState(() => widget.upstreamServers.add( + Map.from({ + 'id': uuid.v4(), + 'controller': TextEditingController() + }) + )), + icon: const Icon(Icons.add) + ), + ) + ], + ), + if (widget.upstreamServers.isNotEmpty) ...widget.upstreamServers.map((controller) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Padding( + padding: const EdgeInsets.only(bottom: 20), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: TextFormField( + controller: controller['controller'], + onChanged: (_) => widget.onCheckValidValues, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.dns_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + labelText: AppLocalizations.of(context)!.serverAddress, + ), + ), + ), + const SizedBox(width: 20), + IconButton( + onPressed: () => widget.onUpdateUpstreamServers( + widget.upstreamServers.where((e) => e['id'] != controller['id']).toList() + ), + icon: const Icon(Icons.remove_circle_outline_outlined) + ) + ], + ), + ), + )).toList(), + if (widget.upstreamServers.isEmpty) Container( + padding: const EdgeInsets.only(top: 12), + child: Column( + children: [ + Text( + AppLocalizations.of(context)!.noUpstreamServers, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 18, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + const SizedBox(height: 10), + Text( + AppLocalizations.of(context)!.willBeUsedGeneralServers, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 15, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + ], + ), + ), + ], + ); + } +} \ No newline at end of file diff --git a/lib/screens/clients/client_screen.dart b/lib/screens/clients/client_screen.dart deleted file mode 100644 index c40a6d4..0000000 --- a/lib/screens/clients/client_screen.dart +++ /dev/null @@ -1,838 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:uuid/uuid.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; - -import 'package:adguard_home_manager/screens/clients/remove_client_modal.dart'; -import 'package:adguard_home_manager/screens/clients/safe_search_modal.dart'; -import 'package:adguard_home_manager/screens/clients/services_modal.dart'; -import 'package:adguard_home_manager/screens/clients/tags_modal.dart'; -import 'package:adguard_home_manager/widgets/custom_list_tile.dart'; - -import 'package:adguard_home_manager/functions/compare_versions.dart'; -import 'package:adguard_home_manager/models/safe_search.dart'; -import 'package:adguard_home_manager/providers/clients_provider.dart'; -import 'package:adguard_home_manager/providers/status_provider.dart'; -import 'package:adguard_home_manager/models/clients.dart'; - -class ClientScreen extends StatefulWidget { - final Client? client; - final String serverVersion; - final void Function(Client) onConfirm; - final void Function(Client)? onDelete; - final bool dialog; - - const ClientScreen({ - Key? key, - this.client, - required this.serverVersion, - required this.onConfirm, - this.onDelete, - required this.dialog - }) : super(key: key); - - @override - State createState() => _ClientScreenState(); -} - -class _ClientScreenState extends State { - final Uuid uuid = const Uuid(); - bool editMode = true; - - bool validValues = false; - - TextEditingController nameController = TextEditingController(); - - List selectedTags = []; - - List> identifiersControllers = [ - { - 'id': 0, - 'controller': TextEditingController() - } - ]; - - bool useGlobalSettingsFiltering = true; - bool? enableFiltering; - bool? enableSafeBrowsing; - bool? enableParentalControl; - bool? enableSafeSearch; - SafeSearch? safeSearch; - - final SafeSearch defaultSafeSearch = SafeSearch( - enabled: false, - bing: false, - duckduckgo: false, - google: false, - pixabay: false, - yandex: false, - youtube: false - ); - - bool useGlobalSettingsServices = true; - List blockedServices = []; - - List> upstreamServers = []; - - - void checkValidValues() { - if ( - nameController.text != '' && - identifiersControllers.isNotEmpty && - identifiersControllers[0]['controller'].text != '' - ) { - setState(() => validValues = true); - } - else { - setState(() => validValues = false); - } - } - - bool version = false; - - @override - void initState() { - version = serverVersionIsAhead( - currentVersion: widget.serverVersion, - referenceVersion: 'v0.107.28', - referenceVersionBeta: 'v0.108.0-b.33' - ); - - if (widget.client != null) { - editMode = false; - - validValues = true; - - nameController.text = widget.client!.name; - selectedTags = widget.client!.tags; - identifiersControllers = widget.client!.ids.map((e) => { - 'id': uuid.v4(), - 'controller': TextEditingController(text: e) - }).toList(); - useGlobalSettingsFiltering = widget.client!.useGlobalSettings; - enableFiltering = widget.client!.filteringEnabled; - enableParentalControl = widget.client!.parentalEnabled; - enableSafeBrowsing = widget.client!.safebrowsingEnabled; - if (version == true) { - safeSearch = widget.client!.safeSearch; - } - else { - enableSafeSearch = widget.client!.safesearchEnabled ?? false; - } - useGlobalSettingsServices = widget.client!.useGlobalBlockedServices; - blockedServices = widget.client!.blockedServices; - upstreamServers = widget.client!.upstreams.map((e) => { - 'id': uuid.v4(), - 'controller': TextEditingController(text: e) - }).toList(); - } - super.initState(); - } - - @override - Widget build(BuildContext context) { - final clientsProvider = Provider.of(context); - final statusProvider = Provider.of(context); - - void createClient() { - final Client client = Client( - name: nameController.text, - ids: List.from(identifiersControllers.map((e) => e['controller'].text)), - useGlobalSettings: useGlobalSettingsFiltering, - filteringEnabled: enableFiltering ?? false, - parentalEnabled: enableParentalControl ?? false, - safebrowsingEnabled: enableSafeBrowsing ?? false, - safesearchEnabled: version == false ? enableSafeSearch : null, - safeSearch: version == true ? safeSearch : null, - useGlobalBlockedServices: useGlobalSettingsServices, - blockedServices: blockedServices, - upstreams: List.from(upstreamServers.map((e) => e['controller'].text)), - tags: selectedTags - ); - widget.onConfirm(client); - } - - Widget sectionLabel({ - required String label, - EdgeInsets? padding - }) { - return Padding( - padding: padding ?? const EdgeInsets.symmetric( - vertical: 24, - horizontal: 24 - ), - child: Text( - label, - style: TextStyle( - fontWeight: FontWeight.w500, - fontSize: 16, - color: Theme.of(context).colorScheme.primary - ), - ), - ); - } - - void enableDisableGlobalSettingsFiltering() { - if (useGlobalSettingsFiltering == true) { - setState(() { - useGlobalSettingsFiltering = false; - - enableFiltering = false; - enableSafeBrowsing = false; - enableParentalControl = false; - enableSafeSearch = false; - safeSearch = defaultSafeSearch; - }); - } - else if (useGlobalSettingsFiltering == false) { - setState(() { - useGlobalSettingsFiltering = true; - - enableFiltering = null; - enableSafeBrowsing = null; - enableParentalControl = null; - enableSafeSearch = null; - safeSearch = null; - }); - } - } - - void openTagsModal() { - showDialog( - context: context, - builder: (context) => TagsModal( - selectedTags: selectedTags, - tags: clientsProvider.clients!.supportedTags, - onConfirm: (selected) => setState(() => selectedTags = selected), - ) - ); - } - - void openServicesModal() { - showDialog( - context: context, - builder: (context) => ServicesModal( - blockedServices: blockedServices, - onConfirm: (values) => setState(() => blockedServices = values), - ) - ); - } - - void updateServicesGlobalSettings(bool value) { - if (value == true) { - setState(() { - blockedServices = []; - useGlobalSettingsServices = true; - }); - } - else if (value == false) { - setState(() { - useGlobalSettingsServices = false; - }); - } - } - - void openDeleteClientScreen() { - showDialog( - context: context, - builder: (ctx) => RemoveClientModal( - onConfirm: () { - Navigator.pop(context); - widget.onDelete!(widget.client!); - } - ) - ); - } - - void openSafeSearchModal() { - showDialog( - context: context, - builder: (context) => SafeSearchModal( - safeSearch: safeSearch ?? defaultSafeSearch, - disabled: !editMode, - onConfirm: (s) => setState(() => safeSearch = s) - ) - ); - } - - Widget settignsTile({ - required String label, - required bool? value, - void Function(bool)? onChange - }) { - return Material( - color: Colors.transparent, - child: InkWell( - onTap: onChange != null - ? value != null ? () => onChange(!value) : null - : null, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 42, - vertical: 5 - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - label, - style: TextStyle( - fontSize: 16, - color: Theme.of(context).colorScheme.onSurface, - ), - ), - useGlobalSettingsFiltering == false - ? Switch( - value: value!, - onChanged: onChange, - ) - : Padding( - padding: const EdgeInsets.symmetric( - vertical: 14, - horizontal: 12 - ), - child: Text( - "Global", - style: TextStyle( - color: Theme.of(context).colorScheme.onSurface, - ), - ), - ) - ], - ), - ), - ), - ); - } - - Widget content(bool withPaddingTop) { - return ListView( - padding: const EdgeInsets.only(top: 0), - children: [ - if (withPaddingTop == true) const SizedBox(height: 24), - if (withPaddingTop == false) const SizedBox(height: 6), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: TextFormField( - enabled: widget.client != null ? false : true, - controller: nameController, - onChanged: (_) => checkValidValues(), - decoration: InputDecoration( - prefixIcon: const Icon(Icons.badge_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - labelText: AppLocalizations.of(context)!.name, - ), - ), - ), - sectionLabel(label: AppLocalizations.of(context)!.tags), - Material( - color: Colors.transparent, - child: InkWell( - onTap: editMode == true ? () => openTagsModal() : null, - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 0, horizontal: 24 - ), - child: Row( - children: [ - Icon( - Icons.label_rounded, - color: Theme.of(context).listTileTheme.iconColor - ), - const SizedBox(width: 16), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - AppLocalizations.of(context)!.selectTags, - style: TextStyle( - fontSize: 16, - color: Theme.of(context).colorScheme.onSurface - ), - ), - const SizedBox(height: 3), - Text( - selectedTags.isNotEmpty - ? "${selectedTags.length} ${AppLocalizations.of(context)!.tagsSelected}" - : AppLocalizations.of(context)!.noTagsSelected, - style: TextStyle( - color: Theme.of(context).listTileTheme.iconColor - ), - ) - ], - ) - ], - ), - ), - ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - sectionLabel( - label: AppLocalizations.of(context)!.identifiers, - padding: const EdgeInsets.only( - left: 24, right: 24, top: 24, bottom: 12 - ) - ), - if (editMode == true) Padding( - padding: const EdgeInsets.only(right: 20), - child: IconButton( - onPressed: () => setState(() => identifiersControllers.add( - Map.from({ - 'id': uuid.v4(), - 'controller': TextEditingController() - }) - )), - icon: const Icon(Icons.add) - ), - ) - ], - ), - if (identifiersControllers.isNotEmpty) ...identifiersControllers.map((controller) => Padding( - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Expanded( - child: TextFormField( - enabled: editMode, - controller: controller['controller'], - onChanged: (_) => checkValidValues(), - decoration: InputDecoration( - prefixIcon: const Icon(Icons.tag), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - helperText: AppLocalizations.of(context)!.identifierHelper, - labelText: AppLocalizations.of(context)!.identifier, - ), - ), - ), - if (editMode == true) ...[ - const SizedBox(width: 20), - Padding( - padding: const EdgeInsets.only(bottom: 25), - child: IconButton( - onPressed: () => setState( - () => identifiersControllers = identifiersControllers.where((e) => e['id'] != controller['id']).toList() - ), - icon: const Icon(Icons.remove_circle_outline_outlined) - ), - ) - ] - ], - ), - )).toList(), - if (identifiersControllers.isEmpty) Container( - padding: const EdgeInsets.only(top: 10), - child: Text( - AppLocalizations.of(context)!.noIdentifiers, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 18, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - ), - sectionLabel( - label: AppLocalizations.of(context)!.settings, - padding: const EdgeInsets.only( - left: 24, right: 24, top: 12, bottom: 24 - ) - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: Material( - color: Theme.of(context).colorScheme.primary.withOpacity(0.1), - borderRadius: BorderRadius.circular(28), - child: InkWell( - onTap: editMode - ? () => enableDisableGlobalSettingsFiltering() - : null, - borderRadius: BorderRadius.circular(28), - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 5 - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - AppLocalizations.of(context)!.useGlobalSettings, - style: TextStyle( - fontSize: 16, - color: Theme.of(context).colorScheme.onSurface - ), - ), - Switch( - value: useGlobalSettingsFiltering, - onChanged: editMode == true - ? (value) => enableDisableGlobalSettingsFiltering() - : null, - ) - ], - ), - ), - ), - ), - ), - const SizedBox(height: 10), - settignsTile( - label: AppLocalizations.of(context)!.enableFiltering, - value: enableFiltering, - onChange: editMode == true - ? (value) => setState(() => enableFiltering = value) - : null - ), - settignsTile( - label: AppLocalizations.of(context)!.enableSafeBrowsing, - value: enableSafeBrowsing, - onChange: editMode == true - ? (value) => setState(() => enableSafeBrowsing = value) - : null - ), - settignsTile( - label: AppLocalizations.of(context)!.enableParentalControl, - value: enableParentalControl, - onChange: editMode == true - ? (value) => setState(() => enableParentalControl = value) - : null - ), - if ( - serverVersionIsAhead( - currentVersion: statusProvider.serverStatus!.serverVersion, - referenceVersion: 'v0.107.28', - referenceVersionBeta: 'v0.108.0-b.33' - ) == true - ) CustomListTile( - title: AppLocalizations.of(context)!.safeSearch, - padding: const EdgeInsets.symmetric( - horizontal: 42, - vertical: 16 - ), - trailing: Padding( - padding: const EdgeInsets.only(right: 16), - child: Icon( - Icons.chevron_right_rounded, - color: useGlobalSettingsFiltering == true - ? Colors.grey - : Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - onTap: useGlobalSettingsFiltering == false - ? () => openSafeSearchModal() - : null, - ), - if ( - serverVersionIsAhead( - currentVersion: statusProvider.serverStatus!.serverVersion, - referenceVersion: 'v0.107.28', - referenceVersionBeta: 'v0.108.0-b.33' - ) == false - ) settignsTile( - label: AppLocalizations.of(context)!.enableSafeSearch, - value: enableSafeSearch, - onChange: editMode == true - ? (value) => setState(() => enableSafeSearch = value) - : null - ), - sectionLabel(label: AppLocalizations.of(context)!.blockedServices), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: Material( - color: Theme.of(context).colorScheme.primary.withOpacity(0.1), - borderRadius: BorderRadius.circular(28), - child: InkWell( - onTap: editMode == true - ? () => updateServicesGlobalSettings(!useGlobalSettingsServices) - : null, - borderRadius: BorderRadius.circular(28), - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 5 - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - AppLocalizations.of(context)!.useGlobalSettings, - style: TextStyle( - fontSize: 16, - color: Theme.of(context).colorScheme.onSurface - ), - ), - Switch( - value: useGlobalSettingsServices, - onChanged: editMode == true - ? (value) => updateServicesGlobalSettings(value) - : null, - ) - ], - ), - ), - ), - ), - ), - const SizedBox(height: 10), - Material( - color: Colors.transparent, - child: InkWell( - onTap: editMode == true - ? useGlobalSettingsServices == false - ? openServicesModal - : null - : null, - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 8, horizontal: 24 - ), - child: Row( - children: [ - Icon( - Icons.public, - color: useGlobalSettingsServices == false - ? Theme.of(context).listTileTheme.iconColor - : Theme.of(context).colorScheme.onSurface.withOpacity(0.38), - ), - const SizedBox(width: 16), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - AppLocalizations.of(context)!.selectBlockedServices, - style: TextStyle( - fontSize: 16, - color: useGlobalSettingsServices == false - ? Theme.of(context).colorScheme.onSurface - : Theme.of(context).colorScheme.onSurface.withOpacity(0.38), - ), - ), - if (useGlobalSettingsServices == false) ...[ - const SizedBox(height: 5), - Text( - blockedServices.isNotEmpty - ? "${blockedServices.length} ${AppLocalizations.of(context)!.servicesBlocked}" - : AppLocalizations.of(context)!.noBlockedServicesSelected, - style: TextStyle( - color: Theme.of(context).listTileTheme.iconColor - ), - ) - ] - ], - ) - ], - ), - ), - ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - sectionLabel(label: AppLocalizations.of(context)!.upstreamServers), - if (editMode == true) Padding( - padding: const EdgeInsets.only(right: 20), - child: IconButton( - onPressed: () => setState(() => upstreamServers.add( - Map.from({ - 'id': uuid.v4(), - 'controller': TextEditingController() - }) - )), - icon: const Icon(Icons.add) - ), - ) - ], - ), - if (upstreamServers.isNotEmpty) ...upstreamServers.map((controller) => Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Padding( - padding: const EdgeInsets.only(bottom: 20), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Expanded( - child: TextFormField( - enabled: editMode, - controller: controller['controller'], - onChanged: (_) => checkValidValues(), - decoration: InputDecoration( - prefixIcon: const Icon(Icons.dns_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - labelText: AppLocalizations.of(context)!.serverAddress, - ), - ), - ), - if (editMode == true) ...[ - const SizedBox(width: 20), - IconButton( - onPressed: () => setState( - () => upstreamServers = upstreamServers.where((e) => e['id'] != controller['id']).toList() - ), - icon: const Icon(Icons.remove_circle_outline_outlined) - ) - ] - ], - ), - ), - )).toList(), - if (upstreamServers.isEmpty) Container( - padding: const EdgeInsets.only(top: 12), - child: Column( - children: [ - Text( - AppLocalizations.of(context)!.noUpstreamServers, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 18, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - const SizedBox(height: 10), - Text( - AppLocalizations.of(context)!.willBeUsedGeneralServers, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 15, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - ], - ), - ), - const SizedBox(height: 20) - ], - ); - } - - if (widget.dialog == true) { - return Dialog( - child: ConstrainedBox( - constraints: const BoxConstraints( - maxWidth: 500 - ), - child: Column( - children: [ - Padding( - padding: const EdgeInsets.all(16), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - IconButton( - onPressed: () => Navigator.pop(context), - icon: const Icon(Icons.clear_rounded) - ), - const SizedBox(width: 8), - Text( - widget.client != null - ? AppLocalizations.of(context)!.client - : AppLocalizations.of(context)!.addClient, - style: const TextStyle( - fontSize: 22 - ), - ), - ], - ), - Row( - children: [ - if (widget.client == null || (widget.client != null && editMode == true)) IconButton( - onPressed: validValues == true - ? () { - createClient(); - Navigator.pop(context); - } - : null, - icon: Icon( - widget.client != null && editMode == true - ? Icons.save_rounded - : Icons.check_rounded - ), - tooltip: widget.client != null && editMode == true - ? AppLocalizations.of(context)!.save - : AppLocalizations.of(context)!.confirm, - ), - if (widget.client != null && editMode == false) IconButton( - onPressed: () => setState(() => editMode = true), - icon: const Icon(Icons.edit_rounded), - tooltip: AppLocalizations.of(context)!.edit, - ), - if (widget.client != null) IconButton( - onPressed: openDeleteClientScreen, - icon: const Icon(Icons.delete_rounded), - tooltip: AppLocalizations.of(context)!.delete, - ), - const SizedBox(width: 10), - ], - ) - ], - ), - ), - Flexible( - child: content(false) - ) - ], - ), - ), - ); - } - else { - return Scaffold( - appBar: AppBar( - leading: IconButton( - onPressed: () => Navigator.pop(context), - icon: const Icon(Icons.close) - ), - title: Text( - widget.client != null - ? AppLocalizations.of(context)!.client - : AppLocalizations.of(context)!.addClient - ), - actions: [ - if (widget.client == null || (widget.client != null && editMode == true)) IconButton( - onPressed: validValues == true - ? () { - createClient(); - Navigator.pop(context); - } - : null, - icon: Icon( - widget.client != null && editMode == true - ? Icons.save_rounded - : Icons.check_rounded - ), - tooltip: widget.client != null && editMode == true - ? AppLocalizations.of(context)!.save - : AppLocalizations.of(context)!.confirm, - ), - if (widget.client != null && editMode == false) IconButton( - onPressed: () => setState(() => editMode = true), - icon: const Icon(Icons.edit_rounded), - tooltip: AppLocalizations.of(context)!.edit, - ), - if (widget.client != null) IconButton( - onPressed: openDeleteClientScreen, - icon: const Icon(Icons.delete_rounded), - tooltip: AppLocalizations.of(context)!.delete, - ), - const SizedBox(width: 10), - ], - ), - body: content(true) - ); - } - } -} \ No newline at end of file diff --git a/lib/screens/clients/clients.dart b/lib/screens/clients/clients.dart index 1dada10..4762e5b 100644 --- a/lib/screens/clients/clients.dart +++ b/lib/screens/clients/clients.dart @@ -7,7 +7,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/screens/clients/clients_list.dart'; import 'package:adguard_home_manager/screens/clients/search_clients.dart'; -import 'package:adguard_home_manager/screens/clients/logs_list_client.dart'; +import 'package:adguard_home_manager/screens/clients/client/logs_list_client.dart'; import 'package:adguard_home_manager/screens/clients/clients_desktop_view.dart'; import 'package:adguard_home_manager/screens/clients/added_list.dart'; @@ -195,7 +195,8 @@ class _ClientsState extends State with TickerProviderStateMixin { clientsProvider.setSearchTermClients(null); }); }, - icon: const Icon(Icons.arrow_back_rounded) + icon: const Icon(Icons.arrow_back_rounded), + tooltip: AppLocalizations.of(context)!.exitSearch, ), const SizedBox(width: 16), Expanded( @@ -223,6 +224,7 @@ class _ClientsState extends State with TickerProviderStateMixin { fontWeight: FontWeight.normal, fontSize: 18 ), + autofocus: true, ), ) ], diff --git a/lib/screens/clients/clients_desktop_view.dart b/lib/screens/clients/clients_desktop_view.dart index d6707ad..9e261ff 100644 --- a/lib/screens/clients/clients_desktop_view.dart +++ b/lib/screens/clients/clients_desktop_view.dart @@ -5,7 +5,7 @@ import 'package:flutter_split_view/flutter_split_view.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:adguard_home_manager/screens/clients/logs_list_client.dart'; +import 'package:adguard_home_manager/screens/clients/client/logs_list_client.dart'; import 'package:adguard_home_manager/screens/clients/added_list.dart'; import 'package:adguard_home_manager/screens/clients/clients_list.dart'; diff --git a/lib/screens/clients/clients_list.dart b/lib/screens/clients/clients_list.dart index 4ecd98c..f3ee547 100644 --- a/lib/screens/clients/clients_list.dart +++ b/lib/screens/clients/clients_list.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:adguard_home_manager/screens/clients/active_client_tile.dart'; +import 'package:adguard_home_manager/screens/clients/client/active_client_tile.dart'; import 'package:adguard_home_manager/widgets/tab_content_list.dart'; diff --git a/lib/screens/clients/fab.dart b/lib/screens/clients/fab.dart index 8d73050..34dee54 100644 --- a/lib/screens/clients/fab.dart +++ b/lib/screens/clients/fab.dart @@ -6,7 +6,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:adguard_home_manager/screens/clients/client_screen.dart'; +import 'package:adguard_home_manager/screens/clients/client/client_screen.dart'; import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/providers/clients_provider.dart'; diff --git a/lib/screens/clients/search_clients.dart b/lib/screens/clients/search_clients.dart index 4d2c754..a70923f 100644 --- a/lib/screens/clients/search_clients.dart +++ b/lib/screens/clients/search_clients.dart @@ -7,8 +7,8 @@ import 'package:animations/animations.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:adguard_home_manager/screens/clients/remove_client_modal.dart'; -import 'package:adguard_home_manager/screens/clients/client_screen.dart'; +import 'package:adguard_home_manager/screens/clients/client/remove_client_modal.dart'; +import 'package:adguard_home_manager/screens/clients/client/client_screen.dart'; import 'package:adguard_home_manager/screens/clients/options_modal.dart'; import 'package:adguard_home_manager/widgets/section_label.dart'; diff --git a/lib/screens/settings/access_settings/clients_list.dart b/lib/screens/settings/access_settings/clients_list.dart index 2466e11..31c01a7 100644 --- a/lib/screens/settings/access_settings/clients_list.dart +++ b/lib/screens/settings/access_settings/clients_list.dart @@ -8,7 +8,7 @@ import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/screens/settings/access_settings/add_client_modal.dart'; -import 'package:adguard_home_manager/screens/clients/remove_client_modal.dart'; +import 'package:adguard_home_manager/screens/clients/client/remove_client_modal.dart'; import 'package:adguard_home_manager/widgets/tab_content_list.dart'; import 'package:adguard_home_manager/functions/snackbar.dart'; From 5b715d04561788fc41cb5cdb606392b0c5d91ecb Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sat, 7 Oct 2023 23:03:40 +0200 Subject: [PATCH 04/14] Autofocus search top items --- lib/screens/top_items/top_items.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/screens/top_items/top_items.dart b/lib/screens/top_items/top_items.dart index f536b47..26b84d5 100644 --- a/lib/screens/top_items/top_items.dart +++ b/lib/screens/top_items/top_items.dart @@ -85,6 +85,7 @@ class _TopItemsScreenState extends State { fontWeight: FontWeight.normal, fontSize: 18 ), + autofocus: true, ), ) : Text(widget.title), From 2ad99e737d7eec417a46bd109a0c2aaca4bc4b7c Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sat, 7 Oct 2023 23:16:52 +0200 Subject: [PATCH 05/14] Change opening client modal --- lib/screens/clients/added_list.dart | 33 +++---------- lib/screens/clients/client/client_screen.dart | 46 +++++++++---------- .../client/client_screen_functions.dart | 39 ++++++++++++++++ .../clients/client/identifiers_section.dart | 1 - lib/screens/clients/fab.dart | 27 +++-------- lib/screens/clients/search_clients.dart | 35 +++----------- 6 files changed, 82 insertions(+), 99 deletions(-) diff --git a/lib/screens/clients/added_list.dart b/lib/screens/clients/added_list.dart index c49667d..08621ce 100644 --- a/lib/screens/clients/added_list.dart +++ b/lib/screens/clients/added_list.dart @@ -9,7 +9,7 @@ import 'package:flutter_split_view/flutter_split_view.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:adguard_home_manager/screens/clients/client/client_screen.dart'; +import 'package:adguard_home_manager/screens/clients/client/client_screen_functions.dart'; import 'package:adguard_home_manager/screens/clients/client/added_client_tile.dart'; import 'package:adguard_home_manager/screens/clients/client/remove_client_modal.dart'; import 'package:adguard_home_manager/screens/clients/fab.dart'; @@ -128,31 +128,12 @@ class _AddedListState extends State { } void openClientModal(Client client) { - if (width > 900 || !(Platform.isAndroid | Platform.isIOS)) { - showDialog( - barrierDismissible: false, - context: context, - builder: (BuildContext context) => ClientScreen( - onConfirm: confirmEditClient, - serverVersion: statusProvider.serverStatus!.serverVersion, - onDelete: deleteClient, - client: client, - dialog: true, - ) - ); - } - else { - Navigator.push(context, MaterialPageRoute( - fullscreenDialog: true, - builder: (BuildContext context) => ClientScreen( - onConfirm: confirmEditClient, - serverVersion: statusProvider.serverStatus!.serverVersion, - onDelete: deleteClient, - client: client, - dialog: false, - ) - )); - } + openClientFormModal( + context: context, + width: width, + onConfirm: confirmEditClient, + onDelete: deleteClient + ); } void openDeleteModal(Client client) { diff --git a/lib/screens/clients/client/client_screen.dart b/lib/screens/clients/client/client_screen.dart index 5994767..4fcee53 100644 --- a/lib/screens/clients/client/client_screen.dart +++ b/lib/screens/clients/client/client_screen.dart @@ -20,18 +20,16 @@ import 'package:adguard_home_manager/models/clients.dart'; class ClientScreen extends StatefulWidget { final Client? client; - final String serverVersion; final void Function(Client) onConfirm; final void Function(Client)? onDelete; - final bool dialog; + final bool fullScreen; const ClientScreen({ Key? key, this.client, - required this.serverVersion, required this.onConfirm, this.onDelete, - required this.dialog + required this.fullScreen }) : super(key: key); @override @@ -81,7 +79,7 @@ class _ClientScreenState extends State { @override void initState() { version = serverVersionIsAhead( - currentVersion: widget.serverVersion, + currentVersion: Provider.of(context, listen: false).serverStatus!.serverVersion, referenceVersion: 'v0.107.28', referenceVersionBeta: 'v0.108.0-b.33' ); @@ -380,7 +378,26 @@ class _ClientScreenState extends State { } - if (widget.dialog == true) { + if (widget.fullScreen == true) { + return Dialog.fullscreen( + child: Scaffold( + appBar: AppBar( + leading: IconButton( + onPressed: () => Navigator.pop(context), + icon: const Icon(Icons.close) + ), + title: Text( + widget.client != null + ? AppLocalizations.of(context)!.client + : AppLocalizations.of(context)!.addClient + ), + actions: actions(), + ), + body: content(true) + ), + ); + } + else { return Dialog( child: ConstrainedBox( constraints: const BoxConstraints( @@ -421,22 +438,5 @@ class _ClientScreenState extends State { ), ); } - else { - return Scaffold( - appBar: AppBar( - leading: IconButton( - onPressed: () => Navigator.pop(context), - icon: const Icon(Icons.close) - ), - title: Text( - widget.client != null - ? AppLocalizations.of(context)!.client - : AppLocalizations.of(context)!.addClient - ), - actions: actions(), - ), - body: content(true) - ); - } } } \ No newline at end of file diff --git a/lib/screens/clients/client/client_screen_functions.dart b/lib/screens/clients/client/client_screen_functions.dart index 1bbfd24..4b84b2b 100644 --- a/lib/screens/clients/client/client_screen_functions.dart +++ b/lib/screens/clients/client/client_screen_functions.dart @@ -1,11 +1,15 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:adguard_home_manager/screens/clients/client/client_screen.dart'; import 'package:adguard_home_manager/screens/clients/client/remove_client_modal.dart'; import 'package:adguard_home_manager/screens/clients/client/safe_search_modal.dart'; import 'package:adguard_home_manager/screens/clients/client/services_modal.dart'; import 'package:adguard_home_manager/screens/clients/client/tags_modal.dart'; +import 'package:adguard_home_manager/models/clients.dart'; import 'package:adguard_home_manager/providers/clients_provider.dart'; import 'package:adguard_home_manager/models/safe_search.dart'; @@ -85,3 +89,38 @@ bool checkValidValues({ return false; } } + +void openClientFormModal({ + required BuildContext context, + required double width, + Client? client, + required void Function(Client) onConfirm, + void Function(Client)? onDelete, +}) { + showGeneralDialog( + context: context, + barrierColor: !(width > 900 || !(Platform.isAndroid | Platform.isIOS)) + ?Colors.transparent + : Colors.black54, + transitionBuilder: (context, anim1, anim2, child) { + return SlideTransition( + position: Tween( + begin: const Offset(0, 1), + end: const Offset(0, 0) + ).animate( + CurvedAnimation( + parent: anim1, + curve: Curves.easeInOutCubicEmphasized + ) + ), + child: child, + ); + }, + pageBuilder: (context, animation, secondaryAnimation) => ClientScreen( + fullScreen: !(width > 900 || !(Platform.isAndroid | Platform.isIOS)), + client: client, + onConfirm: onConfirm, + onDelete: onDelete, + ), + ); +} \ No newline at end of file diff --git a/lib/screens/clients/client/identifiers_section.dart b/lib/screens/clients/client/identifiers_section.dart index 08b044b..3d9acf2 100644 --- a/lib/screens/clients/client/identifiers_section.dart +++ b/lib/screens/clients/client/identifiers_section.dart @@ -1,4 +1,3 @@ -import 'package:adguard_home_manager/screens/clients/client/client_screen_functions.dart'; import 'package:flutter/material.dart'; import 'package:uuid/uuid.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; diff --git a/lib/screens/clients/fab.dart b/lib/screens/clients/fab.dart index 34dee54..f9697f6 100644 --- a/lib/screens/clients/fab.dart +++ b/lib/screens/clients/fab.dart @@ -8,6 +8,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/screens/clients/client/client_screen.dart'; +import 'package:adguard_home_manager/screens/clients/client/client_screen_functions.dart'; import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/providers/clients_provider.dart'; import 'package:adguard_home_manager/models/clients.dart'; @@ -51,27 +52,11 @@ class ClientsFab extends StatelessWidget { } void openAddClient() { - if (width > 900 || !(Platform.isAndroid | Platform.isIOS)) { - showDialog( - barrierDismissible: false, - context: context, - builder: (BuildContext context) => ClientScreen( - onConfirm: confirmAddClient, - serverVersion: statusProvider.serverStatus!.serverVersion, - dialog: true, - ) - ); - } - else { - Navigator.push(context, MaterialPageRoute( - fullscreenDialog: true, - builder: (BuildContext context) => ClientScreen( - onConfirm: confirmAddClient, - serverVersion: statusProvider.serverStatus!.serverVersion, - dialog: false, - ) - )); - } + openClientFormModal( + context: context, + width: width, + onConfirm: confirmAddClient + ); } return FloatingActionButton( diff --git a/lib/screens/clients/search_clients.dart b/lib/screens/clients/search_clients.dart index a70923f..37544f8 100644 --- a/lib/screens/clients/search_clients.dart +++ b/lib/screens/clients/search_clients.dart @@ -1,14 +1,12 @@ // ignore_for_file: use_build_context_synchronously -import 'dart:io'; - import 'package:flutter/material.dart'; import 'package:animations/animations.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/screens/clients/client/remove_client_modal.dart'; -import 'package:adguard_home_manager/screens/clients/client/client_screen.dart'; +import 'package:adguard_home_manager/screens/clients/client/client_screen_functions.dart'; import 'package:adguard_home_manager/screens/clients/options_modal.dart'; import 'package:adguard_home_manager/widgets/section_label.dart'; @@ -137,31 +135,12 @@ class _SearchClientsState extends State { } void openClientModal(Client client) { - if (width > 900 || !(Platform.isAndroid | Platform.isIOS)) { - showDialog( - barrierDismissible: false, - context: context, - builder: (BuildContext context) => ClientScreen( - onConfirm: confirmEditClient, - serverVersion: statusProvider.serverStatus!.serverVersion, - onDelete: deleteClient, - client: client, - dialog: true, - ) - ); - } - else { - Navigator.push(context, MaterialPageRoute( - fullscreenDialog: true, - builder: (BuildContext context) => ClientScreen( - onConfirm: confirmEditClient, - serverVersion: statusProvider.serverStatus!.serverVersion, - onDelete: deleteClient, - client: client, - dialog: false, - ) - )); - } + openClientFormModal( + context: context, + width: width, + onConfirm: confirmEditClient, + onDelete: deleteClient + ); } void openDeleteModal(Client client) { From 9d1ae22bdd88327eaac36c01712fb9c95714d0b5 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 8 Oct 2023 00:05:38 +0200 Subject: [PATCH 06/14] Changes --- lib/screens/clients/added_list.dart | 4 +++- .../clients/client/added_client_tile.dart | 12 +++++----- lib/screens/clients/fab.dart | 22 ++++++++++--------- lib/screens/clients/options_modal.dart | 6 ++++- lib/screens/clients/search_clients.dart | 4 +++- 5 files changed, 29 insertions(+), 19 deletions(-) diff --git a/lib/screens/clients/added_list.dart b/lib/screens/clients/added_list.dart index 08621ce..8f27491 100644 --- a/lib/screens/clients/added_list.dart +++ b/lib/screens/clients/added_list.dart @@ -185,7 +185,9 @@ class _AddedListState extends State { client: widget.data[index], onTap: widget.onClientSelected, onLongPress: openOptionsModal, - onEdit: openClientModal, + onEdit: statusProvider.serverStatus != null + ? (c) => openClientModal(c) + : null, onDelete: openDeleteModal, splitView: widget.splitView, serverVersion: statusProvider.serverStatus!.serverVersion, diff --git a/lib/screens/clients/client/added_client_tile.dart b/lib/screens/clients/client/added_client_tile.dart index 356d7be..9db4edb 100644 --- a/lib/screens/clients/client/added_client_tile.dart +++ b/lib/screens/clients/client/added_client_tile.dart @@ -14,7 +14,7 @@ class AddedClientTile extends StatelessWidget { final Client client; final void Function(Client) onTap; final void Function(Client) onLongPress; - final void Function(Client) onEdit; + final void Function(Client)? onEdit; final void Function(Client) onDelete; final Client? selectedClient; final bool? splitView; @@ -25,7 +25,7 @@ class AddedClientTile extends StatelessWidget { required this.client, required this.onTap, required this.onLongPress, - required this.onEdit, + this.onEdit, required this.onDelete, this.selectedClient, required this.splitView, @@ -44,12 +44,12 @@ class AddedClientTile extends StatelessWidget { borderRadius: BorderRadius.circular(28), child: ContextMenuArea( builder: (context) => [ - CustomListTile( + if (onEdit != null) CustomListTile( title: AppLocalizations.of(context)!.edit, icon: Icons.edit_rounded, onTap: () { Navigator.pop(context); - onEdit(client); + onEdit!(client); } ), CustomListTile( @@ -185,12 +185,12 @@ class AddedClientTile extends StatelessWidget { else { return ContextMenuArea( builder: (context) => [ - CustomListTile( + if (onEdit != null) CustomListTile( title: AppLocalizations.of(context)!.seeDetails, icon: Icons.file_open_rounded, onTap: () { Navigator.pop(context); - onEdit(client); + onEdit!(client); } ), CustomListTile( diff --git a/lib/screens/clients/fab.dart b/lib/screens/clients/fab.dart index f9697f6..ce291c2 100644 --- a/lib/screens/clients/fab.dart +++ b/lib/screens/clients/fab.dart @@ -1,19 +1,16 @@ // ignore_for_file: use_build_context_synchronously -import 'dart:io'; - import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:adguard_home_manager/screens/clients/client/client_screen.dart'; - import 'package:adguard_home_manager/screens/clients/client/client_screen_functions.dart'; + import 'package:adguard_home_manager/functions/snackbar.dart'; +import 'package:adguard_home_manager/providers/status_provider.dart'; import 'package:adguard_home_manager/providers/clients_provider.dart'; import 'package:adguard_home_manager/models/clients.dart'; import 'package:adguard_home_manager/classes/process_modal.dart'; -import 'package:adguard_home_manager/providers/status_provider.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; class ClientsFab extends StatelessWidget { @@ -21,8 +18,8 @@ class ClientsFab extends StatelessWidget { @override Widget build(BuildContext context) { - final appConfigProvider = Provider.of(context); final statusProvider = Provider.of(context); + final appConfigProvider = Provider.of(context); final clientsProvider = Provider.of(context); final width = MediaQuery.of(context).size.width; @@ -59,9 +56,14 @@ class ClientsFab extends StatelessWidget { ); } - return FloatingActionButton( - onPressed: openAddClient, - child: const Icon(Icons.add), - ); + if (statusProvider.serverStatus != null) { + return FloatingActionButton( + onPressed: openAddClient, + child: const Icon(Icons.add), + ); + } + else { + return const SizedBox(); + } } } \ No newline at end of file diff --git a/lib/screens/clients/options_modal.dart b/lib/screens/clients/options_modal.dart index 61dea10..d779aae 100644 --- a/lib/screens/clients/options_modal.dart +++ b/lib/screens/clients/options_modal.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:adguard_home_manager/providers/status_provider.dart'; import 'package:adguard_home_manager/widgets/custom_list_tile_dialog.dart'; class OptionsModal extends StatelessWidget { @@ -15,6 +17,8 @@ class OptionsModal extends StatelessWidget { @override Widget build(BuildContext context) { + final statusProvider = Provider.of(context); + return AlertDialog( contentPadding: const EdgeInsets.symmetric( horizontal: 0, @@ -39,7 +43,7 @@ class OptionsModal extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ const SizedBox(height: 24), - CustomListTileDialog( + if (statusProvider.serverStatus != null) CustomListTileDialog( onTap: () { Navigator.pop(context); onEdit(); diff --git a/lib/screens/clients/search_clients.dart b/lib/screens/clients/search_clients.dart index 37544f8..99d430c 100644 --- a/lib/screens/clients/search_clients.dart +++ b/lib/screens/clients/search_clients.dart @@ -231,7 +231,9 @@ class _SearchClientsState extends State { : const EdgeInsets.symmetric(horizontal: 20, vertical: 15), isThreeLine: true, onLongPress: () => openOptionsModal(clientsScreen[index]), - onTap: () => openClientModal(clientsScreen[index]), + onTap: statusProvider.serverStatus != null + ? () => openClientModal(clientsScreen[index]) + : null, title: Padding( padding: const EdgeInsets.only(bottom: 5), child: Text( From 7690d5f1bc88233bdf77f1b1667fc58a54111171 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 8 Oct 2023 00:13:00 +0200 Subject: [PATCH 07/14] Refactor blocked services modal --- .../filters/blocked_services_screen.dart | 100 ++++++++++++------ lib/screens/filters/filters.dart | 23 +--- 2 files changed, 70 insertions(+), 53 deletions(-) diff --git a/lib/screens/filters/blocked_services_screen.dart b/lib/screens/filters/blocked_services_screen.dart index 75d941b..cefdda5 100644 --- a/lib/screens/filters/blocked_services_screen.dart +++ b/lib/screens/filters/blocked_services_screen.dart @@ -1,5 +1,7 @@ // ignore_for_file: use_build_context_synchronously +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -12,11 +14,11 @@ import 'package:adguard_home_manager/providers/filtering_provider.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; class BlockedServicesScreen extends StatefulWidget { - final bool dialog; + final bool fullScreen; const BlockedServicesScreen({ Key? key, - required this.dialog + required this.fullScreen }) : super(key: key); @override @@ -180,7 +182,40 @@ class _BlockedServicesScreenStateWidget extends State { } } - if (widget.dialog == true) { + if (widget.fullScreen == true) { + return Dialog.fullscreen( + child: Scaffold( + appBar: AppBar( + leading: CloseButton(onPressed: () => Navigator.pop(context)), + title: Text(AppLocalizations.of(context)!.blockedServices), + actions: [ + IconButton( + onPressed: updateBlockedServices, + icon: const Icon( + Icons.save_rounded + ), + tooltip: AppLocalizations.of(context)!.save, + ), + const SizedBox(width: 10) + ], + ), + body: RefreshIndicator( + onRefresh: () async { + final result = await filteringProvider.loadBlockedServices(); + if (result == false) { + showSnacbkar( + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.blockedServicesListNotLoaded, + color: Colors.red + ); + } + }, + child: body() + ), + ), + ); + } + else { return Dialog( child: ConstrainedBox( constraints: const BoxConstraints( @@ -228,35 +263,34 @@ class _BlockedServicesScreenStateWidget extends State { ), ); } - else { - return Scaffold( - appBar: AppBar( - title: Text(AppLocalizations.of(context)!.blockedServices), - actions: [ - IconButton( - onPressed: updateBlockedServices, - icon: const Icon( - Icons.save_rounded - ), - tooltip: AppLocalizations.of(context)!.save, - ), - const SizedBox(width: 10) - ], - ), - body: RefreshIndicator( - onRefresh: () async { - final result = await filteringProvider.loadBlockedServices(); - if (result == false) { - showSnacbkar( - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.blockedServicesListNotLoaded, - color: Colors.red - ); - } - }, - child: body() - ), - ); - } } +} + +void openBlockedServicesModal({ + required BuildContext context, + required double width, +}) { + showGeneralDialog( + context: context, + barrierColor: !(width > 700 || !(Platform.isAndroid || Platform.isIOS)) + ?Colors.transparent + : Colors.black54, + transitionBuilder: (context, anim1, anim2, child) { + return SlideTransition( + position: Tween( + begin: const Offset(0, 1), + end: const Offset(0, 0) + ).animate( + CurvedAnimation( + parent: anim1, + curve: Curves.easeInOutCubicEmphasized + ) + ), + child: child, + ); + }, + pageBuilder: (context, animation, secondaryAnimation) => BlockedServicesScreen( + fullScreen: !(width > 700 || !(Platform.isAndroid || Platform.isIOS)), + ), + ); } \ No newline at end of file diff --git a/lib/screens/filters/filters.dart b/lib/screens/filters/filters.dart index 00cc214..c4e6d9b 100644 --- a/lib/screens/filters/filters.dart +++ b/lib/screens/filters/filters.dart @@ -149,26 +149,9 @@ class _FiltersState extends State { } } - void openBlockedServicesModal() { + void openBlockedServices() { Future.delayed(const Duration(seconds: 0), () { - if (width > 700 || !(Platform.isAndroid || Platform.isIOS)) { - showDialog( - context: context, - builder: (context) => const BlockedServicesScreen( - dialog: true, - ), - barrierDismissible: false - ); - } - else { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => const BlockedServicesScreen( - dialog: false, - ), - ) - ); - } + openBlockedServicesModal(context: context, width: width); }); } @@ -312,7 +295,7 @@ class _FiltersState extends State { ) ), PopupMenuItem( - onTap: openBlockedServicesModal, + onTap: openBlockedServices, child: Row( children: [ const Icon(Icons.block), From 24dc69d0849a06013981279d1c0e2b708f8e95f7 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 8 Oct 2023 00:14:53 +0200 Subject: [PATCH 08/14] Improvements --- lib/screens/filters/filters_triple_column.dart | 3 ++- lib/widgets/process_dialog.dart | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/screens/filters/filters_triple_column.dart b/lib/screens/filters/filters_triple_column.dart index de0ffb0..2f7979d 100644 --- a/lib/screens/filters/filters_triple_column.dart +++ b/lib/screens/filters/filters_triple_column.dart @@ -276,7 +276,8 @@ class FiltersTripleColumn extends StatelessWidget { subtitleWidget: generateSubtitle(filteringProvider.filtering!.userRules[index]), trailing: IconButton( onPressed: () => onRemoveCustomRule(filteringProvider.filtering!.userRules[index]), - icon: const Icon(Icons.delete) + icon: const Icon(Icons.delete), + tooltip: AppLocalizations.of(context)!.delete, ), ), ), diff --git a/lib/widgets/process_dialog.dart b/lib/widgets/process_dialog.dart index e821696..0c91442 100644 --- a/lib/widgets/process_dialog.dart +++ b/lib/widgets/process_dialog.dart @@ -24,7 +24,7 @@ class ProcessDialog extends StatelessWidget { children: [ const CircularProgressIndicator(), const SizedBox(width: 40), - Expanded( + Flexible( child: Text( message, style: TextStyle( From 95aec4b3a5efe3c004b5db556fd2533bdde1d37d Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 8 Oct 2023 00:19:32 +0200 Subject: [PATCH 09/14] Adapted more modals --- lib/screens/filters/add_button.dart | 43 ++++++++++--------- lib/screens/filters/add_custom_rule.dart | 53 +++++++++++++----------- 2 files changed, 51 insertions(+), 45 deletions(-) diff --git a/lib/screens/filters/add_button.dart b/lib/screens/filters/add_button.dart index 3cbedc9..195d7af 100644 --- a/lib/screens/filters/add_button.dart +++ b/lib/screens/filters/add_button.dart @@ -56,27 +56,30 @@ class AddFiltersButton extends StatelessWidget { } void openAddCustomRule() { - if (width > 700 || !(Platform.isAndroid || Platform.isIOS)) { - showDialog( - context: context, - builder: (context) => AddCustomRule( - onConfirm: confirmAddRule, - dialog: true, - ), - barrierDismissible: false - ); - } - else { - Navigator.of(context).push( - MaterialPageRoute( - fullscreenDialog: true, - builder: (context) => AddCustomRule( - onConfirm: confirmAddRule, - dialog: false, + showGeneralDialog( + context: context, + barrierColor: !(width > 700 || !(Platform.isAndroid || Platform.isIOS)) + ?Colors.transparent + : Colors.black54, + transitionBuilder: (context, anim1, anim2, child) { + return SlideTransition( + position: Tween( + begin: const Offset(0, 1), + end: const Offset(0, 0) + ).animate( + CurvedAnimation( + parent: anim1, + curve: Curves.easeInOutCubicEmphasized + ) ), - ) - ); - } + child: child, + ); + }, + pageBuilder: (context, animation, secondaryAnimation) => AddCustomRule( + fullScreen: !(width > 700 || !(Platform.isAndroid || Platform.isIOS)), + onConfirm: confirmAddRule, + ), + ); } void confirmAddList({required String name, required String url, required String type}) async { diff --git a/lib/screens/filters/add_custom_rule.dart b/lib/screens/filters/add_custom_rule.dart index dfb5753..4b79fc4 100644 --- a/lib/screens/filters/add_custom_rule.dart +++ b/lib/screens/filters/add_custom_rule.dart @@ -6,12 +6,12 @@ import 'package:adguard_home_manager/constants/urls.dart'; class AddCustomRule extends StatefulWidget { final void Function(String) onConfirm; - final bool dialog; + final bool fullScreen; const AddCustomRule({ Key? key, required this.onConfirm, - required this.dialog + required this.fullScreen }) : super(key: key); @override @@ -328,7 +328,32 @@ class _AddCustomRuleState extends State { ]; } - if (widget.dialog == true) { + if (widget.fullScreen == true) { + return Dialog.fullscreen( + child: Scaffold( + appBar: AppBar( + leading: CloseButton(onPressed: () => Navigator.pop(context)), + title: Text(AppLocalizations.of(context)!.addCustomRule), + actions: [ + IconButton( + onPressed: checkValidValues() == true + ? () { + Navigator.pop(context); + widget.onConfirm(buildRule()); + } + : null, + icon: const Icon(Icons.check) + ), + const SizedBox(width: 10) + ], + ), + body: ListView( + children: content(), + ) + ), + ); + } + else { return Dialog( child: ConstrainedBox( constraints: const BoxConstraints( @@ -383,27 +408,5 @@ class _AddCustomRuleState extends State { ), ); } - else { - return Scaffold( - appBar: AppBar( - title: Text(AppLocalizations.of(context)!.addCustomRule), - actions: [ - IconButton( - onPressed: checkValidValues() == true - ? () { - Navigator.pop(context); - widget.onConfirm(buildRule()); - } - : null, - icon: const Icon(Icons.check) - ), - const SizedBox(width: 10) - ], - ), - body: ListView( - children: content(), - ) - ); - } } } \ No newline at end of file From bb5cdc13d1d95bdd867d319ecd98d1efaa99a6d3 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 8 Oct 2023 00:26:48 +0200 Subject: [PATCH 10/14] Updated optionbox styles --- .../filters/update_interval_lists_modal.dart | 86 ++----------------- lib/widgets/option_box.dart | 28 +++--- 2 files changed, 26 insertions(+), 88 deletions(-) diff --git a/lib/screens/filters/update_interval_lists_modal.dart b/lib/screens/filters/update_interval_lists_modal.dart index c168793..deb714f 100644 --- a/lib/screens/filters/update_interval_lists_modal.dart +++ b/lib/screens/filters/update_interval_lists_modal.dart @@ -94,19 +94,7 @@ class _UpdateIntervalListsModalState extends State { optionsValue: selectedOption, itemValue: 0, onTap: _updateRadioValue, - child: Center( - child: AnimatedDefaultTextStyle( - duration: const Duration(milliseconds: 250), - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 14, - color: selectedOption == 0 - ? Theme.of(context).colorScheme.onInverseSurface - : Theme.of(context).colorScheme.onSurface - ), - child: Text(AppLocalizations.of(context)!.never), - ), - ), + label: AppLocalizations.of(context)!.never, ), ), ), @@ -118,19 +106,7 @@ class _UpdateIntervalListsModalState extends State { optionsValue: selectedOption, itemValue: 1, onTap: _updateRadioValue, - child: Center( - child: AnimatedDefaultTextStyle( - duration: const Duration(milliseconds: 250), - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 14, - color: selectedOption == 1 - ? Theme.of(context).colorScheme.onInverseSurface - : Theme.of(context).colorScheme.onSurface - ), - child: Text(AppLocalizations.of(context)!.hour1), - ), - ), + label: AppLocalizations.of(context)!.hour1, ), ), ), @@ -142,19 +118,7 @@ class _UpdateIntervalListsModalState extends State { optionsValue: selectedOption, itemValue: 12, onTap: _updateRadioValue, - child: Center( - child: AnimatedDefaultTextStyle( - duration: const Duration(milliseconds: 250), - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 14, - color: selectedOption == 12 - ? Theme.of(context).colorScheme.onInverseSurface - : Theme.of(context).colorScheme.onSurface - ), - child: Text(AppLocalizations.of(context)!.hours12), - ), - ), + label: AppLocalizations.of(context)!.hours12, ), ), ), @@ -166,19 +130,8 @@ class _UpdateIntervalListsModalState extends State { optionsValue: selectedOption, itemValue: 24, onTap: _updateRadioValue, - child: Center( - child: AnimatedDefaultTextStyle( - duration: const Duration(milliseconds: 250), - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 14, - color: selectedOption == 24 - ? Theme.of(context).colorScheme.onInverseSurface - : Theme.of(context).colorScheme.onSurface - ), - child: Text(AppLocalizations.of(context)!.hours24), - ), - ), + label: AppLocalizations.of(context)!.hours24, + ), ), ), @@ -190,19 +143,8 @@ class _UpdateIntervalListsModalState extends State { optionsValue: selectedOption, itemValue: 72, onTap: _updateRadioValue, - child: Center( - child: AnimatedDefaultTextStyle( - duration: const Duration(milliseconds: 250), - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 14, - color: selectedOption == 72 - ? Theme.of(context).colorScheme.onInverseSurface - : Theme.of(context).colorScheme.onSurface - ), - child: Text(AppLocalizations.of(context)!.days3), - ), - ), + label: AppLocalizations.of(context)!.days3, + ), ), ), @@ -214,19 +156,7 @@ class _UpdateIntervalListsModalState extends State { optionsValue: selectedOption, itemValue: 168, onTap: _updateRadioValue, - child: Center( - child: AnimatedDefaultTextStyle( - duration: const Duration(milliseconds: 250), - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 14, - color: selectedOption == 168 - ? Theme.of(context).colorScheme.onInverseSurface - : Theme.of(context).colorScheme.onSurface - ), - child: Text(AppLocalizations.of(context)!.days7), - ), - ), + label: AppLocalizations.of(context)!.days7, ), ), ), diff --git a/lib/widgets/option_box.dart b/lib/widgets/option_box.dart index 7c05a67..f19a88f 100644 --- a/lib/widgets/option_box.dart +++ b/lib/widgets/option_box.dart @@ -1,17 +1,17 @@ import 'package:flutter/material.dart'; class OptionBox extends StatelessWidget { - final Widget child; final dynamic optionsValue; final dynamic itemValue; final void Function(dynamic) onTap; + final String label; const OptionBox({ Key? key, - required this.child, required this.optionsValue, required this.itemValue, required this.onTap, + required this.label, }) : super(key: key); @override @@ -25,19 +25,27 @@ class OptionBox extends StatelessWidget { child: AnimatedContainer( duration: const Duration(milliseconds: 250), curve: Curves.easeInOut, - padding: const EdgeInsets.all(15), + padding: const EdgeInsets.symmetric(vertical: 16), decoration: BoxDecoration( borderRadius: BorderRadius.circular(50), - border: Border.all( - color: optionsValue == itemValue - ? Theme.of(context).colorScheme.primary - : Theme.of(context).colorScheme.onSurfaceVariant - ), color: optionsValue == itemValue ? Theme.of(context).colorScheme.primary - : Colors.transparent, + : Theme.of(context).colorScheme.primaryContainer, + ), + child: AnimatedDefaultTextStyle( + duration: const Duration(milliseconds: 250), + style: TextStyle( + fontWeight: FontWeight.w500, + fontSize: 14, + color: optionsValue == itemValue + ? Theme.of(context).colorScheme.onInverseSurface + : Theme.of(context).colorScheme.onSurface + ), + child: Text( + label, + textAlign: TextAlign.center, + ), ), - child: child, ), ), ); From 151cbb996e96eb0a07cdf7d1ebfaa11836acb7ed Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 8 Oct 2023 14:22:03 +0200 Subject: [PATCH 11/14] Bug fixes --- lib/models/dns_info.dart | 2 +- .../settings/dns/private_reverse_servers.dart | 2 +- lib/services/http_requests.dart | 250 +++++++++++------- 3 files changed, 153 insertions(+), 101 deletions(-) diff --git a/lib/models/dns_info.dart b/lib/models/dns_info.dart index 5ab3cc2..67103f7 100644 --- a/lib/models/dns_info.dart +++ b/lib/models/dns_info.dart @@ -13,7 +13,7 @@ class DnsInfo { int? cacheTtlMin; int? cacheTtlMax; bool? cacheOptimistic; - bool resolveClients; + bool? resolveClients; bool usePrivatePtrResolvers; List localPtrUpstreams; String blockingIpv4; diff --git a/lib/screens/settings/dns/private_reverse_servers.dart b/lib/screens/settings/dns/private_reverse_servers.dart index bf6f63b..cd02684 100644 --- a/lib/screens/settings/dns/private_reverse_servers.dart +++ b/lib/screens/settings/dns/private_reverse_servers.dart @@ -79,7 +79,7 @@ class _PrivateReverseDnsServersScreenState extends State res['statusCode'] ?? 'null').toString(), + resBody: result.map((res) => res['body'] ?? 'null').toString(), + ) + }; + } } else { return { @@ -636,22 +650,21 @@ class ApiClient { 'log': AppLog( type: 'get_clients', dateTime: DateTime.now(), - message: 'error_code_not_expected', + message: 'no_response', statusCode: result.map((res) => res['statusCode'] ?? 'null').toString(), resBody: result.map((res) => res['body'] ?? 'null').toString(), ) }; } - } - else { + } catch (e) { + Sentry.captureException(e); return { 'result': 'error', 'log': AppLog( type: 'get_clients', dateTime: DateTime.now(), message: 'no_response', - statusCode: result.map((res) => res['statusCode'] ?? 'null').toString(), - resBody: result.map((res) => res['body'] ?? 'null').toString(), + resBody: e.toString() ) }; } @@ -701,68 +714,94 @@ class ApiClient { String? responseStatus, String? search }) async { - final result = await apiRequest( - server: server, - method: 'get', - urlPath: '/querylog?limit=$count${offset != null ? '&offset=$offset' : ''}${olderThan != null ? '&older_than=${olderThan.toIso8601String()}' : ''}${responseStatus != null ? '&response_status=$responseStatus' : ''}${search != null ? '&search=$search' : ''}', - type: 'get_logs' - ); - - if (result['hasResponse'] == true) { - if (result['statusCode'] == 200) { - return { - 'result': 'success', - 'data': LogsData.fromJson(jsonDecode(result['body'])) - }; + try { + final result = await apiRequest( + server: server, + method: 'get', + urlPath: '/querylog?limit=$count${offset != null ? '&offset=$offset' : ''}${olderThan != null ? '&older_than=${olderThan.toIso8601String()}' : ''}${responseStatus != null ? '&response_status=$responseStatus' : ''}${search != null ? '&search=$search' : ''}', + type: 'get_logs' + ); + + if (result['hasResponse'] == true) { + if (result['statusCode'] == 200) { + return { + 'result': 'success', + 'data': LogsData.fromJson(jsonDecode(result['body'])) + }; + } + else { + return { + 'result': 'error', + 'log': AppLog( + type: 'get_logs', + dateTime: DateTime.now(), + message: 'error_code_not_expected', + statusCode: result['statusCode'].toString(), + resBody: result['body'] + ) + }; + } } else { - return { - 'result': 'error', - 'log': AppLog( - type: 'get_logs', - dateTime: DateTime.now(), - message: 'error_code_not_expected', - statusCode: result['statusCode'].toString(), - resBody: result['body'] - ) - }; + return result; } - } - else { - return result; + } catch (e) { + Sentry.captureException(e); + return { + 'result': 'error', + 'log': AppLog( + type: 'get_logs', + dateTime: DateTime.now(), + message: 'error_code_not_expected', + resBody: e.toString() + ) + }; } } Future getFilteringRules() async { - final result = await apiRequest( - server: server, - method: 'get', - urlPath: '/filtering/status', - type: 'get_filtering_rules' - ); - - if (result['hasResponse'] == true) { - if (result['statusCode'] == 200) { - return { - 'result': 'success', - 'data': FilteringStatus.fromJson(jsonDecode(result['body'])) - }; + try { + final result = await apiRequest( + server: server, + method: 'get', + urlPath: '/filtering/status', + type: 'get_filtering_rules' + ); + + if (result['hasResponse'] == true) { + if (result['statusCode'] == 200) { + return { + 'result': 'success', + 'data': FilteringStatus.fromJson(jsonDecode(result['body'])) + }; + } + else { + return { + 'result': 'error', + 'log': AppLog( + type: 'get_filtering_rules', + dateTime: DateTime.now(), + message: 'error_code_not_expected', + statusCode: result['statusCode'].toString(), + resBody: result['body'] + ) + }; + } } else { - return { - 'result': 'error', - 'log': AppLog( - type: 'get_filtering_rules', - dateTime: DateTime.now(), - message: 'error_code_not_expected', - statusCode: result['statusCode'].toString(), - resBody: result['body'] - ) - }; + return result; } - } - else { - return result; + } catch (e) { + Sentry.captureException(e); + return { + 'result': 'error', + 'log': AppLog( + type: 'get_filtering_rules', + dateTime: DateTime.now(), + message: 'error_code_not_expected', + resBody: e.toString() + ) + }; } } @@ -899,32 +938,46 @@ class ApiClient { } Future getFiltering() async { - final result = await Future.wait([ - apiRequest( - urlPath: '/filtering/status', - method: 'get', - server: server, - type: 'get_filtering_status' - ), - apiRequest( - urlPath: '/blocked_services/list', - method: 'get', - server: server, - type: 'get_filtering_status' - ), - ]); + try { + final result = await Future.wait([ + apiRequest( + urlPath: '/filtering/status', + method: 'get', + server: server, + type: 'get_filtering_status' + ), + apiRequest( + urlPath: '/blocked_services/list', + method: 'get', + server: server, + type: 'get_filtering_status' + ), + ]); - if (result[0]['hasResponse'] == true && result[0]['hasResponse'] == true) { - if (result[0]['statusCode'] == 200 && result[0]['statusCode'] == 200) { - return { - 'result': 'success', - 'data': Filtering.fromJson({ - ...jsonDecode(result[0]['body']), - "blocked_services": result[1]['body'] != null - ? jsonDecode(result[1]['body']) - : [] - }) - }; + if (result[0]['hasResponse'] == true && result[0]['hasResponse'] == true) { + if (result[0]['statusCode'] == 200 && result[0]['statusCode'] == 200) { + return { + 'result': 'success', + 'data': Filtering.fromJson({ + ...jsonDecode(result[0]['body']), + "blocked_services": result[1]['body'] != null + ? jsonDecode(result[1]['body']) + : [] + }) + }; + } + else { + return { + 'result': 'error', + 'log': AppLog( + type: 'get_filtering_status', + dateTime: DateTime.now(), + message: 'error_code_not_expected', + statusCode: result.map((res) => res['statusCode'] ?? 'null').toString(), + resBody: result.map((res) => res['body'] ?? 'null').toString(), + ) + }; + } } else { return { @@ -932,22 +985,21 @@ class ApiClient { 'log': AppLog( type: 'get_filtering_status', dateTime: DateTime.now(), - message: 'error_code_not_expected', + message: 'no_response', statusCode: result.map((res) => res['statusCode'] ?? 'null').toString(), resBody: result.map((res) => res['body'] ?? 'null').toString(), ) }; } - } - else { + } catch (e) { + Sentry.captureException(e); return { 'result': 'error', 'log': AppLog( type: 'get_filtering_status', dateTime: DateTime.now(), message: 'no_response', - statusCode: result.map((res) => res['statusCode'] ?? 'null').toString(), - resBody: result.map((res) => res['body'] ?? 'null').toString(), + resBody: e.toString(), ) }; } From 48fc96a16a4d2544823b9acbb111776da9098027 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 8 Oct 2023 14:37:18 +0200 Subject: [PATCH 12/14] Add lists fixes --- lib/providers/filtering_provider.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/providers/filtering_provider.dart b/lib/providers/filtering_provider.dart index 8b2d459..d416455 100644 --- a/lib/providers/filtering_provider.dart +++ b/lib/providers/filtering_provider.dart @@ -325,14 +325,14 @@ class FilteringProvider with ChangeNotifier { }; } } - else if (result1['result'] == 'error' && result1['log'].statusCode == '400' && result1['log'].resBody.toString().contains("Couldn't fetch filter from url")) { + else if (result1['result'] == 'error' && result1['log'].statusCode == '400' && result1['log'].resBody.toString().contains("data is HTML, not plain text")) { notifyListeners(); return { 'success': false, 'error': 'invalid_url' }; } - else if (result1['result'] == 'error' && result1['log'].statusCode == '400' && result1['log'].resBody.toString().contains('Filter URL already added')) { + else if (result1['result'] == 'error' && result1['log'].statusCode == '400' && result1['log'].resBody.toString().contains('url already exists')) { notifyListeners(); return { 'success': false, From ddd0fe177bc3931a3a8e7fa52e4f57c2298b02d6 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 8 Oct 2023 14:52:08 +0200 Subject: [PATCH 13/14] Fixed update lists --- lib/l10n/app_en.arb | 1 + lib/l10n/app_es.arb | 1 + lib/screens/filters/list_details_screen.dart | 31 +++++++++++++------- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 84a22f6..41fb9ba 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -317,6 +317,7 @@ "deletingRule": "Deleting rule...", "enablingList": "Enabling list...", "disablingList": "Disabling list...", + "savingList": "Saving list...", "disableFiltering": "Disable filtering", "enablingFiltering": "Enabling filtering...", "disablingFiltering": "Disabling filtering...", diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 9b97a3a..2e0734c 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -317,6 +317,7 @@ "deletingRule": "Eliminando regla...", "enablingList": "Habilitando lista...", "disablingList": "Deshabilitando lista...", + "savingList": "Guardando lista...", "disableFiltering": "Deshabilitar filtrado", "enablingFiltering": "Habilitando filtrado...", "disablingFiltering": "Deshabilitando filtrado...", diff --git a/lib/screens/filters/list_details_screen.dart b/lib/screens/filters/list_details_screen.dart index 42464ec..e98c759 100644 --- a/lib/screens/filters/list_details_screen.dart +++ b/lib/screens/filters/list_details_screen.dart @@ -76,15 +76,20 @@ class _ListDetailsScreenState extends State { // ------- // } - void updateList(FilteringListActions action) async { + void updateList({ + required FilteringListActions action, + required Filter filterList, + }) async { ProcessModal processModal = ProcessModal(context: context); processModal.open( - list!.enabled == true - ? AppLocalizations.of(context)!.disablingList - : AppLocalizations.of(context)!.enablingList, + action == FilteringListActions.edit + ? AppLocalizations.of(context)!.savingList + : action == FilteringListActions.disable + ? AppLocalizations.of(context)!.disablingList + : AppLocalizations.of(context)!.enablingList, ); final result = await filteringProvider.updateList( - list: list, + list: filterList, type: widget.type, action: action ); @@ -204,7 +209,8 @@ class _ListDetailsScreenState extends State { list: list, type: widget.type, onEdit: ({required Filter list, required String type}) async => updateList( - FilteringListActions.edit + action: FilteringListActions.edit, + filterList: list ), dialog: true, ), @@ -217,7 +223,8 @@ class _ListDetailsScreenState extends State { list: list, type: widget.type, onEdit: ({required Filter list, required String type}) async => updateList( - FilteringListActions.edit + action: FilteringListActions.edit, + filterList: list ), dialog: false, ), @@ -302,9 +309,10 @@ class _ListDetailsScreenState extends State { children: [ IconButton( onPressed: () => updateList( - list!.enabled == true + action: list!.enabled == true ? FilteringListActions.disable - : FilteringListActions.enable + : FilteringListActions.enable, + filterList: list ), icon: Icon( list.enabled == true @@ -371,9 +379,10 @@ class _ListDetailsScreenState extends State { right: 20, child: FloatingActionButton( onPressed: () => updateList( - list!.enabled == true + action: list!.enabled == true ? FilteringListActions.disable - : FilteringListActions.enable + : FilteringListActions.enable, + filterList: list ), child: Icon( list.enabled == true From f84b217d919e07e50e575bb2d64b4580fc8d50e7 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 8 Oct 2023 21:46:18 +0200 Subject: [PATCH 14/14] Updated dhcp interfaces list --- lib/screens/settings/dhcp/dhcp.dart | 4 +- .../settings/dhcp/dhcp_interface_item.dart | 136 ++++++++ .../settings/dhcp/select_interface_modal.dart | 316 +++++++----------- 3 files changed, 266 insertions(+), 190 deletions(-) create mode 100644 lib/screens/settings/dhcp/dhcp_interface_item.dart diff --git a/lib/screens/settings/dhcp/dhcp.dart b/lib/screens/settings/dhcp/dhcp.dart index a93a338..724a33a 100644 --- a/lib/screens/settings/dhcp/dhcp.dart +++ b/lib/screens/settings/dhcp/dhcp.dart @@ -345,7 +345,9 @@ class _DhcpScreenState extends State { }), dialog: false, ), - isScrollControlled: true + isScrollControlled: true, + useSafeArea: true, + backgroundColor: Colors.transparent ); } }); diff --git a/lib/screens/settings/dhcp/dhcp_interface_item.dart b/lib/screens/settings/dhcp/dhcp_interface_item.dart new file mode 100644 index 0000000..900328e --- /dev/null +++ b/lib/screens/settings/dhcp/dhcp_interface_item.dart @@ -0,0 +1,136 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +import 'package:adguard_home_manager/models/dhcp.dart'; + +class DhcpInterfaceItem extends StatelessWidget { + final NetworkInterface networkInterface; + final void Function(NetworkInterface) onSelect; + + const DhcpInterfaceItem({ + Key? key, + required this.networkInterface, + required this.onSelect + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Material( + color: Colors.transparent, + child: InkWell( + onTap: () { + Navigator.pop(context); + onSelect(networkInterface); + }, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + networkInterface.name, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w400, + color: Theme.of(context).colorScheme.onSurface + ), + ), + Row( + children: [ + Text( + "${AppLocalizations.of(context)!.hardwareAddress}: ", + style: TextStyle( + fontSize: 14, + color: Theme.of(context).colorScheme.onSurfaceVariant + ), + ), + Text( + networkInterface.hardwareAddress, + style: TextStyle( + fontSize: 14, + color: Theme.of(context).colorScheme.onSurfaceVariant + ), + ), + ], + ), + const SizedBox(height: 5), + if (networkInterface.flags.isNotEmpty) ...[ + Row( + children: [ + Text( + "Flags: ", + style: TextStyle( + fontSize: 14, + color: Theme.of(context).colorScheme.onSurfaceVariant + ), + ), + Text( + networkInterface.flags.join(', '), + style: TextStyle( + fontSize: 14, + color: Theme.of(context).colorScheme.onSurfaceVariant + ), + ), + ], + ), + const SizedBox(height: 5), + ], + if (networkInterface.gatewayIp != null && networkInterface.gatewayIp != '') ...[ + Row( + children: [ + Text( + "${AppLocalizations.of(context)!.gatewayIp}: ", + style: TextStyle( + fontSize: 14, + color: Theme.of(context).colorScheme.onSurfaceVariant + ), + ), + Text( + networkInterface.gatewayIp!, + style: TextStyle( + fontSize: 14, + color: Theme.of(context).colorScheme.onSurfaceVariant + ), + ), + ], + ), + const SizedBox(height: 5), + ], + if (networkInterface.ipv4Addresses.isNotEmpty) ...[ + Row( + children: [ + Flexible( + child: Text( + "${AppLocalizations.of(context)!.ipv4addresses}: ${networkInterface.ipv4Addresses.join(', ')}", + style: TextStyle( + fontSize: 14, + color: Theme.of(context).colorScheme.onSurfaceVariant + ), + ), + ) + ], + ), + const SizedBox(height: 5), + ], + if (networkInterface.ipv6Addresses.isNotEmpty) ...[ + Row( + children: [ + Flexible( + child: Text( + "${AppLocalizations.of(context)!.ipv6addresses}: ${networkInterface.ipv6Addresses.join(', ')}", + style: TextStyle( + fontSize: 14, + color: Theme.of(context).colorScheme.onSurfaceVariant + ), + ), + ) + ], + ), + ] + ], + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/screens/settings/dhcp/select_interface_modal.dart b/lib/screens/settings/dhcp/select_interface_modal.dart index 01b7e06..668548d 100644 --- a/lib/screens/settings/dhcp/select_interface_modal.dart +++ b/lib/screens/settings/dhcp/select_interface_modal.dart @@ -3,6 +3,8 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:adguard_home_manager/screens/settings/dhcp/dhcp_interface_item.dart'; + import 'package:adguard_home_manager/models/dhcp.dart'; class SelectInterfaceModal extends StatelessWidget { @@ -19,205 +21,141 @@ class SelectInterfaceModal extends StatelessWidget { @override Widget build(BuildContext context) { - Widget content() { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Flexible( - child: SingleChildScrollView( - child: Wrap( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Column( - children: [ - Padding( - padding: const EdgeInsets.only(top: 24), - child: Icon( - Icons.settings_ethernet_rounded, - size: 24, - color: Theme.of(context).listTileTheme.iconColor - ), - ), - const SizedBox(height: 16), - Text( - AppLocalizations.of(context)!.selectInterface, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 24, - color: Theme.of(context).colorScheme.onSurface - ), - ), - ], - ), - ], - ), - const SizedBox(height: 16), - ListView.builder( - primary: false, - shrinkWrap: true, - itemCount: interfaces.length, - itemBuilder: (context, index) => Material( - color: Colors.transparent, - child: InkWell( - onTap: () { - Navigator.pop(context); - onSelect(interfaces[index]); - }, - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - interfaces[index].name, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w400, - color: Theme.of(context).colorScheme.onSurface - ), - ), - Row( - children: [ - Text( - "${AppLocalizations.of(context)!.hardwareAddress}: ", - style: TextStyle( - fontSize: 14, - color: Theme.of(context).colorScheme.onSurfaceVariant - ), - ), - Text( - interfaces[index].hardwareAddress, - style: TextStyle( - fontSize: 14, - color: Theme.of(context).colorScheme.onSurfaceVariant - ), - ), - ], - ), - const SizedBox(height: 5), - if (interfaces[index].flags.isNotEmpty) ...[ - Row( - children: [ - Text( - "Flags: ", - style: TextStyle( - fontSize: 14, - color: Theme.of(context).colorScheme.onSurfaceVariant - ), - ), - Text( - interfaces[index].flags.join(', '), - style: TextStyle( - fontSize: 14, - color: Theme.of(context).colorScheme.onSurfaceVariant - ), - ), - ], - ), - const SizedBox(height: 5), - ], - if (interfaces[index].gatewayIp != null && interfaces[index].gatewayIp != '') ...[ - Row( - children: [ - Text( - "${AppLocalizations.of(context)!.gatewayIp}: ", - style: TextStyle( - fontSize: 14, - color: Theme.of(context).colorScheme.onSurfaceVariant - ), - ), - Text( - interfaces[index].gatewayIp!, - style: TextStyle( - fontSize: 14, - color: Theme.of(context).colorScheme.onSurfaceVariant - ), - ), - ], - ), - const SizedBox(height: 5), - ], - if (interfaces[index].ipv4Addresses.isNotEmpty) ...[ - Row( - children: [ - Flexible( - child: Text( - "${AppLocalizations.of(context)!.ipv4addresses}: ${interfaces[index].ipv4Addresses.join(', ')}", - style: TextStyle( - fontSize: 14, - color: Theme.of(context).colorScheme.onSurfaceVariant - ), - ), - ) - ], - ), - const SizedBox(height: 5), - ], - if (interfaces[index].ipv6Addresses.isNotEmpty) ...[ - Row( - children: [ - Flexible( - child: Text( - "${AppLocalizations.of(context)!.ipv6addresses}: ${interfaces[index].ipv6Addresses.join(', ')}", - style: TextStyle( - fontSize: 14, - color: Theme.of(context).colorScheme.onSurfaceVariant - ), - ), - ) - ], - ), - ] - ], - ), - ), - ), - ) - ), - ], - ), - ), - ), - Padding( - padding: const EdgeInsets.all(20), - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(AppLocalizations.of(context)!.cancel) - ) - ], - ), - ), - if (Platform.isIOS) const SizedBox(height: 16) - ], - ); - } - if (dialog == true) { return Dialog( child: ConstrainedBox( constraints: const BoxConstraints( - maxWidth: 500 + maxWidth: 500, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.all(16), + child: Wrap( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Column( + children: [ + Icon( + Icons.settings_ethernet_rounded, + size: 24, + color: Theme.of(context).listTileTheme.iconColor + ), + const SizedBox(height: 16), + Text( + AppLocalizations.of(context)!.selectInterface, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 24, + color: Theme.of(context).colorScheme.onSurface + ), + ), + ], + ), + ], + ), + const SizedBox(height: 16), + ], + ), + ), + Expanded( + child: ListView.builder( + itemCount: interfaces.length, + itemBuilder: (context, index) => DhcpInterfaceItem( + networkInterface: interfaces[index], + onSelect: onSelect + ) + ), + ), + Padding( + padding: const EdgeInsets.all(20), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(AppLocalizations.of(context)!.cancel) + ) + ], + ), + ), + if (Platform.isIOS) const SizedBox(height: 16) + ], ), - child: content() ), ); } else { - return Container( - decoration: BoxDecoration( - color: Theme.of(context).dialogBackgroundColor, - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(28), - topRight: Radius.circular(28) - ) + return GestureDetector( + onTap: () => Navigator.of(context).pop(), + child: Container( + color: Colors.transparent, + child: DraggableScrollableSheet( + initialChildSize: 0.6, + minChildSize: 0.3, + maxChildSize: 1, + builder: (context, controller) { + return Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(28), + topRight: Radius.circular(28), + ), + ), + child: Column( + children: [ + Container( + margin: const EdgeInsets.all(16), + width: 36, + height: 4, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: Colors.grey + ), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: Column( + children: [ + Icon( + Icons.settings_ethernet_rounded, + size: 24, + color: Theme.of(context).listTileTheme.iconColor + ), + const SizedBox(height: 16), + Text( + AppLocalizations.of(context)!.selectInterface, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 24, + color: Theme.of(context).colorScheme.onSurface + ), + ), + ], + ), + ), + Expanded( + child: ListView.builder( + controller: controller, + itemCount: interfaces.length, + itemBuilder: (context, index) => DhcpInterfaceItem( + networkInterface: interfaces[index], + onSelect: onSelect + ) + ) + ), + const SizedBox(height: 16) + ], + ), + ); + }, ), - child: content() - ); + ), + ); } } } \ No newline at end of file