diff --git a/lib/functions/open_url.dart b/lib/functions/open_url.dart index b4d52d5..b435e47 100644 --- a/lib/functions/open_url.dart +++ b/lib/functions/open_url.dart @@ -1,17 +1,30 @@ -import 'package:flutter_web_browser/flutter_web_browser.dart'; +import 'dart:io'; -void openUrl(String url) { - FlutterWebBrowser.openWebPage( - url: url, - customTabsOptions: const CustomTabsOptions( - instantAppsEnabled: true, - showTitle: true, - urlBarHidingEnabled: false, - ), - safariVCOptions: const SafariViewControllerOptions( - barCollapsingEnabled: true, - dismissButtonStyle: SafariViewControllerDismissButtonStyle.close, - modalPresentationCapturesStatusBarAppearance: true, - ) - ); -} \ No newline at end of file +import 'package:flutter_web_browser/flutter_web_browser.dart'; +import 'package:url_launcher/url_launcher.dart'; + +void openUrl(String url) async { + if (Platform.isAndroid || Platform.isIOS) { + FlutterWebBrowser.openWebPage( + url: url, + customTabsOptions: const CustomTabsOptions( + instantAppsEnabled: true, + showTitle: true, + urlBarHidingEnabled: false, + ), + safariVCOptions: const SafariViewControllerOptions( + barCollapsingEnabled: true, + dismissButtonStyle: SafariViewControllerDismissButtonStyle.close, + modalPresentationCapturesStatusBarAppearance: true, + ) + ); + } + else { + final uri = Uri.parse(url); + if (await canLaunchUrl(uri)) { + await launchUrl(uri); + } else { + throw 'Could not launch $url'; + } + } +} \ No newline at end of file diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index ea3738d..c08bb8e 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -609,5 +609,7 @@ "safeSearchSettingsNotLoaded": "Error when loading safe search settings.", "loadingLogsSettings": "Loading logs settings...", "selectOptionLeftColumn": "Select an option of the left column", - "selectClientLeftColumn": "Select a client of the left column" + "selectClientLeftColumn": "Select a client of the left column", + "disableList": "Disable list", + "enableList": "Enable list" } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 6bc3ded..e4bf4b6 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -609,5 +609,7 @@ "safeSearchSettingsNotLoaded": "Error al cargar la configuración de búsqueda segura.", "loadingLogsSettings": "Cargando configuración de registros...", "selectOptionLeftColumn": "Selecciona una opción de la columna de la izquierda", - "selectClientLeftColumn": "Selecciona un cliente de la columna de la izquierda" + "selectClientLeftColumn": "Selecciona un cliente de la columna de la izquierda", + "disableList": "Deshabilitar lista", + "enableList": "Habilitar lista" } \ No newline at end of file diff --git a/lib/screens/filters/fab.dart b/lib/screens/filters/add_button.dart similarity index 88% rename from lib/screens/filters/fab.dart rename to lib/screens/filters/add_button.dart index 7a06e35..f2ba43e 100644 --- a/lib/screens/filters/fab.dart +++ b/lib/screens/filters/add_button.dart @@ -17,12 +17,14 @@ import 'package:adguard_home_manager/constants/enums.dart'; import 'package:adguard_home_manager/models/filtering.dart'; import 'package:adguard_home_manager/providers/servers_provider.dart'; -class FiltersFab extends StatelessWidget { +class AddFiltersButton extends StatelessWidget { final String type; + final Widget Function(void Function()) widget; - const FiltersFab({ + const AddFiltersButton({ Key? key, required this.type, + required this.widget }) : super(key: key); @override @@ -68,14 +70,27 @@ class FiltersFab extends StatelessWidget { } void openAddCustomRule() { - Navigator.of(context).push( - MaterialPageRoute( - fullscreenDialog: true, + if (width > 700 || !(Platform.isAndroid || Platform.isIOS)) { + showDialog( + context: context, builder: (context) => AddCustomRule( - onConfirm: confirmAddRule + onConfirm: confirmAddRule, + dialog: true, ), - ) - ); + barrierDismissible: false + ); + } + else { + Navigator.of(context).push( + MaterialPageRoute( + fullscreenDialog: true, + builder: (context) => AddCustomRule( + onConfirm: confirmAddRule, + dialog: false, + ), + ) + ); + } } void confirmAddList({required String name, required String url, required String type}) async { @@ -182,11 +197,10 @@ class FiltersFab extends StatelessWidget { } } - return FloatingActionButton( - onPressed: type == 'blacklist' || type == 'whitelist' + return widget( + type == 'blacklist' || type == 'whitelist' ? () => openAddWhitelistBlacklist() : () => openAddCustomRule(), - child: const Icon(Icons.add), ); } } \ No newline at end of file diff --git a/lib/screens/filters/add_custom_rule.dart b/lib/screens/filters/add_custom_rule.dart index 3f1e1f2..dfb5753 100644 --- a/lib/screens/filters/add_custom_rule.dart +++ b/lib/screens/filters/add_custom_rule.dart @@ -1,17 +1,17 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:provider/provider.dart'; -import 'package:flutter_web_browser/flutter_web_browser.dart'; +import 'package:adguard_home_manager/functions/open_url.dart'; import 'package:adguard_home_manager/constants/urls.dart'; -import 'package:adguard_home_manager/providers/app_config_provider.dart'; class AddCustomRule extends StatefulWidget { final void Function(String) onConfirm; + final bool dialog; const AddCustomRule({ Key? key, - required this.onConfirm + required this.onConfirm, + required this.dialog }) : super(key: key); @override @@ -72,293 +72,338 @@ class _AddCustomRuleState extends State { return rule; } - - void openDocsPage() { - FlutterWebBrowser.openWebPage( - url: Urls.customRuleDocs, - customTabsOptions: const CustomTabsOptions( - instantAppsEnabled: true, - showTitle: true, - urlBarHidingEnabled: false, - ), - safariVCOptions: const SafariViewControllerOptions( - barCollapsingEnabled: true, - dismissButtonStyle: SafariViewControllerDismissButtonStyle.close, - modalPresentationCapturesStatusBarAppearance: true, - ) - ); - } @override Widget build(BuildContext context) { - 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: [ - const SizedBox(height: 24), - Row( - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - Container( - padding: const EdgeInsets.symmetric( - horizontal: 10, - vertical: 5 - ), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.primary.withOpacity(0.1), - borderRadius: BorderRadius.circular(30), - border: Border.all( - color: Theme.of(context).colorScheme.primary - ) - ), - child: Text( - buildRule(), - textAlign: TextAlign.center, - style: TextStyle( - color: Theme.of(context).colorScheme.primary, - fontWeight: FontWeight.w500 - ), + + List content() { + return [ + const SizedBox(height: 24), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 5 + ), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(30), + border: Border.all( + color: Theme.of(context).colorScheme.primary ) ), - ], - ), - const SizedBox(height: 30), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: TextFormField( - controller: domainController, - onChanged: (value) => setState(() => {}), - decoration: InputDecoration( - prefixIcon: const Icon(Icons.link_rounded), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) + child: Text( + buildRule(), + textAlign: TextAlign.center, + style: TextStyle( + color: Theme.of(context).colorScheme.primary, + fontWeight: FontWeight.w500 ), - errorText: domainError, - labelText: AppLocalizations.of(context)!.domain, + ) + ), + ], + ), + Container(height: 30), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: TextFormField( + controller: domainController, + onChanged: (value) => setState(() => {}), + decoration: InputDecoration( + prefixIcon: const Icon(Icons.link_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) ), + errorText: domainError, + labelText: AppLocalizations.of(context)!.domain, ), ), - const SizedBox(height: 30), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: SegmentedButton( - segments: [ - ButtonSegment( - value: BlockingPresets.block, - label: Text(AppLocalizations.of(context)!.block) - ), - ButtonSegment( - value: BlockingPresets.unblock, - label: Text(AppLocalizations.of(context)!.unblock) - ), - ButtonSegment( - value: BlockingPresets.custom, - label: Text(AppLocalizations.of(context)!.custom) - ), - ], - selected: {preset}, - onSelectionChanged: (value) => setState(() => preset = value.first), - ), + ), + Container(height: 30), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: SegmentedButton( + segments: [ + ButtonSegment( + value: BlockingPresets.block, + label: Text(AppLocalizations.of(context)!.block) + ), + ButtonSegment( + value: BlockingPresets.unblock, + label: Text(AppLocalizations.of(context)!.unblock) + ), + ButtonSegment( + value: BlockingPresets.custom, + label: Text(AppLocalizations.of(context)!.custom) + ), + ], + selected: {preset}, + onSelectionChanged: (value) => setState(() => preset = value.first), ), - const SizedBox(height: 20), - Material( - color: Colors.transparent, - child: InkWell( - onTap: () => setState(() => addImportant = !addImportant), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 28), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Padding( - padding: const EdgeInsets.only(left: 10), - child: Text( - AppLocalizations.of(context)!.addImportant, - style: TextStyle( - fontSize: 16, - color: Theme.of(context).colorScheme.onSurface - ), + ), + Container(height: 20), + Material( + color: Colors.transparent, + child: InkWell( + onTap: () => setState(() => addImportant = !addImportant), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 28), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 10), + child: Text( + AppLocalizations.of(context)!.addImportant, + style: TextStyle( + fontSize: 16, + color: Theme.of(context).colorScheme.onSurface ), ), - Switch( - value: addImportant, - onChanged: (value) => setState(() => addImportant = value), - ) - ], - ), + ), + Switch( + value: addImportant, + onChanged: (value) => setState(() => addImportant = value), + ) + ], ), ), ), - const SizedBox(height: 20), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: Card( - child: Padding( - padding: const EdgeInsets.all(20), - child: Column( + ), + Container(height: 20), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Card( + child: Padding( + padding: const EdgeInsets.all(20), + child: Column( + children: [ + Row( + children: [ + Icon( + Icons.info, + color: Theme.of(context).colorScheme.onSurface + ), + const SizedBox(width: 20), + Text( + AppLocalizations.of(context)!.examples, + style: TextStyle( + fontSize: 18, + color: Theme.of(context).colorScheme.onSurface + ), + ) + ], + ), + const SizedBox(height: 20), + SizedBox( + width: double.maxFinite, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "||example.org^", + textAlign: TextAlign.left, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.primary + ), + ), + const SizedBox(height: 5), + Text( + AppLocalizations.of(context)!.example1, + style: TextStyle( + fontSize: 14, + color: Theme.of(context).colorScheme.primary + ), + ), + const SizedBox(height: 20), + Text( + "@@||example.org^", + textAlign: TextAlign.left, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.primary + ), + ), + const SizedBox(height: 5), + Text( + AppLocalizations.of(context)!.example2, + style: TextStyle( + fontSize: 14, + color: Theme.of(context).colorScheme.primary + ), + ), + const SizedBox(height: 20), + Text( + "! Here goes a comment", + textAlign: TextAlign.left, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.primary + ), + ), + Text( + "# Also a comment", + textAlign: TextAlign.left, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.primary + ), + ), + const SizedBox(height: 5), + Text( + AppLocalizations.of(context)!.example3, + style: TextStyle( + fontSize: 14, + color: Theme.of(context).colorScheme.primary + ), + ), + const SizedBox(height: 20), + Text( + "/REGEX/", + textAlign: TextAlign.left, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.primary + ), + ), + const SizedBox(height: 5), + Text( + AppLocalizations.of(context)!.example4, + style: TextStyle( + fontSize: 14, + color: Theme.of(context).colorScheme.primary + ), + ), + ], + ), + ) + ], + ), + ), + ), + ), + Container(height: 20), + Material( + color: Colors.transparent, + child: InkWell( + onTap: () => openUrl(Urls.customRuleDocs), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 10), + child: Text( + AppLocalizations.of(context)!.moreInformation, + style: TextStyle( + fontSize: 16, + color: Theme.of(context).colorScheme.onSurface + ), + ), + ), + Padding( + padding: const EdgeInsets.only(right: 15), + child: Icon( + Icons.open_in_new, + color: Theme.of(context).colorScheme.onSurface + ), + ) + ], + ), + ), + ), + ), + Container(height: 20) + ]; + } + + if (widget.dialog == true) { + return Dialog( + child: ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 500 + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.all(16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ - Icon( - Icons.info, - color: Theme.of(context).colorScheme.onSurface + IconButton( + onPressed: () => Navigator.pop(context), + icon: const Icon(Icons.clear_rounded), + tooltip: AppLocalizations.of(context)!.close, ), - const SizedBox(width: 20), + const SizedBox(width: 8), Text( - AppLocalizations.of(context)!.examples, - style: TextStyle( - fontSize: 18, - color: Theme.of(context).colorScheme.onSurface + AppLocalizations.of(context)!.addCustomRule, + style: const TextStyle( + fontSize: 22 ), - ) + ), ], ), - const SizedBox(height: 20), - SizedBox( - width: double.maxFinite, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "||example.org^", - textAlign: TextAlign.left, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.primary - ), - ), - const SizedBox(height: 5), - Text( - AppLocalizations.of(context)!.example1, - style: TextStyle( - fontSize: 14, - color: Theme.of(context).colorScheme.primary - ), - ), - const SizedBox(height: 20), - Text( - "@@||example.org^", - textAlign: TextAlign.left, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.primary - ), - ), - const SizedBox(height: 5), - Text( - AppLocalizations.of(context)!.example2, - style: TextStyle( - fontSize: 14, - color: Theme.of(context).colorScheme.primary - ), - ), - const SizedBox(height: 20), - Text( - "! Here goes a comment", - textAlign: TextAlign.left, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.primary - ), - ), - Text( - "# Also a comment", - textAlign: TextAlign.left, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.primary - ), - ), - const SizedBox(height: 5), - Text( - AppLocalizations.of(context)!.example3, - style: TextStyle( - fontSize: 14, - color: Theme.of(context).colorScheme.primary - ), - ), - const SizedBox(height: 20), - Text( - "/REGEX/", - textAlign: TextAlign.left, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.primary - ), - ), - const SizedBox(height: 5), - Text( - AppLocalizations.of(context)!.example4, - style: TextStyle( - fontSize: 14, - color: Theme.of(context).colorScheme.primary - ), - ), - ], - ), + IconButton( + onPressed: checkValidValues() == true + ? () { + Navigator.pop(context); + widget.onConfirm(buildRule()); + } + : null, + icon: const Icon(Icons.check) ) ], ), ), - ), - ), - const SizedBox(height: 20), - Material( - color: Colors.transparent, - child: InkWell( - onTap: openDocsPage, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 10), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Padding( - padding: const EdgeInsets.only(left: 10), - child: Text( - AppLocalizations.of(context)!.moreInformation, - style: TextStyle( - fontSize: 16, - color: Theme.of(context).colorScheme.onSurface - ), - ), - ), - Padding( - padding: const EdgeInsets.only(right: 15), - child: Icon( - Icons.open_in_new, - color: Theme.of(context).colorScheme.onSurface - ), - ) - ], + Flexible( + child: SingleChildScrollView( + child: Wrap( + alignment: WrapAlignment.center, + children: content(), + ), ), - ), - ), + ) + ], ), - const SizedBox(height: 20) - ], - ), - ); + ), + ); + } + 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 diff --git a/lib/screens/filters/blocked_services_screen.dart b/lib/screens/filters/blocked_services_screen.dart index fba09d2..2c0912e 100644 --- a/lib/screens/filters/blocked_services_screen.dart +++ b/lib/screens/filters/blocked_services_screen.dart @@ -12,7 +12,12 @@ import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/providers/servers_provider.dart'; class BlockedServicesScreen extends StatelessWidget { - const BlockedServicesScreen({Key? key}) : super(key: key); + final bool dialog; + + const BlockedServicesScreen({ + Key? key, + required this.dialog + }) : super(key: key); @override Widget build(BuildContext context) { @@ -21,7 +26,8 @@ class BlockedServicesScreen extends StatelessWidget { return BlockedServicesScreenWidget( serversProvider: serversProvider, - appConfigProvider: appConfigProvider + appConfigProvider: appConfigProvider, + dialog: dialog, ); } } @@ -29,11 +35,13 @@ class BlockedServicesScreen extends StatelessWidget { class BlockedServicesScreenWidget extends StatefulWidget { final ServersProvider serversProvider; final AppConfigProvider appConfigProvider; + final bool dialog; const BlockedServicesScreenWidget({ Key? key, required this.serversProvider, required this.appConfigProvider, + required this.dialog }) : super(key: key); @override @@ -209,24 +217,74 @@ class _BlockedServicesScreenStateWidget extends State Navigator.pop(context), + icon: const Icon(Icons.clear_rounded), + tooltip: AppLocalizations.of(context)!.close, + ), + const SizedBox(width: 8), + Text( + AppLocalizations.of(context)!.blockedServices, + style: const TextStyle( + fontSize: 22 + ), + ) + ], + ), + IconButton( + onPressed: updateBlockedServices, + icon: const Icon( + Icons.save_rounded + ), + tooltip: AppLocalizations.of(context)!.save, + ), + ], + ), + ), + Expanded( + child: body() + ), + ], + ) + ), + ); + } + 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: loadBlockedServices, + child: body() + ), + ); + } } } \ No newline at end of file diff --git a/lib/screens/filters/custom_rules_list.dart b/lib/screens/filters/custom_rules_list.dart index 41b1d0f..5a96716 100644 --- a/lib/screens/filters/custom_rules_list.dart +++ b/lib/screens/filters/custom_rules_list.dart @@ -2,33 +2,27 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; -import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:adguard_home_manager/screens/filters/fab.dart'; -import 'package:adguard_home_manager/screens/filters/remove_custom_rule_modal.dart'; +import 'package:adguard_home_manager/screens/filters/add_button.dart'; import 'package:adguard_home_manager/widgets/tab_content_list.dart'; -import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/constants/enums.dart'; -import 'package:adguard_home_manager/models/filtering.dart'; -import 'package:adguard_home_manager/providers/app_config_provider.dart'; -import 'package:adguard_home_manager/services/http_requests.dart'; -import 'package:adguard_home_manager/providers/servers_provider.dart'; -import 'package:adguard_home_manager/classes/process_modal.dart'; class CustomRulesList extends StatefulWidget { final LoadStatus loadStatus; final ScrollController scrollController; final List data; final Future Function() fetchData; + final void Function(String) onRemoveCustomRule; const CustomRulesList({ Key? key, required this.loadStatus, required this.scrollController, required this.data, - required this.fetchData + required this.fetchData, + required this.onRemoveCustomRule }) : super(key: key); @override @@ -61,52 +55,6 @@ class _CustomRulesListState extends State { @override Widget build(BuildContext context) { - final serversProvider = Provider.of(context); - final appConfigProvider = Provider.of(context); - - void removeCustomRule(String rule) async { - ProcessModal processModal = ProcessModal(context: context); - processModal.open(AppLocalizations.of(context)!.deletingRule); - - final List newRules = serversProvider.filtering.data!.userRules.where((r) => r != rule).toList(); - - final result = await setCustomRules(server: serversProvider.selectedServer!, rules: newRules); - - processModal.close(); - - if (result['result'] == 'success') { - FilteringData filteringData = serversProvider.filtering.data!; - filteringData.userRules = newRules; - serversProvider.setFilteringData(filteringData); - - showSnacbkar( - context: context, - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.ruleRemovedSuccessfully, - color: Colors.green - ); - } - else { - appConfigProvider.addLog(result['log']); - - showSnacbkar( - context: context, - appConfigProvider: appConfigProvider, - label: AppLocalizations.of(context)!.ruleNotRemoved, - color: Colors.red - ); - } - } - - void openRemoveCustomRuleModal(String rule) { - showDialog( - context: context, - builder: (context) => RemoveCustomRule( - onConfirm: () => removeCustomRule(rule), - ) - ); - } - bool checkIfComment(String value) { final regex = RegExp(r'^(!|#).*$'); if (regex.hasMatch(value)) { @@ -184,7 +132,7 @@ class _CustomRulesListState extends State { ), subtitle: generateSubtitle(widget.data[index]), trailing: IconButton( - onPressed: () => openRemoveCustomRuleModal(widget.data[index]), + onPressed: () => widget.onRemoveCustomRule(widget.data[index]), icon: const Icon(Icons.delete) ), ), @@ -239,8 +187,12 @@ class _CustomRulesListState extends State { ), loadStatus: widget.loadStatus, onRefresh: widget.fetchData, - fab: const FiltersFab( + fab: AddFiltersButton( type: 'custom_rule', + widget: (fn) => FloatingActionButton( + onPressed: fn, + child: const Icon(Icons.add), + ), ), fabVisible: isVisible, ); diff --git a/lib/screens/filters/filter_list_tile.dart b/lib/screens/filters/filter_list_tile.dart deleted file mode 100644 index 1782d62..0000000 --- a/lib/screens/filters/filter_list_tile.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'package:flutter/material.dart'; - -class FilterListTile extends StatelessWidget { - final IconData icon; - final String title; - final String subtitle; - final Color? color; - final bool? bold; - - const FilterListTile({ - Key? key, - required this.icon, - required this.title, - required this.subtitle, - this.color, - this.bold, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return Container( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Icon( - icon, - size: 24, - color: Theme.of(context).listTileTheme.iconColor, - ), - const SizedBox(width: 16), - Flexible( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - title, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w400, - color: Theme.of(context).colorScheme.onSurface - ), - ), - const SizedBox(height: 3), - Text( - subtitle, - style: TextStyle( - fontSize: 14, - color: color ?? Theme.of(context).listTileTheme.textColor, - fontWeight: bold == true ? FontWeight.bold : FontWeight.w400 - ), - ), - ], - ), - ) - ], - ), - ); - } -} \ No newline at end of file diff --git a/lib/screens/filters/filters.dart b/lib/screens/filters/filters.dart index 81d4f96..607d791 100644 --- a/lib/screens/filters/filters.dart +++ b/lib/screens/filters/filters.dart @@ -6,15 +6,18 @@ 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/filters/filters_list.dart'; import 'package:adguard_home_manager/screens/filters/check_host_modal.dart'; -import 'package:adguard_home_manager/screens/filters/custom_rules_list.dart'; +import 'package:adguard_home_manager/screens/filters/filters_tabs_view.dart'; +import 'package:adguard_home_manager/screens/filters/filters_triple_column.dart'; +import 'package:adguard_home_manager/screens/filters/list_details_screen.dart'; +import 'package:adguard_home_manager/screens/filters/remove_custom_rule_modal.dart'; import 'package:adguard_home_manager/screens/filters/blocked_services_screen.dart'; import 'package:adguard_home_manager/screens/filters/update_interval_lists_modal.dart'; import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/classes/process_modal.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; +import 'package:adguard_home_manager/models/filtering.dart'; import 'package:adguard_home_manager/constants/enums.dart'; import 'package:adguard_home_manager/services/http_requests.dart'; import 'package:adguard_home_manager/models/clients.dart'; @@ -49,10 +52,7 @@ class FiltersWidget extends StatefulWidget { State createState() => _FiltersWidgetState(); } -class _FiltersWidgetState extends State with TickerProviderStateMixin { - late TabController tabController; - final ScrollController scrollController = ScrollController(); - +class _FiltersWidgetState extends State { Future fetchFilters() async { widget.serversProvider.setFilteringLoadStatus(LoadStatus.loading, false); @@ -70,20 +70,14 @@ class _FiltersWidgetState extends State with TickerProviderStateM } } + List generateClientsList(List clients, List ips) { + return clients.where((client) => ips.contains(client.ip)).toList(); + } + @override void initState() { fetchFilters(); super.initState(); - tabController = TabController( - initialIndex: 0, - length: 3, - vsync: this, - ); - tabController.addListener(() => widget.appConfigProvider.setSelectedFiltersTab(tabController.index)); - } - - List generateClientsList(List clients, List ips) { - return clients.where((client) => ips.contains(client.ip)).toList(); } @override @@ -232,200 +226,222 @@ class _FiltersWidgetState extends State with TickerProviderStateM void openBlockedServicesModal() { Future.delayed(const Duration(seconds: 0), () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => const BlockedServicesScreen(), - ) - ); + 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, + ), + ) + ); + } }); } - return DefaultTabController( - length: 3, - child: NestedScrollView( - controller: scrollController, - headerSliverBuilder: ((context, innerBoxIsScrolled) { - return [ - SliverOverlapAbsorber( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - sliver: SliverAppBar( - title: Text(AppLocalizations.of(context)!.filters), - pinned: true, - floating: true, - forceElevated: innerBoxIsScrolled, - centerTitle: false, - actions: serversProvider.filtering.loadStatus == LoadStatus.loaded ? [ - IconButton( - onPressed: enableDisableFiltering, - tooltip: serversProvider.filtering.data!.enabled == true - ? AppLocalizations.of(context)!.disableFiltering - : AppLocalizations.of(context)!.enableFiltering, - icon: Stack( - children: [ - const Icon(Icons.power_settings_new_rounded), - Positioned( - bottom: 0, - right: 0, - child: Stack( - children: [ - Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(30), - color: Colors.white - ), - child: Icon( - serversProvider.filtering.data!.enabled == true - ? Icons.check_circle_rounded - : Icons.cancel, - size: 12, - color: serversProvider.filtering.data!.enabled == true - ? appConfigProvider.useThemeColorForStatus == true - ? Theme.of(context).colorScheme.primary - : Colors.green - : appConfigProvider.useThemeColorForStatus == true - ? Colors.grey - : Colors.red - ), - ), - ], - ), - ) - ], - ) + void removeCustomRule(String rule) async { + ProcessModal processModal = ProcessModal(context: context); + processModal.open(AppLocalizations.of(context)!.deletingRule); + + final List newRules = serversProvider.filtering.data!.userRules.where((r) => r != rule).toList(); + + final result = await setCustomRules(server: serversProvider.selectedServer!, rules: newRules); + + processModal.close(); + + if (result['result'] == 'success') { + FilteringData filteringData = serversProvider.filtering.data!; + filteringData.userRules = newRules; + serversProvider.setFilteringData(filteringData); + + showSnacbkar( + context: context, + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.ruleRemovedSuccessfully, + color: Colors.green + ); + } + else { + appConfigProvider.addLog(result['log']); + + showSnacbkar( + context: context, + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.ruleNotRemoved, + color: Colors.red + ); + } + } + + void openRemoveCustomRuleModal(String rule) { + showDialog( + context: context, + builder: (context) => RemoveCustomRule( + onConfirm: () => removeCustomRule(rule), + ) + ); + } + + void openListDetails(Filter filter, String type) { + if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) { + showDialog( + context: context, + builder: (context) => ListDetailsScreen( + list: filter, + type: type, + dialog: true, + ), + barrierDismissible: false + ); + } + else { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => ListDetailsScreen( + list: filter, + type: type, + dialog: false, + ) + ) + ); + } + } + + List actions() { + if (serversProvider.filtering.loadStatus == LoadStatus.loaded) { + return [ + IconButton( + onPressed: enableDisableFiltering, + tooltip: serversProvider.filtering.data!.enabled == true + ? AppLocalizations.of(context)!.disableFiltering + : AppLocalizations.of(context)!.enableFiltering, + icon: Stack( + children: [ + const Icon(Icons.power_settings_new_rounded), + Positioned( + bottom: 0, + right: 0, + child: Stack( + children: [ + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(30), + color: Colors.white + ), + child: Icon( + serversProvider.filtering.data!.enabled == true + ? Icons.check_circle_rounded + : Icons.cancel, + size: 12, + color: serversProvider.filtering.data!.enabled == true + ? appConfigProvider.useThemeColorForStatus == true + ? Theme.of(context).colorScheme.primary + : Colors.green + : appConfigProvider.useThemeColorForStatus == true + ? Colors.grey + : Colors.red + ), + ), + ], ), - IconButton( - onPressed: () { - if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) { - showDialog( - context: context, - builder: (context) => UpdateIntervalListsModal( - interval: serversProvider.filtering.data!.interval, - onChange: setUpdateFrequency, - dialog: true, - ), - ); - } - else { - showModalBottomSheet( - context: context, - builder: (context) => UpdateIntervalListsModal( - interval: serversProvider.filtering.data!.interval, - onChange: setUpdateFrequency, - dialog: false, - ), - backgroundColor: Colors.transparent, - isScrollControlled: true - ); - } - }, - icon: const Icon(Icons.update_rounded) + ) + ], + ) + ), + IconButton( + onPressed: () { + if (width > 900 || !(Platform.isAndroid || Platform.isIOS)) { + showDialog( + context: context, + builder: (context) => UpdateIntervalListsModal( + interval: serversProvider.filtering.data!.interval, + onChange: setUpdateFrequency, + dialog: true, ), - PopupMenuButton( - itemBuilder: (context) => [ - PopupMenuItem( - onTap: fetchUpdateLists, - child: Row( - children: [ - const Icon(Icons.sync_rounded), - const SizedBox(width: 10), - Text(AppLocalizations.of(context)!.updateLists) - ], - ) - ), - PopupMenuItem( - onTap: openBlockedServicesModal, - child: Row( - children: [ - const Icon(Icons.block), - const SizedBox(width: 10), - Text(AppLocalizations.of(context)!.blockedServices) - ], - ) - ), - PopupMenuItem( - onTap: showCheckHostModal, - child: Row( - children: [ - const Icon(Icons.shield_rounded), - const SizedBox(width: 10), - Text(AppLocalizations.of(context)!.checkHostFiltered) - ], - ) - ), - ] + ); + } + else { + showModalBottomSheet( + context: context, + builder: (context) => UpdateIntervalListsModal( + interval: serversProvider.filtering.data!.interval, + onChange: setUpdateFrequency, + dialog: false, ), - const SizedBox(width: 5), - ] : [], - bottom: TabBar( - controller: tabController, - isScrollable: true, - unselectedLabelColor: Theme.of(context).colorScheme.onSurfaceVariant, - tabs: [ - Tab( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.verified_user_rounded), - const SizedBox(width: 8), - Text(AppLocalizations.of(context)!.whitelists,) - ], - ), - ), - Tab( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.gpp_bad_rounded), - const SizedBox(width: 8), - Text(AppLocalizations.of(context)!.blacklists) - ], - ), - ), - Tab( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.shield_rounded), - const SizedBox(width: 8), - Text(AppLocalizations.of(context)!.customRules) - ], - ), - ), - ] + backgroundColor: Colors.transparent, + isScrollControlled: true + ); + } + }, + icon: const Icon(Icons.update_rounded), + tooltip: AppLocalizations.of(context)!.updateFrequency, + ), + PopupMenuButton( + itemBuilder: (context) => [ + PopupMenuItem( + onTap: fetchUpdateLists, + child: Row( + children: [ + const Icon(Icons.sync_rounded), + const SizedBox(width: 10), + Text(AppLocalizations.of(context)!.updateLists) + ], ) ), - ) - ]; - }), - body: TabBarView( - controller: tabController, - children: [ - FiltersList( - loadStatus: serversProvider.filtering.loadStatus, - scrollController: scrollController, - type: 'whitelist', - data: serversProvider.filtering.loadStatus == LoadStatus.loaded - ? serversProvider.filtering.data!.whitelistFilters : [], - fetchData: fetchFilters, - ), - FiltersList( - loadStatus: serversProvider.filtering.loadStatus, - scrollController: scrollController, - type: 'blacklist', - data: serversProvider.filtering.loadStatus == LoadStatus.loaded - ? serversProvider.filtering.data!.filters : [], - fetchData: fetchFilters, - ), - CustomRulesList( - loadStatus: serversProvider.filtering.loadStatus, - scrollController: scrollController, - data: serversProvider.filtering.loadStatus == LoadStatus.loaded - ? serversProvider.filtering.data!.userRules : [], - fetchData: fetchFilters, - ), - ] - ) - ) - ); + PopupMenuItem( + onTap: openBlockedServicesModal, + child: Row( + children: [ + const Icon(Icons.block), + const SizedBox(width: 10), + Text(AppLocalizations.of(context)!.blockedServices) + ], + ) + ), + PopupMenuItem( + onTap: showCheckHostModal, + child: Row( + children: [ + const Icon(Icons.shield_rounded), + const SizedBox(width: 10), + Text(AppLocalizations.of(context)!.checkHostFiltered) + ], + ) + ), + ] + ), + const SizedBox(width: 5), + ]; + } + else { + return []; + } + } + + if (width > 1200) { + return FiltersTripleColumn( + onRemoveCustomRule: openRemoveCustomRuleModal, + onOpenDetailsModal: openListDetails, + actions: actions(), + refreshData: fetchFilters, + ); + } + else { + return FiltersTabsView( + appConfigProvider: appConfigProvider, + fetchFilters: fetchFilters, + actions: actions(), + onRemoveCustomRule: openRemoveCustomRuleModal, + onOpenDetailsModal: openListDetails, + ); + } } } \ No newline at end of file diff --git a/lib/screens/filters/filters_list.dart b/lib/screens/filters/filters_list.dart index effe42d..be4ff75 100644 --- a/lib/screens/filters/filters_list.dart +++ b/lib/screens/filters/filters_list.dart @@ -7,8 +7,7 @@ import 'package:provider/provider.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:adguard_home_manager/screens/filters/fab.dart'; -import 'package:adguard_home_manager/screens/filters/list_details_screen.dart'; +import 'package:adguard_home_manager/screens/filters/add_button.dart'; import 'package:adguard_home_manager/widgets/custom_list_tile.dart'; import 'package:adguard_home_manager/widgets/tab_content_list.dart'; @@ -23,6 +22,7 @@ class FiltersList extends StatefulWidget { final List data; final Future Function() fetchData; final String type; + final void Function(Filter, String) onOpenDetailsScreen; const FiltersList({ Key? key, @@ -31,6 +31,7 @@ class FiltersList extends StatefulWidget { required this.data, required this.fetchData, required this.type, + required this.onOpenDetailsScreen }) : super(key: key); @override @@ -64,17 +65,6 @@ class _FiltersListState extends State { @override Widget build(BuildContext context) { final appConfigProvider = Provider.of(context); - - void openDetailsModal(Filter filter) { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => ListDetailsScreen( - list: filter, - type: widget.type, - ) - ) - ); - } return CustomTabContentList( loadingGenerator: () => SizedBox( @@ -112,7 +102,7 @@ class _FiltersListState extends State { ? Colors.grey : Colors.red ), - onTap: () => openDetailsModal(widget.data[index]), + onTap: () => widget.onOpenDetailsScreen(widget.data[index], widget.type), ), noData: Container( width: double.maxFinite, @@ -166,8 +156,12 @@ class _FiltersListState extends State { ), loadStatus: widget.loadStatus, onRefresh: widget.fetchData, - fab: FiltersFab( + fab: AddFiltersButton( type: widget.type, + widget: (fn) => FloatingActionButton( + onPressed: fn, + child: const Icon(Icons.add), + ), ), fabVisible: isVisible, ); diff --git a/lib/screens/filters/filters_tabs_view.dart b/lib/screens/filters/filters_tabs_view.dart new file mode 100644 index 0000000..17f74a9 --- /dev/null +++ b/lib/screens/filters/filters_tabs_view.dart @@ -0,0 +1,143 @@ +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/filters/custom_rules_list.dart'; +import 'package:adguard_home_manager/screens/filters/filters_list.dart'; + +import 'package:adguard_home_manager/constants/enums.dart'; +import 'package:adguard_home_manager/models/filtering.dart'; +import 'package:adguard_home_manager/providers/app_config_provider.dart'; +import 'package:adguard_home_manager/providers/servers_provider.dart'; + +class FiltersTabsView extends StatefulWidget { + final AppConfigProvider appConfigProvider; + final Future Function() fetchFilters; + final List actions; + final void Function(String) onRemoveCustomRule; + final void Function(Filter, String) onOpenDetailsModal; + + const FiltersTabsView({ + Key? key, + required this.appConfigProvider, + required this.fetchFilters, + required this.actions, + required this.onOpenDetailsModal, + required this.onRemoveCustomRule + }) : super(key: key); + + @override + State createState() => _FiltersTabsViewState(); +} + +class _FiltersTabsViewState extends State with TickerProviderStateMixin { + late TabController tabController; + final ScrollController scrollController = ScrollController(); + + @override + void initState() { + widget.fetchFilters(); + super.initState(); + tabController = TabController( + initialIndex: 0, + length: 3, + vsync: this, + ); + tabController.addListener(() => widget.appConfigProvider.setSelectedFiltersTab(tabController.index)); + } + + @override + Widget build(BuildContext context) { + final serversProvider = Provider.of(context); + + return DefaultTabController( + length: 3, + child: NestedScrollView( + controller: scrollController, + headerSliverBuilder: ((context, innerBoxIsScrolled) { + return [ + SliverOverlapAbsorber( + handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), + sliver: SliverAppBar( + title: Text(AppLocalizations.of(context)!.filters), + pinned: true, + floating: true, + forceElevated: innerBoxIsScrolled, + centerTitle: false, + actions: widget.actions, + bottom: TabBar( + controller: tabController, + isScrollable: true, + unselectedLabelColor: Theme.of(context).colorScheme.onSurfaceVariant, + tabs: [ + Tab( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.verified_user_rounded), + const SizedBox(width: 8), + Text(AppLocalizations.of(context)!.whitelists,) + ], + ), + ), + Tab( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.gpp_bad_rounded), + const SizedBox(width: 8), + Text(AppLocalizations.of(context)!.blacklists) + ], + ), + ), + Tab( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.shield_rounded), + const SizedBox(width: 8), + Text(AppLocalizations.of(context)!.customRules) + ], + ), + ), + ] + ) + ), + ) + ]; + }), + body: TabBarView( + controller: tabController, + children: [ + FiltersList( + loadStatus: serversProvider.filtering.loadStatus, + scrollController: scrollController, + type: 'whitelist', + data: serversProvider.filtering.loadStatus == LoadStatus.loaded + ? serversProvider.filtering.data!.whitelistFilters : [], + fetchData: widget.fetchFilters, + onOpenDetailsScreen: widget.onOpenDetailsModal, + ), + FiltersList( + loadStatus: serversProvider.filtering.loadStatus, + scrollController: scrollController, + type: 'blacklist', + data: serversProvider.filtering.loadStatus == LoadStatus.loaded + ? serversProvider.filtering.data!.filters : [], + fetchData: widget.fetchFilters, + onOpenDetailsScreen: widget.onOpenDetailsModal, + ), + CustomRulesList( + loadStatus: serversProvider.filtering.loadStatus, + scrollController: scrollController, + data: serversProvider.filtering.loadStatus == LoadStatus.loaded + ? serversProvider.filtering.data!.userRules : [], + fetchData: widget.fetchFilters, + onRemoveCustomRule: widget.onRemoveCustomRule, + ), + ] + ) + ) + ); + } +} \ No newline at end of file diff --git a/lib/screens/filters/filters_triple_column.dart b/lib/screens/filters/filters_triple_column.dart new file mode 100644 index 0000000..3e82fc5 --- /dev/null +++ b/lib/screens/filters/filters_triple_column.dart @@ -0,0 +1,311 @@ +import 'dart:io'; + +import 'package:adguard_home_manager/screens/filters/add_button.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:provider/provider.dart'; + +import 'package:adguard_home_manager/widgets/custom_list_tile.dart'; + +import 'package:adguard_home_manager/constants/enums.dart'; +import 'package:adguard_home_manager/models/filtering.dart'; +import 'package:adguard_home_manager/functions/number_format.dart'; +import 'package:adguard_home_manager/providers/app_config_provider.dart'; +import 'package:adguard_home_manager/providers/servers_provider.dart'; + +class FiltersTripleColumn extends StatelessWidget { + final void Function(String) onRemoveCustomRule; + final void Function(Filter, String) onOpenDetailsModal; + final List actions; + final Future Function() refreshData; + + const FiltersTripleColumn({ + Key? key, + required this.onRemoveCustomRule, + required this.onOpenDetailsModal, + required this.actions, + required this.refreshData + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final serversProvider = Provider.of(context); + final appConfigProvider = Provider.of(context); + + bool checkIfComment(String value) { + final regex = RegExp(r'^(!|#).*$'); + if (regex.hasMatch(value)) { + return true; + } + else { + return false; + } + } + + Widget? generateSubtitle(String rule) { + final allowRegex = RegExp(r'^@@.*$'); + final blockRegex = RegExp(r'^\|\|.*$'); + final commentRegex = RegExp(r'^(#|!).*$'); + + if (allowRegex.hasMatch(rule)) { + return Text( + AppLocalizations.of(context)!.allowed, + style: const TextStyle( + color: Colors.green + ), + ); + } + else if (blockRegex.hasMatch(rule)) { + return Text( + AppLocalizations.of(context)!.blocked, + style: const TextStyle( + color: Colors.red + ), + ); + } + else if (commentRegex.hasMatch(rule)) { + return Text( + AppLocalizations.of(context)!.comment, + style: const TextStyle( + color: Colors.grey + ), + ); + } + else { + return null; + } + } + + Widget content() { + switch (serversProvider.filtering.loadStatus) { + case LoadStatus.loading: + return Row( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const CircularProgressIndicator(), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.loadingFilters, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ), + ], + ); + + case LoadStatus.loaded: + return Row( + children: [ + Expanded( + flex: 1, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + AppLocalizations.of(context)!.whitelists, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.w500 + ), + ), + AddFiltersButton( + type: 'whitelist', + widget: (fn) => IconButton( + onPressed: fn, + icon: const Icon(Icons.add_rounded) + ) + ) + ], + ), + ), + Expanded( + child: ListView.builder( + itemCount: serversProvider.filtering.data!.whitelistFilters.length, + itemBuilder: (context, index) => CustomListTile( + title: serversProvider.filtering.data!.whitelistFilters[index].name, + subtitle: "${intFormat(serversProvider.filtering.data!.whitelistFilters[index].rulesCount, Platform.localeName)} ${AppLocalizations.of(context)!.enabledRules}", + trailing: Icon( + serversProvider.filtering.data!.whitelistFilters[index].enabled == true + ? Icons.check_circle_rounded + : Icons.cancel, + color: serversProvider.filtering.data!.whitelistFilters[index].enabled == true + ? appConfigProvider.useThemeColorForStatus == true + ? Theme.of(context).colorScheme.primary + : Colors.green + : appConfigProvider.useThemeColorForStatus == true + ? Colors.grey + : Colors.red + ), + onTap: () => onOpenDetailsModal(serversProvider.filtering.data!.whitelistFilters[index], 'whitelist'), + ), + ), + ) + ], + ), + ), + Expanded( + flex: 1, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + AppLocalizations.of(context)!.blacklists, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.w500 + ), + ), + AddFiltersButton( + type: 'blacklist', + widget: (fn) => IconButton( + onPressed: fn, + icon: const Icon(Icons.add_rounded) + ) + ) + ], + ), + ), + Expanded( + child: ListView.builder( + itemCount: serversProvider.filtering.data!.filters.length, + itemBuilder: (context, index) => CustomListTile( + title: serversProvider.filtering.data!.filters[index].name, + subtitle: "${intFormat(serversProvider.filtering.data!.filters[index].rulesCount, Platform.localeName)} ${AppLocalizations.of(context)!.enabledRules}", + trailing: Icon( + serversProvider.filtering.data!.filters[index].enabled == true + ? Icons.check_circle_rounded + : Icons.cancel, + color: serversProvider.filtering.data!.filters[index].enabled == true + ? appConfigProvider.useThemeColorForStatus == true + ? Theme.of(context).colorScheme.primary + : Colors.green + : appConfigProvider.useThemeColorForStatus == true + ? Colors.grey + : Colors.red + ), + onTap: () => onOpenDetailsModal(serversProvider.filtering.data!.filters[index], 'blacklist'), + ), + ), + ) + ], + ), + ), + Expanded( + flex: 1, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + AppLocalizations.of(context)!.customRules, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.w500 + ), + ), + AddFiltersButton( + type: '', + widget: (fn) => IconButton( + onPressed: fn, + icon: const Icon(Icons.add_rounded) + ) + ) + ], + ), + ), + Expanded( + child: ListView.builder( + itemCount: serversProvider.filtering.data!.userRules.length, + itemBuilder: (context, index) => ListTile( + title: Text( + serversProvider.filtering.data!.userRules[index], + style: TextStyle( + color: checkIfComment(serversProvider.filtering.data!.userRules[index]) == true + ? Theme.of(context).colorScheme.onSurface.withOpacity(0.6) + : Theme.of(context).colorScheme.onSurface, + fontWeight: FontWeight.normal, + ), + ), + subtitle: generateSubtitle(serversProvider.filtering.data!.userRules[index]), + trailing: IconButton( + onPressed: () => onRemoveCustomRule(serversProvider.filtering.data!.userRules[index]), + icon: const Icon(Icons.delete) + ), + ), + ), + ) + ], + ), + ), + ], + ); + + case LoadStatus.error: + return SizedBox.expand( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Icon( + Icons.error, + color: Colors.red, + size: 50, + ), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.filtersNotLoaded, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ), + ], + ), + ); + + default: + return const SizedBox(); + } + } + + return Scaffold( + appBar: AppBar( + title: Text(AppLocalizations.of(context)!.filters), + actions: [ + IconButton( + onPressed: refreshData, + icon: const Icon(Icons.refresh_rounded), + tooltip: AppLocalizations.of(context)!.refresh, + ), + ...actions + ], + ), + body: content(), + ); + } +} \ No newline at end of file diff --git a/lib/screens/filters/list_details_screen.dart b/lib/screens/filters/list_details_screen.dart index 3001004..92ae4e0 100644 --- a/lib/screens/filters/list_details_screen.dart +++ b/lib/screens/filters/list_details_screen.dart @@ -7,9 +7,9 @@ import 'package:flutter/rendering.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:adguard_home_manager/screens/filters/filter_list_tile.dart'; import 'package:adguard_home_manager/screens/filters/add_list_modal.dart'; import 'package:adguard_home_manager/screens/filters/delete_list_modal.dart'; +import 'package:adguard_home_manager/widgets/custom_list_tile.dart'; import 'package:adguard_home_manager/functions/format_time.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; @@ -23,11 +23,13 @@ import 'package:adguard_home_manager/models/filtering.dart'; class ListDetailsScreen extends StatefulWidget { final Filter list; final String type; + final bool dialog; const ListDetailsScreen({ Key? key, required this.list, required this.type, + required this.dialog }) : super(key: key); @override @@ -218,122 +220,234 @@ class _ListDetailsScreenState extends State { } } - return Scaffold( - appBar: AppBar( - title: Text(AppLocalizations.of(context)!.listDetails), - actions: [ - IconButton( - onPressed: () => { - if (width > 700 || !(Platform.isAndroid || Platform.isIOS)) { - showDialog( - context: context, - builder: (ctx) => AddListModal( - list: widget.list, - type: widget.type, - onEdit: confirmEditList, - dialog: true, - ), - ) - } - else { - showModalBottomSheet( - context: context, - builder: (ctx) => AddListModal( - list: widget.list, - type: widget.type, - onEdit: confirmEditList, - dialog: false, - ), - isScrollControlled: true, - backgroundColor: Colors.transparent - ) - } - }, - icon: const Icon(Icons.edit), - tooltip: AppLocalizations.of(context)!.edit, + List content() { + return [ + CustomListTile( + icon: Icons.shield_rounded, + title: AppLocalizations.of(context)!.currentStatus, + subtitleWidget: Text( + enabled == true + ? AppLocalizations.of(context)!.enabled + : AppLocalizations.of(context)!.disabled, + style: TextStyle( + color: enabled == true + ? appConfigProvider.useThemeColorForStatus == true + ? Theme.of(context).colorScheme.primary + : Colors.green + : appConfigProvider.useThemeColorForStatus == true + ? Colors.grey + : Colors.red, + fontWeight: FontWeight.w500 + ), ), - IconButton( - onPressed: () { + padding: widget.dialog == true + ? const EdgeInsets.symmetric( + horizontal: 24, + vertical: 8 + ) + : null, + ), + CustomListTile( + icon: Icons.badge_rounded, + title: AppLocalizations.of(context)!.name, + subtitle: name, + padding: widget.dialog == true + ? const EdgeInsets.symmetric( + horizontal: 24, + vertical: 8 + ) + : null, + ), + CustomListTile( + icon: Icons.link_rounded, + title: "URL", + subtitle: widget.list.url, + padding: widget.dialog == true + ? const EdgeInsets.symmetric( + horizontal: 24, + vertical: 8 + ) + : null, + ), + CustomListTile( + icon: Icons.list_rounded, + title: AppLocalizations.of(context)!.rules, + subtitle: widget.list.rulesCount.toString(), + padding: widget.dialog == true + ? const EdgeInsets.symmetric( + horizontal: 24, + vertical: 8 + ) + : null, + ), + CustomListTile( + icon: Icons.shield_rounded, + title: AppLocalizations.of(context)!.listType, + subtitle: widget.type == 'whitelist' + ? AppLocalizations.of(context)!.whitelist + : AppLocalizations.of(context)!.blacklist, + padding: widget.dialog == true + ? const EdgeInsets.symmetric( + horizontal: 24, + vertical: 8 + ) + : null, + ), + if (widget.list.lastUpdated != null) CustomListTile( + icon: Icons.schedule_rounded, + title: AppLocalizations.of(context)!.latestUpdate, + subtitle: convertTimestampLocalTimezone(widget.list.lastUpdated!, 'dd-MM-yyyy HH:mm'), + padding: widget.dialog == true + ? const EdgeInsets.symmetric( + horizontal: 24, + vertical: 8 + ) + : null, + ), + if (widget.dialog == true) Container(height: 16) + ]; + } + + List actions() { + return [ + IconButton( + onPressed: () => { + if (width > 700 || !(Platform.isAndroid || Platform.isIOS)) { showDialog( context: context, - builder: (context) => DeleteListModal( - onConfirm: () => deleteList(widget.list, widget.type), - ) - ); - }, - icon: const Icon(Icons.delete), - tooltip: AppLocalizations.of(context)!.delete, + builder: (ctx) => AddListModal( + list: widget.list, + type: widget.type, + onEdit: confirmEditList, + dialog: true, + ), + ) + } + else { + showModalBottomSheet( + context: context, + builder: (ctx) => AddListModal( + list: widget.list, + type: widget.type, + onEdit: confirmEditList, + dialog: false, + ), + isScrollControlled: true, + backgroundColor: Colors.transparent + ) + } + }, + icon: const Icon(Icons.edit), + tooltip: AppLocalizations.of(context)!.edit, + ), + IconButton( + onPressed: () { + showDialog( + context: context, + builder: (context) => DeleteListModal( + onConfirm: () => deleteList(widget.list, widget.type), + ) + ); + }, + icon: const Icon(Icons.delete), + tooltip: AppLocalizations.of(context)!.delete, + ), + const SizedBox(width: 10), + ]; + } + + if (widget.dialog == true) { + return Dialog( + child: ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 500 ), - const SizedBox(width: 10), - ], - ), - body: Stack( - children: [ - ListView( + child: Column( + mainAxisSize: MainAxisSize.min, children: [ - FilterListTile( - icon: Icons.shield_rounded, - title: AppLocalizations.of(context)!.currentStatus, - subtitle: enabled == true - ? AppLocalizations.of(context)!.enabled - : AppLocalizations.of(context)!.disabled, - color: enabled == true - ? appConfigProvider.useThemeColorForStatus == true - ? Theme.of(context).colorScheme.primary - : Colors.green - : appConfigProvider.useThemeColorForStatus == true - ? Colors.grey - : Colors.red, - bold: true, - ), - FilterListTile( - icon: Icons.badge_rounded, - title: AppLocalizations.of(context)!.name, - subtitle: name - ), - FilterListTile( - icon: Icons.link_rounded, - title: "URL", - subtitle: widget.list.url - ), - FilterListTile( - icon: Icons.list_rounded, - title: AppLocalizations.of(context)!.rules, - subtitle: widget.list.rulesCount.toString() - ), - FilterListTile( - icon: Icons.shield_rounded, - title: AppLocalizations.of(context)!.listType, - subtitle: widget.type == 'whitelist' - ? AppLocalizations.of(context)!.whitelist - : AppLocalizations.of(context)!.blacklist, - ), - if (widget.list.lastUpdated != null) FilterListTile( - icon: Icons.schedule_rounded, - title: AppLocalizations.of(context)!.latestUpdate, - subtitle: convertTimestampLocalTimezone(widget.list.lastUpdated!, 'dd-MM-yyyy HH:mm'), + 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), + tooltip: AppLocalizations.of(context)!.close, + ), + const SizedBox(width: 8), + Text( + AppLocalizations.of(context)!.listDetails, + style: const TextStyle( + fontSize: 22 + ), + ) + ], + ), + Row( + children: [ + IconButton( + onPressed: () => enableDisableList(widget.list, !enabled), + icon: Icon( + enabled == true + ? Icons.gpp_bad_rounded + : Icons.verified_user_rounded, + ), + tooltip: enabled == true + ? AppLocalizations.of(context)!.disableList + : AppLocalizations.of(context)!.enableList, + ), + ...actions() + ], + ) + ], + ), ), + Flexible( + child: SingleChildScrollView( + child: Wrap( + children: content(), + ), + ) + ) ], ), - AnimatedPositioned( - duration: const Duration(milliseconds: 100), - curve: Curves.easeInOut, - bottom: fabVisible ? - appConfigProvider.showingSnackbar - ? 70 : (Platform.isIOS ? 40 : 20) - : -70, - right: 20, - child: FloatingActionButton( - onPressed: () => enableDisableList(widget.list, !enabled), - child: Icon( - enabled == true - ? Icons.gpp_bad_rounded - : Icons.verified_user_rounded, - ), + ) + ); + } + else { + return Scaffold( + appBar: AppBar( + title: Text(AppLocalizations.of(context)!.listDetails), + actions: actions(), + ), + body: Stack( + children: [ + ListView( + children: content(), ), - ) - ], - ), - ); + AnimatedPositioned( + duration: const Duration(milliseconds: 100), + curve: Curves.easeInOut, + bottom: fabVisible ? + appConfigProvider.showingSnackbar + ? 70 : (Platform.isIOS ? 40 : 20) + : -70, + right: 20, + child: FloatingActionButton( + onPressed: () => enableDisableList(widget.list, !enabled), + child: Icon( + enabled == true + ? Icons.gpp_bad_rounded + : Icons.verified_user_rounded, + ), + ), + ) + ], + ), + ); + } } } \ No newline at end of file diff --git a/lib/screens/settings/settings.dart b/lib/screens/settings/settings.dart index fe93473..167e2bb 100644 --- a/lib/screens/settings/settings.dart +++ b/lib/screens/settings/settings.dart @@ -119,7 +119,7 @@ class SettingsWidget extends StatelessWidget { ), body: ListView( children: [ - if (serversProvider.selectedServer != null) ...[ + if (serversProvider.selectedServer != null && serversProvider.serverStatus.data != null) ...[ SectionLabel(label: AppLocalizations.of(context)!.serverSettings), if (serverVersionIsAhead( currentVersion: serversProvider.serverStatus.data!.serverVersion, diff --git a/lib/widgets/custom_list_tile.dart b/lib/widgets/custom_list_tile.dart index bfd379d..6f27129 100644 --- a/lib/widgets/custom_list_tile.dart +++ b/lib/widgets/custom_list_tile.dart @@ -23,7 +23,7 @@ class CustomListTile extends StatelessWidget { this.padding, this.onLongPress, this.disabled, - this.onHover + this.onHover, }) : super(key: key); @override diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 8492786..cb9d874 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -8,6 +8,7 @@ #include #include +#include #include void fl_register_plugins(FlPluginRegistry* registry) { @@ -17,6 +18,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) sqlite3_flutter_libs_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "Sqlite3FlutterLibsPlugin"); sqlite3_flutter_libs_plugin_register_with_registrar(sqlite3_flutter_libs_registrar); + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); g_autoptr(FlPluginRegistrar) window_size_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "WindowSizePlugin"); window_size_plugin_register_with_registrar(window_size_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 163e042..da06542 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -5,6 +5,7 @@ list(APPEND FLUTTER_PLUGIN_LIST dynamic_color sqlite3_flutter_libs + url_launcher_linux window_size ) diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index b4f5f0d..1c34d3c 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -10,6 +10,7 @@ import dynamic_color import package_info_plus_macos import sqflite import sqlite3_flutter_libs +import url_launcher_macos import window_size func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { @@ -18,5 +19,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin")) + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) WindowSizePlugin.register(with: registry.registrar(forPlugin: "WindowSizePlugin")) } diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 338e2e2..9346410 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -27,6 +27,8 @@ PODS: - sqlite3/fts5 - sqlite3/perf-threadsafe - sqlite3/rtree + - url_launcher_macos (0.0.1): + - FlutterMacOS - window_size (0.0.2): - FlutterMacOS @@ -37,6 +39,7 @@ DEPENDENCIES: - package_info_plus_macos (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus_macos/macos`) - sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/macos`) - sqlite3_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/macos`) + - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) - window_size (from `Flutter/ephemeral/.symlinks/plugins/window_size/macos`) SPEC REPOS: @@ -57,6 +60,8 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/sqflite/macos sqlite3_flutter_libs: :path: Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/macos + url_launcher_macos: + :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos window_size: :path: Flutter/ephemeral/.symlinks/plugins/window_size/macos @@ -69,6 +74,7 @@ SPEC CHECKSUMS: sqflite: a5789cceda41d54d23f31d6de539d65bb14100ea sqlite3: d31b2b69d59bd1b4ab30e5c92eb18fd8e82fa392 sqlite3_flutter_libs: f20746e4a0245afbee4f20d9afc0072ebff7cc26 + url_launcher_macos: 5335912b679c073563f29d89d33d10d459f95451 window_size: 339dafa0b27a95a62a843042038fa6c3c48de195 PODFILE CHECKSUM: 353c8bcc5d5b0994e508d035b5431cfe18c1dea7 diff --git a/pubspec.lock b/pubspec.lock index c1e392f..898b60a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -675,6 +675,70 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.4" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: "75f2846facd11168d007529d6cd8fcb2b750186bea046af9711f10b907e1587e" + url: "https://pub.dev" + source: hosted + version: "6.1.10" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "22f8db4a72be26e9e3a4aa3f194b1f7afbc76d20ec141f84be1d787db2155cbd" + url: "https://pub.dev" + source: hosted + version: "6.0.31" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: "9af7ea73259886b92199f9e42c116072f05ff9bea2dcb339ab935dfc957392c2" + url: "https://pub.dev" + source: hosted + version: "6.1.4" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: "207f4ddda99b95b4d4868320a352d374b0b7e05eefad95a4a26f57da413443f5" + url: "https://pub.dev" + source: hosted + version: "3.0.5" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "91ee3e75ea9dadf38036200c5d3743518f4a5eb77a8d13fda1ee5764373f185e" + url: "https://pub.dev" + source: hosted + version: "3.0.5" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "6c9ca697a5ae218ce56cece69d46128169a58aa8653c1b01d26fcd4aad8c4370" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "81fe91b6c4f84f222d186a9d23c73157dc4c8e1c71489c4d08be1ad3b228f1aa" + url: "https://pub.dev" + source: hosted + version: "2.0.16" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "254708f17f7c20a9c8c471f67d86d76d4a3f9c1591aad1e15292008aceb82771" + url: "https://pub.dev" + source: hosted + version: "3.0.6" uuid: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 135c7b6..34a0e57 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -64,6 +64,7 @@ dependencies: git: url: https://github.com/JGeek00/flutter_split_view ref: hide-divider + url_launcher: ^6.1.10 dev_dependencies: flutter_test: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 208406a..3d0e948 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -8,6 +8,7 @@ #include #include +#include #include void RegisterPlugins(flutter::PluginRegistry* registry) { @@ -15,6 +16,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("DynamicColorPluginCApi")); Sqlite3FlutterLibsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("Sqlite3FlutterLibsPlugin")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); WindowSizePluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("WindowSizePlugin")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 69d8901..8ca2ff7 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -5,6 +5,7 @@ list(APPEND FLUTTER_PLUGIN_LIST dynamic_color sqlite3_flutter_libs + url_launcher_windows window_size )