From 27ffa75d63ff13a24cc294e072dd8d23b9d3ba1b Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Thu, 4 May 2023 22:38:37 +0200 Subject: [PATCH] Added some context menus on desktop --- lib/functions/get_filtered_status.dart | 39 ++ lib/l10n/app_en.arb | 4 +- lib/l10n/app_es.arb | 4 +- lib/main.dart | 6 +- lib/models/menu_option.dart | 15 + lib/screens/clients/active_client_tile.dart | 157 ++++--- lib/screens/clients/added_client_tile.dart | 405 ++++++++++-------- lib/screens/clients/logs_list_client.dart | 102 +++-- .../filters/filters_triple_column.dart | 58 ++- lib/screens/home/home.dart | 6 +- lib/screens/home/top_items.dart | 42 +- lib/screens/home/top_items_options_modal.dart | 84 ---- lib/screens/logs/log_tile.dart | 47 +- lib/screens/top_items/top_items.dart | 142 +++--- lib/screens/top_items/top_items_modal.dart | 142 +++--- lib/widgets/domain_options.dart | 158 +++++++ lib/widgets/options_modal.dart | 59 +++ pubspec.lock | 16 + pubspec.yaml | 1 + 19 files changed, 864 insertions(+), 623 deletions(-) create mode 100644 lib/models/menu_option.dart delete mode 100644 lib/screens/home/top_items_options_modal.dart create mode 100644 lib/widgets/domain_options.dart create mode 100644 lib/widgets/options_modal.dart diff --git a/lib/functions/get_filtered_status.dart b/lib/functions/get_filtered_status.dart index 7cddfab..6825ebf 100644 --- a/lib/functions/get_filtered_status.dart +++ b/lib/functions/get_filtered_status.dart @@ -126,4 +126,43 @@ Map getFilteredStatus(BuildContext context, AppConfigProvider a default: return {'filtered': null, 'label': 'Unknown'}; } +} + +bool isDomainBlocked(String filterKey) { + switch (filterKey) { + case 'NotFilteredNotFound': + return false; + + case 'NotFilteredWhiteList': + return false; + + case 'NotFilteredError': + return false; + + case 'FilteredBlackList': + return true; + + case 'FilteredSafeBrowsing': + return true; + + case 'FilteredParental': + return true; + + case 'FilteredInvalid': + return true; + + case 'FilteredSafeSearch': + return true; + + case 'FilteredBlockedService': + return true; + + case 'Rewrite': + case 'RewriteEtcHosts': + case 'RewriteRule': + return false; + + default: + return false; + } } \ No newline at end of file diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 3b087c6..f6beb3f 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -612,5 +612,7 @@ "selectClientLeftColumn": "Select a client of the left column", "disableList": "Disable list", "enableList": "Enable list", - "screens": "Screens" + "screens": "Screens", + "copiedClipboard": "Copied to clipboard", + "seeDetails": "See details" } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 90c396a..88940fd 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -612,5 +612,7 @@ "selectClientLeftColumn": "Selecciona un cliente de la columna de la izquierda", "disableList": "Deshabilitar lista", "enableList": "Habilitar lista", - "screens": "Pantallas" + "screens": "Pantallas", + "copiedClipboard": "Copiado al portapapeles", + "seeDetails": "Ver los detalles" } \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 5efba05..1d900af 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -140,7 +140,11 @@ class _MainState extends State
{ ], builder: (context, child) { return MediaQuery( - data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0), + data: MediaQuery.of(context).copyWith( + textScaleFactor: !(Platform.isAndroid || Platform.isIOS) + ? 0.9 + : 1.0 + ), child: child!, ); }, diff --git a/lib/models/menu_option.dart b/lib/models/menu_option.dart new file mode 100644 index 0000000..e898514 --- /dev/null +++ b/lib/models/menu_option.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; + +class MenuOption { + final IconData? icon; + final String title; + final void Function() action; + final bool? disabled; + + const MenuOption({ + required this.title, + required this.action, + this.icon, + this.disabled + }); +} \ No newline at end of file diff --git a/lib/screens/clients/active_client_tile.dart b/lib/screens/clients/active_client_tile.dart index cabbb6c..975ddbf 100644 --- a/lib/screens/clients/active_client_tile.dart +++ b/lib/screens/clients/active_client_tile.dart @@ -1,7 +1,10 @@ import 'package:flutter/material.dart'; +import 'package:contextmenu/contextmenu.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/widgets/custom_list_tile.dart'; +import 'package:adguard_home_manager/functions/copy_clipboard.dart'; import 'package:adguard_home_manager/models/clients.dart'; class ActiveClientTile extends StatelessWidget { @@ -23,77 +26,113 @@ class ActiveClientTile extends StatelessWidget { if (splitView == true) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 12), - child: Material( - color: Colors.transparent, - borderRadius: BorderRadius.circular(28), - child: InkWell( + child: ContextMenuArea( + builder: (context) => [ + CustomListTile( + title: AppLocalizations.of(context)!.copyClipboard, + icon: Icons.copy_rounded, + onTap: () { + copyToClipboard( + context: context, + value: client.name != '' + ? client.name! + : client.ip, + successMessage: AppLocalizations.of(context)!.copiedClipboard, + ); + Navigator.pop(context); + }, + ) + ], + child: Material( + color: Colors.transparent, borderRadius: BorderRadius.circular(28), - onTap: () => onTap(client), - child: Container( - width: double.maxFinite, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(28), - color: client == selectedClient - ? Theme.of(context).colorScheme.primaryContainer - : null - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Flexible( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - client.name != '' - ? client.name! - : client.ip, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w400, - color: Theme.of(context).colorScheme.onSurface, + child: InkWell( + borderRadius: BorderRadius.circular(28), + onTap: () => onTap(client), + child: Container( + width: double.maxFinite, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(28), + color: client == selectedClient + ? Theme.of(context).colorScheme.primaryContainer + : null + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + client.name != '' + ? client.name! + : client.ip, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w400, + color: Theme.of(context).colorScheme.onSurface, + ), ), - ), - if (client.name != '') Text(client.ip) - ], - ), - ) - ], + if (client.name != '') Text(client.ip) + ], + ), + ) + ], + ), ), - ), - Text( - client.source, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurface + Text( + client.source, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface + ), ), - ), - ], - ) + ], + ) + ), ), ), ), ); } else { - return CustomListTile( - title: client.name != '' - ? client.name! - : client.ip, - subtitle: client.name != '' - ? client.ip - : null, - trailing: Text( - client.source, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurface + return ContextMenuArea( + builder: (context) => [ + CustomListTile( + title: AppLocalizations.of(context)!.copyClipboard, + icon: Icons.copy_rounded, + onTap: () { + copyToClipboard( + context: context, + value: client.name != '' + ? client.name! + : client.ip, + successMessage: AppLocalizations.of(context)!.copiedClipboard, + ); + Navigator.pop(context); + }, + ) + ], + child: CustomListTile( + title: client.name != '' + ? client.name! + : client.ip, + subtitle: client.name != '' + ? client.ip + : null, + trailing: Text( + client.source, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface + ), ), + onTap: () => onTap(client), ), - onTap: () => onTap(client), ); } } diff --git a/lib/screens/clients/added_client_tile.dart b/lib/screens/clients/added_client_tile.dart index d8a4b8f..fcf2ac8 100644 --- a/lib/screens/clients/added_client_tile.dart +++ b/lib/screens/clients/added_client_tile.dart @@ -1,14 +1,17 @@ import 'package:flutter/material.dart'; +import 'package:contextmenu/contextmenu.dart'; import 'package:provider/provider.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.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/functions/copy_clipboard.dart'; import 'package:adguard_home_manager/models/clients.dart'; import 'package:adguard_home_manager/providers/servers_provider.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; -class AddedClientTile extends StatefulWidget { +class AddedClientTile extends StatelessWidget { final Client client; final void Function(Client) onTap; final void Function(Client) onLongPress; @@ -26,224 +29,254 @@ class AddedClientTile extends StatefulWidget { required this.splitView }) : super(key: key); - @override - State createState() => _AddedClientTileState(); -} - -class _AddedClientTileState extends State { - bool hover = false; - @override Widget build(BuildContext context) { final serversProvider = Provider.of(context); final appConfigProvider = Provider.of(context); - if (widget.splitView == true) { + if (splitView == true) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 12), child: Material( color: Colors.transparent, borderRadius: BorderRadius.circular(28), - child: InkWell( - borderRadius: BorderRadius.circular(28), - onTap: () => widget.onTap(widget.client), - onHover: (v) => setState(() => hover = v), - child: Container( - width: double.maxFinite, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(28), - color: widget.client == widget.selectedClient - ? Theme.of(context).colorScheme.primaryContainer - : null + child: ContextMenuArea( + builder: (context) => [ + CustomListTile( + title: AppLocalizations.of(context)!.seeDetails, + icon: Icons.file_open_rounded, + onTap: () { + Navigator.pop(context); + onEdit(client); + } ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Flexible( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - widget.client.ids.toString().replaceAll(RegExp(r'^\[|\]$'), ''), - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w400, - color: Theme.of(context).colorScheme.onSurface, + CustomListTile( + title: AppLocalizations.of(context)!.copyClipboard, + icon: Icons.copy_rounded, + onTap: () { + copyToClipboard( + context: context, + value: client.ids.toString().replaceAll(RegExp(r'^\[|\]$'), ''), + successMessage: AppLocalizations.of(context)!.copiedClipboard, + ); + Navigator.pop(context); + } + ), + ], + child: InkWell( + borderRadius: BorderRadius.circular(28), + onTap: () => onTap(client), + onLongPress: () => onLongPress(client), + child: Container( + width: double.maxFinite, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(28), + color: client == selectedClient + ? Theme.of(context).colorScheme.primaryContainer + : null + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + client.ids.toString().replaceAll(RegExp(r'^\[|\]$'), ''), + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w400, + color: Theme.of(context).colorScheme.onSurface, + ), ), - ), - const SizedBox(height: 8), - Row( - children: [ - Icon( - Icons.filter_list_rounded, - size: 19, - color: widget.client.filteringEnabled == true - ? appConfigProvider.useThemeColorForStatus == true - ? Theme.of(context).colorScheme.primary - : Colors.green - : appConfigProvider.useThemeColorForStatus == true - ? Colors.grey - : Colors.red, - ), - const SizedBox(width: 10), - Icon( - Icons.vpn_lock_rounded, - size: 18, - color: widget.client.safebrowsingEnabled == true - ? appConfigProvider.useThemeColorForStatus == true - ? Theme.of(context).colorScheme.primary - : Colors.green - : appConfigProvider.useThemeColorForStatus == true - ? Colors.grey - : Colors.red, - ), - const SizedBox(width: 10), - Icon( - Icons.block, - size: 18, - color: widget.client.parentalEnabled == true - ? appConfigProvider.useThemeColorForStatus == true - ? Theme.of(context).colorScheme.primary - : Colors.green - : appConfigProvider.useThemeColorForStatus == true - ? Colors.grey - : Colors.red, - ), - const SizedBox(width: 10), - Icon( - Icons.search_rounded, - size: 19, - color: serverVersionIsAhead( - currentVersion: serversProvider.serverStatus.data!.serverVersion, - referenceVersion: 'v0.107.28', - referenceVersionBeta: 'v0.108.0-b.33' - ) == true - ? widget.client.safeSearch != null && widget.client.safeSearch!.enabled == true - ? appConfigProvider.useThemeColorForStatus == true - ? Theme.of(context).colorScheme.primary - : Colors.green - : appConfigProvider.useThemeColorForStatus == true - ? Colors.grey - : Colors.red - : widget.client.safesearchEnabled == true + const SizedBox(height: 8), + Row( + children: [ + Icon( + Icons.filter_list_rounded, + size: 19, + color: client.filteringEnabled == true ? appConfigProvider.useThemeColorForStatus == true ? Theme.of(context).colorScheme.primary : Colors.green : appConfigProvider.useThemeColorForStatus == true ? Colors.grey : Colors.red, - ) - ], - ) - ], - ), - ) - ], + ), + const SizedBox(width: 10), + Icon( + Icons.vpn_lock_rounded, + size: 18, + color: client.safebrowsingEnabled == true + ? appConfigProvider.useThemeColorForStatus == true + ? Theme.of(context).colorScheme.primary + : Colors.green + : appConfigProvider.useThemeColorForStatus == true + ? Colors.grey + : Colors.red, + ), + const SizedBox(width: 10), + Icon( + Icons.block, + size: 18, + color: client.parentalEnabled == true + ? appConfigProvider.useThemeColorForStatus == true + ? Theme.of(context).colorScheme.primary + : Colors.green + : appConfigProvider.useThemeColorForStatus == true + ? Colors.grey + : Colors.red, + ), + const SizedBox(width: 10), + Icon( + Icons.search_rounded, + size: 19, + color: serverVersionIsAhead( + currentVersion: serversProvider.serverStatus.data!.serverVersion, + referenceVersion: 'v0.107.28', + referenceVersionBeta: 'v0.108.0-b.33' + ) == true + ? client.safeSearch != null && client.safeSearch!.enabled == true + ? appConfigProvider.useThemeColorForStatus == true + ? Theme.of(context).colorScheme.primary + : Colors.green + : appConfigProvider.useThemeColorForStatus == true + ? Colors.grey + : Colors.red + : client.safesearchEnabled == true + ? appConfigProvider.useThemeColorForStatus == true + ? Theme.of(context).colorScheme.primary + : Colors.green + : appConfigProvider.useThemeColorForStatus == true + ? Colors.grey + : Colors.red, + ) + ], + ) + ], + ), + ) + ], + ), ), - ), - if (hover == true) IconButton( - onPressed: () => widget.onEdit(widget.client), - icon: const Icon(Icons.edit_rounded) - ) - ], - ) + ], + ) + ), ), ), ), ); } else { - return CustomListTile( - onLongPress: () => widget.onLongPress(widget.client), - onTap: () => widget.onTap(widget.client), - onHover: (v) => setState(() => hover = v), - title: widget.client.name, - subtitleWidget: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - widget.client.ids.toString().replaceAll(RegExp(r'^\[|\]$'), ''), - style: TextStyle( - color: Theme.of(context).listTileTheme.textColor + return ContextMenuArea( + builder: (context) => [ + CustomListTile( + title: AppLocalizations.of(context)!.seeDetails, + icon: Icons.file_open_rounded, + onTap: () { + Navigator.pop(context); + onEdit(client); + } + ), + CustomListTile( + title: AppLocalizations.of(context)!.copyClipboard, + icon: Icons.copy_rounded, + onTap: () { + copyToClipboard( + context: context, + value: client.ids.toString().replaceAll(RegExp(r'^\[|\]$'), ''), + successMessage: AppLocalizations.of(context)!.copiedClipboard, + ); + Navigator.pop(context); + } + ), + ], + child: CustomListTile( + onLongPress: () => onLongPress(client), + onTap: () => onTap(client), + title: client.name, + subtitleWidget: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + client.ids.toString().replaceAll(RegExp(r'^\[|\]$'), ''), + style: TextStyle( + color: Theme.of(context).listTileTheme.textColor + ), ), - ), - const SizedBox(height: 8), - Row( - children: [ - Icon( - Icons.filter_list_rounded, - size: 19, - color: widget.client.filteringEnabled == true - ? appConfigProvider.useThemeColorForStatus == true - ? Theme.of(context).colorScheme.primary - : Colors.green - : appConfigProvider.useThemeColorForStatus == true - ? Colors.grey - : Colors.red, - ), - const SizedBox(width: 10), - Icon( - Icons.vpn_lock_rounded, - size: 18, - color: widget.client.safebrowsingEnabled == true - ? appConfigProvider.useThemeColorForStatus == true - ? Theme.of(context).colorScheme.primary - : Colors.green - : appConfigProvider.useThemeColorForStatus == true - ? Colors.grey - : Colors.red, - ), - const SizedBox(width: 10), - Icon( - Icons.block, - size: 18, - color: widget.client.parentalEnabled == true - ? appConfigProvider.useThemeColorForStatus == true - ? Theme.of(context).colorScheme.primary - : Colors.green - : appConfigProvider.useThemeColorForStatus == true - ? Colors.grey - : Colors.red, - ), - const SizedBox(width: 10), - Icon( - Icons.search_rounded, - size: 19, - color: serverVersionIsAhead( - currentVersion: serversProvider.serverStatus.data!.serverVersion, - referenceVersion: 'v0.107.28', - referenceVersionBeta: 'v0.108.0-b.33' - ) == true - ? widget.client.safeSearch != null && widget.client.safeSearch!.enabled == true - ? appConfigProvider.useThemeColorForStatus == true - ? Theme.of(context).colorScheme.primary - : Colors.green - : appConfigProvider.useThemeColorForStatus == true - ? Colors.grey - : Colors.red - : widget.client.safesearchEnabled == true + const SizedBox(height: 8), + Row( + children: [ + Icon( + Icons.filter_list_rounded, + size: 19, + color: client.filteringEnabled == true ? appConfigProvider.useThemeColorForStatus == true ? Theme.of(context).colorScheme.primary : Colors.green : appConfigProvider.useThemeColorForStatus == true ? Colors.grey : Colors.red, - ) - ], - ) - ], + ), + const SizedBox(width: 10), + Icon( + Icons.vpn_lock_rounded, + size: 18, + color: client.safebrowsingEnabled == true + ? appConfigProvider.useThemeColorForStatus == true + ? Theme.of(context).colorScheme.primary + : Colors.green + : appConfigProvider.useThemeColorForStatus == true + ? Colors.grey + : Colors.red, + ), + const SizedBox(width: 10), + Icon( + Icons.block, + size: 18, + color: client.parentalEnabled == true + ? appConfigProvider.useThemeColorForStatus == true + ? Theme.of(context).colorScheme.primary + : Colors.green + : appConfigProvider.useThemeColorForStatus == true + ? Colors.grey + : Colors.red, + ), + const SizedBox(width: 10), + Icon( + Icons.search_rounded, + size: 19, + color: serverVersionIsAhead( + currentVersion: serversProvider.serverStatus.data!.serverVersion, + referenceVersion: 'v0.107.28', + referenceVersionBeta: 'v0.108.0-b.33' + ) == true + ? client.safeSearch != null && client.safeSearch!.enabled == true + ? appConfigProvider.useThemeColorForStatus == true + ? Theme.of(context).colorScheme.primary + : Colors.green + : appConfigProvider.useThemeColorForStatus == true + ? Colors.grey + : Colors.red + : client.safesearchEnabled == true + ? appConfigProvider.useThemeColorForStatus == true + ? Theme.of(context).colorScheme.primary + : Colors.green + : appConfigProvider.useThemeColorForStatus == true + ? Colors.grey + : Colors.red, + ) + ], + ) + ], + ), ), - trailing: hover == true - ? IconButton( - onPressed: () => widget.onEdit(widget.client), - icon: const Icon(Icons.edit_rounded) - ) - : null, ); } } diff --git a/lib/screens/clients/logs_list_client.dart b/lib/screens/clients/logs_list_client.dart index e2c0c27..99b0891 100644 --- a/lib/screens/clients/logs_list_client.dart +++ b/lib/screens/clients/logs_list_client.dart @@ -144,53 +144,67 @@ class _LogsListClientState extends State { ); case 1: - return RefreshIndicator( - onRefresh: fetchLogs, - child: ListView.builder( - controller: scrollController, - padding: const EdgeInsets.only(top: 0), - itemCount: isLoadingMore == true - ? logsData!.data.length+1 - : logsData!.data.length, - itemBuilder: (context, index) { - if (isLoadingMore == true && index == logsData!.data.length) { - return const Padding( - padding: EdgeInsets.symmetric(vertical: 20), - child: Center( - child: CircularProgressIndicator(), - ), - ); - } - else { - return LogTile( - log: logsData!.data[index], - index: index, - length: logsData!.data.length, - useAlwaysNormalTile: true, - onLogTap: (log) => { - if (width > 700) { - showDialog( - context: context, - builder: (context) => LogDetailsScreen( - log: log, - dialog: true + if (logsData!.data.isNotEmpty) { + return RefreshIndicator( + onRefresh: fetchLogs, + child: ListView.builder( + controller: scrollController, + padding: const EdgeInsets.only(top: 0), + itemCount: isLoadingMore == true + ? logsData!.data.length+1 + : logsData!.data.length, + itemBuilder: (context, index) { + if (isLoadingMore == true && index == logsData!.data.length) { + return const Padding( + padding: EdgeInsets.symmetric(vertical: 20), + child: Center( + child: CircularProgressIndicator(), + ), + ); + } + else { + return LogTile( + log: logsData!.data[index], + index: index, + length: logsData!.data.length, + useAlwaysNormalTile: true, + onLogTap: (log) => { + if (width > 700) { + showDialog( + context: context, + builder: (context) => LogDetailsScreen( + log: log, + dialog: true + ) ) - ) + } + else { + Navigator.push(context, MaterialPageRoute( + builder: (context) => LogDetailsScreen( + log: log, + dialog: false + ) + )) + } } - else { - Navigator.push(context, MaterialPageRoute( - builder: (context) => LogDetailsScreen( - log: log, - dialog: false - ) - )) - } - } - ); + ); + } } - } - ), - ); + ), + ); + } + else { + return Center( + child: Text( + AppLocalizations.of(context)!.noLogsDisplay, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + ); + } case 2: return SizedBox( diff --git a/lib/screens/filters/filters_triple_column.dart b/lib/screens/filters/filters_triple_column.dart index 3e82fc5..337a1e8 100644 --- a/lib/screens/filters/filters_triple_column.dart +++ b/lib/screens/filters/filters_triple_column.dart @@ -1,13 +1,17 @@ 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:contextmenu/contextmenu.dart'; import 'package:provider/provider.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.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/options_modal.dart'; import 'package:adguard_home_manager/constants/enums.dart'; +import 'package:adguard_home_manager/models/menu_option.dart'; +import 'package:adguard_home_manager/functions/copy_clipboard.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'; @@ -235,20 +239,44 @@ class FiltersTripleColumn extends StatelessWidget { 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, + itemBuilder: (context, index) => ContextMenuArea( + builder: (context) => [ + CustomListTile( + title: AppLocalizations.of(context)!.copyClipboard, + icon: Icons.copy_rounded, + onTap: () { + copyToClipboard( + context: context, + value: serversProvider.filtering.data!.userRules[index], + successMessage: AppLocalizations.of(context)!.copiedClipboard, + ); + Navigator.pop(context); + } + ), + ], + child: CustomListTile( + onLongPress: () => showDialog( + context: context, + builder: (context) => OptionsModal( + options: [ + MenuOption( + title: AppLocalizations.of(context)!.copyClipboard, + icon: Icons.copy_rounded, + action: () => copyToClipboard( + context: context, + value: serversProvider.filtering.data!.userRules[index], + successMessage: AppLocalizations.of(context)!.copiedClipboard, + ) + ) + ] + ) + ), + title: serversProvider.filtering.data!.userRules[index], + subtitleWidget: generateSubtitle(serversProvider.filtering.data!.userRules[index]), + trailing: IconButton( + onPressed: () => onRemoveCustomRule(serversProvider.filtering.data!.userRules[index]), + icon: const Icon(Icons.delete) ), - ), - subtitle: generateSubtitle(serversProvider.filtering.data!.userRules[index]), - trailing: IconButton( - onPressed: () => onRemoveCustomRule(serversProvider.filtering.data!.userRules[index]), - icon: const Icon(Icons.delete) ), ), ), diff --git a/lib/screens/home/home.dart b/lib/screens/home/home.dart index 541f732..20eff68 100644 --- a/lib/screens/home/home.dart +++ b/lib/screens/home/home.dart @@ -216,9 +216,9 @@ class _HomeState extends State { maxWidth: 500 ), child: TopItems( - label: AppLocalizations.of(context)!.topBlockedDomains, - data: serversProvider.serverStatus.data!.stats.topBlockedDomains, - type: 'topBlockedDomains', + label: AppLocalizations.of(context)!.topClients, + data: serversProvider.serverStatus.data!.stats.topClients, + type: 'topClients', ), ), ), diff --git a/lib/screens/home/top_items.dart b/lib/screens/home/top_items.dart index 0bf85d2..e956409 100644 --- a/lib/screens/home/top_items.dart +++ b/lib/screens/home/top_items.dart @@ -2,17 +2,18 @@ import 'dart:io'; -import 'package:adguard_home_manager/screens/top_items/top_items_modal.dart'; -import 'package:animations/animations.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:adguard_home_manager/screens/home/top_items_options_modal.dart'; +import 'package:adguard_home_manager/widgets/domain_options.dart'; +import 'package:adguard_home_manager/screens/top_items/top_items_modal.dart'; +import 'package:adguard_home_manager/widgets/options_modal.dart'; import 'package:adguard_home_manager/screens/top_items/top_items.dart'; import 'package:adguard_home_manager/models/applied_filters.dart'; +import 'package:adguard_home_manager/models/menu_option.dart'; import 'package:adguard_home_manager/providers/logs_provider.dart'; import 'package:adguard_home_manager/classes/process_modal.dart'; import 'package:adguard_home_manager/models/filtering_status.dart'; @@ -119,14 +120,32 @@ class TopItems extends StatelessWidget { ); } + List generateOptions(String domain) { + final isBlocked = getIsBlocked(); + return [ + if (isBlocked == true) MenuOption( + title: AppLocalizations.of(context)!.unblock, + icon: Icons.check, + action: () => blockUnblock(domain, 'unblock') + ), + if (isBlocked == false) MenuOption( + title: AppLocalizations.of(context)!.block, + icon: Icons.check, + action: () => blockUnblock(domain, 'block') + ), + MenuOption( + title: AppLocalizations.of(context)!.copyClipboard, + icon: Icons.check, + action: () => copyDomainClipboard(domain) + ), + ]; + } + void openOptionsModal(String domain, String type) { showDialog( context: context, - builder: (context) => TopItemsOptionsModal( - isBlocked: getIsBlocked(), - changeStatus: (String status) => blockUnblock(domain, status), - copyToClipboard: () => copyDomainClipboard(domain), - type: type, + builder: (context) => OptionsModal( + options: generateOptions(domain), ) ); } @@ -143,7 +162,10 @@ class TopItems extends StatelessWidget { return Material( color: Colors.transparent, - child: InkWell( + child: DomainOptions( + item: item.keys.toList()[0], + isClient: type == 'topClients', + isBlocked: type == 'topBlockedDomains', onTap: () { if (type == 'topQueriedDomains' || type == 'topBlockedDomains') { logsProvider.setSearchText(item.keys.toList()[0]); @@ -167,10 +189,8 @@ class TopItems extends StatelessWidget { clients: [item.keys.toList()[0]] ) ); - appConfigProvider.setSelectedScreen(2); } }, - onLongPress: () => openOptionsModal(item.keys.toList()[0], type), child: Padding( padding: const EdgeInsets.symmetric( horizontal: 20, diff --git a/lib/screens/home/top_items_options_modal.dart b/lib/screens/home/top_items_options_modal.dart deleted file mode 100644 index 18c8f9e..0000000 --- a/lib/screens/home/top_items_options_modal.dart +++ /dev/null @@ -1,84 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; - -import 'package:adguard_home_manager/widgets/custom_list_tile_dialog.dart'; - -class TopItemsOptionsModal extends StatelessWidget { - final bool? isBlocked; - final void Function(String status)? changeStatus; - final void Function() copyToClipboard; - final String type; - - const TopItemsOptionsModal({ - Key? key, - this.isBlocked, - this.changeStatus, - required this.copyToClipboard, - required this.type - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return AlertDialog( - contentPadding: const EdgeInsets.symmetric(vertical: 16), - title: Column( - children: [ - Icon( - Icons.more_horiz, - size: 24, - color: Theme.of(context).listTileTheme.iconColor - ), - const SizedBox(height: 16), - Text( - AppLocalizations.of(context)!.options, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurface - ), - ) - ], - ), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - if (type == 'topQueriedDomains' || type == 'topBlockedDomains') ...[ - if (isBlocked == true && changeStatus != null) CustomListTileDialog( - title: AppLocalizations.of(context)!.unblock, - icon: Icons.check, - onTap: () { - Navigator.pop(context); - changeStatus!('unblock'); - }, - ), - if (isBlocked == false && changeStatus != null) CustomListTileDialog( - title: AppLocalizations.of(context)!.block, - icon: Icons.block, - onTap: () { - Navigator.pop(context); - changeStatus!('block'); - }, - ), - ], - CustomListTileDialog( - title: AppLocalizations.of(context)!.copyClipboard, - icon: Icons.copy, - onTap: () { - Navigator.pop(context); - copyToClipboard(); - } - ), - ], - ), - actions: [ - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(AppLocalizations.of(context)!.cancel) - ) - ], - ) - ], - ); - } -} \ No newline at end of file diff --git a/lib/screens/logs/log_tile.dart b/lib/screens/logs/log_tile.dart index a4c082f..a3c25f9 100644 --- a/lib/screens/logs/log_tile.dart +++ b/lib/screens/logs/log_tile.dart @@ -2,15 +2,11 @@ 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/home/top_items_options_modal.dart'; +import 'package:adguard_home_manager/widgets/domain_options.dart'; -import 'package:adguard_home_manager/functions/copy_clipboard.dart'; -import 'package:adguard_home_manager/functions/block_unblock_domain.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/functions/get_filtered_status.dart'; -import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/models/logs.dart'; import 'package:adguard_home_manager/functions/format_time.dart'; @@ -75,42 +71,16 @@ class LogTile extends StatelessWidget { ); } - void changeBlockStatus(String status) async { - final result = await blockUnblock(context, log.question.name, status); - showSnacbkar( - context: context, - appConfigProvider: appConfigProvider, - label: result['message'], - color: result['success'] == true ? Colors.green : Colors.red - ); - } - - void openOptionsModal(Log log) { - showDialog( - context: context, - builder: (context) => TopItemsOptionsModal( - isBlocked: getFilteredStatus(context, appConfigProvider, log.reason, false)['color'] == Colors.red - ? true : false, - changeStatus: changeBlockStatus, - copyToClipboard: () => copyToClipboard( - context: context, - value: log.question.name, - successMessage: AppLocalizations.of(context)!.domainCopiedClipboard - ), - type: 'topQueriedDomains', // topQueriedDomains can also be used here. It's the same - ) - ); - } - if (width > 1100 && !(useAlwaysNormalTile == true)) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 12), - child: Material( - color: Colors.transparent, + child: InkWell( borderRadius: BorderRadius.circular(28), - child: InkWell( - borderRadius: BorderRadius.circular(28), + child: DomainOptions( onTap: () => onLogTap(log), + borderRadius: BorderRadius.circular(28), + item: log.question.name, + isBlocked: isDomainBlocked(log.reason), child: Container( width: double.maxFinite, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), @@ -291,9 +261,10 @@ class LogTile extends StatelessWidget { else { return Material( color: Colors.transparent, - child: InkWell( + child: DomainOptions( onTap: () => onLogTap(log), - onLongPress: () => openOptionsModal(log), + item: log.question.name, + isBlocked: isDomainBlocked(log.reason), child: Container( width: double.maxFinite, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), diff --git a/lib/screens/top_items/top_items.dart b/lib/screens/top_items/top_items.dart index efee2af..8f7fd9e 100644 --- a/lib/screens/top_items/top_items.dart +++ b/lib/screens/top_items/top_items.dart @@ -7,11 +7,10 @@ import 'package:percent_indicator/percent_indicator.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:adguard_home_manager/screens/home/top_items_options_modal.dart'; +import 'package:adguard_home_manager/widgets/domain_options.dart'; import 'package:adguard_home_manager/widgets/custom_list_tile.dart'; import 'package:adguard_home_manager/models/applied_filters.dart'; -import 'package:adguard_home_manager/functions/copy_clipboard.dart'; import 'package:adguard_home_manager/providers/logs_provider.dart'; import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/functions/number_format.dart'; @@ -67,45 +66,7 @@ class _TopItemsScreenState extends State { for (var element in data) { total = total + int.parse(element.values.toList()[0].toString()); } - - bool? getIsBlocked() { - if (widget.type == 'topBlockedDomains') { - return true; - } - else if (widget.type == 'topQueriedDomains') { - return false; - } - else { - return null; - } - } - - void changeBlockStatus(String status, String domain) async { - final result = await blockUnblock(context, domain, status); - showSnacbkar( - context: context, - appConfigProvider: appConfigProvider, - label: result['message'], - color: result['success'] == true ? Colors.green : Colors.red - ); - } - - void openOptionsModal(String domain, String type) { - showDialog( - context: context, - builder: (context) => TopItemsOptionsModal( - isBlocked: getIsBlocked(), - changeStatus: (String status) => changeBlockStatus(status, domain), - copyToClipboard: () => copyToClipboard( - context: context, - value: domain, - successMessage: AppLocalizations.of(context)!.domainCopiedClipboard - ), - type: type, - ) - ); - } - + return Scaffold( appBar: AppBar( title: searchActive == true @@ -197,7 +158,10 @@ class _TopItemsScreenState extends State { } } - return CustomListTile( + return DomainOptions( + item: screenData[index].keys.toList()[0], + isBlocked: widget.type == 'topBlockedDomains', + isClient: widget.type == 'topClients', onTap: () { if (widget.type == 'topQueriedDomains' || widget.type == 'topBlockedDomains') { logsProvider.setSearchText(screenData[index].keys.toList()[0]); @@ -226,59 +190,57 @@ class _TopItemsScreenState extends State { Navigator.pop(context); } }, - onLongPress: () => openOptionsModal( - screenData[index].keys.toList()[0], - widget.type - ), - title: screenData[index].keys.toList()[0], - trailing: Text( - screenData[index].values.toList()[0].toString(), - style: TextStyle( - color: Theme.of(context).colorScheme.onSurfaceVariant + child: CustomListTile( + title: screenData[index].keys.toList()[0], + trailing: Text( + screenData[index].values.toList()[0].toString(), + style: TextStyle( + color: Theme.of(context).colorScheme.onSurfaceVariant + ), ), - ), - subtitleWidget: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (name != null) ...[ - Text( - name, - style: TextStyle( - fontSize: 14, - color: Theme.of(context).colorScheme.onSurface + subtitleWidget: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (name != null) ...[ + Text( + name, + style: TextStyle( + fontSize: 14, + color: Theme.of(context).colorScheme.onSurface + ), ), - ), - const SizedBox(height: 5), - ], - Row( - children: [ - SizedBox( - width: 50, - child: Text( - "${doubleFormat((screenData[index].values.toList()[0]/total*100), Platform.localeName)}%", - style: TextStyle( - color: Theme.of(context).listTileTheme.textColor + const SizedBox(height: 5), + ], + Row( + children: [ + SizedBox( + width: 50, + child: Text( + "${doubleFormat((screenData[index].values.toList()[0]/total*100), Platform.localeName)}%", + style: TextStyle( + color: Theme.of(context).listTileTheme.textColor + ), ), ), - ), - const SizedBox(width: 10), - Flexible( - child: LinearPercentIndicator( - animation: true, - lineHeight: 4, - animationDuration: 500, - curve: Curves.easeOut, - percent: screenData[index].values.toList()[0]/total, - barRadius: const Radius.circular(5), - progressColor: Theme.of(context).colorScheme.primary, - backgroundColor: Theme.of(context).colorScheme.surfaceVariant, + const SizedBox(width: 10), + Flexible( + child: LinearPercentIndicator( + animation: true, + lineHeight: 4, + animationDuration: 500, + curve: Curves.easeOut, + percent: screenData[index].values.toList()[0]/total, + barRadius: const Radius.circular(5), + progressColor: Theme.of(context).colorScheme.primary, + backgroundColor: Theme.of(context).colorScheme.surfaceVariant, + ), ), - ), - const SizedBox(width: 10), - ], - ), - ], - ) + const SizedBox(width: 10), + ], + ), + ], + ) + ), ); } ) diff --git a/lib/screens/top_items/top_items_modal.dart b/lib/screens/top_items/top_items_modal.dart index 55aae09..d07c5f8 100644 --- a/lib/screens/top_items/top_items_modal.dart +++ b/lib/screens/top_items/top_items_modal.dart @@ -7,11 +7,10 @@ import 'package:percent_indicator/percent_indicator.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:adguard_home_manager/screens/home/top_items_options_modal.dart'; +import 'package:adguard_home_manager/widgets/domain_options.dart'; import 'package:adguard_home_manager/widgets/custom_list_tile.dart'; import 'package:adguard_home_manager/models/applied_filters.dart'; -import 'package:adguard_home_manager/functions/copy_clipboard.dart'; import 'package:adguard_home_manager/providers/logs_provider.dart'; import 'package:adguard_home_manager/functions/snackbar.dart'; import 'package:adguard_home_manager/functions/number_format.dart'; @@ -66,45 +65,7 @@ class _TopItemsModalState extends State { for (var element in data) { total = total + int.parse(element.values.toList()[0].toString()); } - - bool? getIsBlocked() { - if (widget.type == 'topBlockedDomains') { - return true; - } - else if (widget.type == 'topQueriedDomains') { - return false; - } - else { - return null; - } - } - - void changeBlockStatus(String status, String domain) async { - final result = await blockUnblock(context, domain, status); - showSnacbkar( - context: context, - appConfigProvider: appConfigProvider, - label: result['message'], - color: result['success'] == true ? Colors.green : Colors.red - ); - } - - void openOptionsModal(String domain, String type) { - showDialog( - context: context, - builder: (context) => TopItemsOptionsModal( - isBlocked: getIsBlocked(), - changeStatus: (String status) => changeBlockStatus(status, domain), - copyToClipboard: () => copyToClipboard( - context: context, - value: domain, - successMessage: AppLocalizations.of(context)!.domainCopiedClipboard - ), - type: type, - ) - ); - } - + return Dialog( child: ConstrainedBox( constraints: const BoxConstraints( @@ -169,7 +130,10 @@ class _TopItemsModalState extends State { } } - return CustomListTile( + return DomainOptions( + isBlocked: widget.type == 'topBlockedDomains', + isClient: widget.type == 'topClients', + item: screenData[index].keys.toList()[0], onTap: () { if (widget.type == 'topQueriedDomains' || widget.type == 'topBlockedDomains') { logsProvider.setSearchText(screenData[index].keys.toList()[0]); @@ -198,59 +162,57 @@ class _TopItemsModalState extends State { Navigator.pop(context); } }, - onLongPress: () => openOptionsModal( - screenData[index].keys.toList()[0], - widget.type - ), - title: screenData[index].keys.toList()[0], - trailing: Text( - screenData[index].values.toList()[0].toString(), - style: TextStyle( - color: Theme.of(context).colorScheme.onSurfaceVariant + child: CustomListTile( + title: screenData[index].keys.toList()[0], + trailing: Text( + screenData[index].values.toList()[0].toString(), + style: TextStyle( + color: Theme.of(context).colorScheme.onSurfaceVariant + ), ), - ), - subtitleWidget: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (name != null) ...[ - Text( - name, - style: TextStyle( - fontSize: 14, - color: Theme.of(context).colorScheme.onSurface + subtitleWidget: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (name != null) ...[ + Text( + name, + style: TextStyle( + fontSize: 14, + color: Theme.of(context).colorScheme.onSurface + ), ), - ), - const SizedBox(height: 5), - ], - Row( - children: [ - SizedBox( - width: 50, - child: Text( - "${doubleFormat((screenData[index].values.toList()[0]/total*100), Platform.localeName)}%", - style: TextStyle( - color: Theme.of(context).listTileTheme.textColor + const SizedBox(height: 5), + ], + Row( + children: [ + SizedBox( + width: 50, + child: Text( + "${doubleFormat((screenData[index].values.toList()[0]/total*100), Platform.localeName)}%", + style: TextStyle( + color: Theme.of(context).listTileTheme.textColor + ), ), ), - ), - const SizedBox(width: 10), - Flexible( - child: LinearPercentIndicator( - animation: true, - lineHeight: 4, - animationDuration: 500, - curve: Curves.easeOut, - percent: screenData[index].values.toList()[0]/total, - barRadius: const Radius.circular(5), - progressColor: Theme.of(context).colorScheme.primary, - backgroundColor: Theme.of(context).colorScheme.surfaceVariant, + const SizedBox(width: 10), + Flexible( + child: LinearPercentIndicator( + animation: true, + lineHeight: 4, + animationDuration: 500, + curve: Curves.easeOut, + percent: screenData[index].values.toList()[0]/total, + barRadius: const Radius.circular(5), + progressColor: Theme.of(context).colorScheme.primary, + backgroundColor: Theme.of(context).colorScheme.surfaceVariant, + ), ), - ), - const SizedBox(width: 10), - ], - ), - ], - ) + const SizedBox(width: 10), + ], + ), + ], + ) + ), ); } ), diff --git a/lib/widgets/domain_options.dart b/lib/widgets/domain_options.dart new file mode 100644 index 0000000..e250763 --- /dev/null +++ b/lib/widgets/domain_options.dart @@ -0,0 +1,158 @@ +// ignore_for_file: use_build_context_synchronously + +import 'package:flutter/material.dart'; +import 'package:contextmenu/contextmenu.dart'; +import 'package:flutter/services.dart'; +import 'package:provider/provider.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +import 'package:adguard_home_manager/widgets/options_modal.dart'; +import 'package:adguard_home_manager/widgets/custom_list_tile.dart'; + +import 'package:adguard_home_manager/classes/process_modal.dart'; +import 'package:adguard_home_manager/models/filtering_status.dart'; +import 'package:adguard_home_manager/providers/app_config_provider.dart'; +import 'package:adguard_home_manager/providers/servers_provider.dart'; +import 'package:adguard_home_manager/services/http_requests.dart'; +import 'package:adguard_home_manager/models/menu_option.dart'; + +class DomainOptions extends StatelessWidget { + final bool isBlocked; + final bool? isClient; + final String item; + final Widget child; + final void Function() onTap; + final BorderRadius? borderRadius; + + const DomainOptions({ + Key? key, + required this.isBlocked, + this.isClient, + required this.item, + required this.child, + required this.onTap, + this.borderRadius + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final serversProvider = Provider.of(context); + final appConfigProvider = Provider.of(context); + + void blockUnblock(String domain, String newStatus) async { + final ProcessModal processModal = ProcessModal(context: context); + processModal.open(AppLocalizations.of(context)!.savingUserFilters); + + final rules = await getFilteringRules(server: serversProvider.selectedServer!); + + if (rules['result'] == 'success') { + FilteringStatus oldStatus = serversProvider.serverStatus.data!.filteringStatus; + + List newRules = rules['data'].userRules.where((d) => !d.contains(domain)).toList(); + if (newStatus == 'block') { + newRules.add("||$domain^"); + } + else if (newStatus == 'unblock') { + newRules.add("@@||$domain^"); + } + FilteringStatus newObj = serversProvider.serverStatus.data!.filteringStatus; + newObj.userRules = newRules; + serversProvider.setFilteringStatus(newObj); + + final result = await postFilteringRules(server: serversProvider.selectedServer!, data: {'rules': newRules}); + + processModal.close(); + + if (result['result'] == 'success') { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(AppLocalizations.of(context)!.userFilteringRulesUpdated), + backgroundColor: Colors.green, + ) + ); + } + else { + appConfigProvider.addLog(result['log']); + serversProvider.setFilteringStatus(oldStatus); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(AppLocalizations.of(context)!.userFilteringRulesNotUpdated), + backgroundColor: Colors.red, + ) + ); + } + } + else { + appConfigProvider.addLog(rules['log']); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(AppLocalizations.of(context)!.userFilteringRulesNotUpdated), + backgroundColor: Colors.red, + ) + ); + } + } + + void copyDomainClipboard(String domain) async { + await Clipboard.setData( + ClipboardData(text: domain) + ); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(AppLocalizations.of(context)!.domainCopiedClipboard), + backgroundColor: Colors.green, + ) + ); + } + + List generateOptions() { + return [ + if (isClient != true && isBlocked == true) MenuOption( + title: AppLocalizations.of(context)!.unblock, + icon: Icons.check, + action: () => blockUnblock(item, 'unblock') + ), + if (isClient != true && isBlocked == false) MenuOption( + title: AppLocalizations.of(context)!.block, + icon: Icons.block, + action: () => blockUnblock(item, 'block') + ), + MenuOption( + title: AppLocalizations.of(context)!.copyClipboard, + icon: Icons.copy, + action: () => copyDomainClipboard(item) + ), + ]; + } + + void openOptionsModal() { + showDialog( + context: context, + builder: (context) => OptionsModal( + options: generateOptions(), + ) + ); + } + + return Material( + color: Colors.transparent, + borderRadius: borderRadius, + child: ContextMenuArea( + builder: (context) => generateOptions().map((opt) => CustomListTile( + title: opt.title, + icon: opt.icon, + onTap: () { + opt.action(); + Navigator.pop(context); + }, + )).toList(), + child: InkWell( + onTap: onTap, + onLongPress: () => openOptionsModal(), + borderRadius: borderRadius, + child: child, + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/widgets/options_modal.dart b/lib/widgets/options_modal.dart new file mode 100644 index 0000000..4128fb2 --- /dev/null +++ b/lib/widgets/options_modal.dart @@ -0,0 +1,59 @@ +import 'package:adguard_home_manager/models/menu_option.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +import 'package:adguard_home_manager/widgets/custom_list_tile_dialog.dart'; + +class OptionsModal extends StatelessWidget { + final List options; + + const OptionsModal({ + Key? key, + required this.options, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return AlertDialog( + contentPadding: const EdgeInsets.symmetric(vertical: 16), + title: Column( + children: [ + Icon( + Icons.more_horiz, + size: 24, + color: Theme.of(context).listTileTheme.iconColor + ), + const SizedBox(height: 16), + Text( + AppLocalizations.of(context)!.options, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface + ), + ) + ], + ), + content: Column( + mainAxisSize: MainAxisSize.min, + children: options.map((opt) => CustomListTileDialog( + title: opt.title, + icon: opt.icon, + onTap: () { + Navigator.pop(context); + opt.action(); + }, + )).toList() + ), + actions: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(AppLocalizations.of(context)!.cancel) + ) + ], + ) + ], + ); + } +} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 2a10f2b..421a3d4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + after_layout: + dependency: transitive + description: + name: after_layout + sha256: "95a1cb2ca1464f44f14769329fbf15987d20ab6c88f8fc5d359bd362be625f29" + url: "https://pub.dev" + source: hosted + version: "1.2.0" animations: dependency: "direct main" description: @@ -81,6 +89,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.17.0" + contextmenu: + dependency: "direct main" + description: + name: contextmenu + sha256: e0c7d60e2fc9f316f5b03f5fe2c0f977d65125345d1a1f77eea02be612e32d0c + url: "https://pub.dev" + source: hosted + version: "3.0.0" convert: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 900ee58..ab4be31 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -68,6 +68,7 @@ dependencies: url: https://github.com/JGeek00/flutter_split_view ref: master-alt url_launcher: ^6.1.10 + contextmenu: ^3.0.0 dev_dependencies: flutter_test: