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';