Added some context menus on desktop

This commit is contained in:
Juan Gilsanz Polo 2023-05-04 22:38:37 +02:00
parent d628e56248
commit 27ffa75d63
19 changed files with 864 additions and 623 deletions

View file

@ -127,3 +127,42 @@ Map<String, dynamic> getFilteredStatus(BuildContext context, AppConfigProvider a
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;
}
}

View file

@ -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"
}

View file

@ -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"
}

View file

@ -140,7 +140,11 @@ class _MainState extends State<Main> {
],
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!,
);
},

View file

@ -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
});
}

View file

@ -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),
);
}
}

View file

@ -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<AddedClientTile> createState() => _AddedClientTileState();
}
class _AddedClientTileState extends State<AddedClientTile> {
bool hover = false;
@override
Widget build(BuildContext context) {
final serversProvider = Provider.of<ServersProvider>(context);
final appConfigProvider = Provider.of<AppConfigProvider>(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,
);
}
}

View file

@ -144,53 +144,67 @@ class _LogsListClientState extends State<LogsListClient> {
);
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(

View file

@ -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)
),
),
),

View file

@ -216,9 +216,9 @@ class _HomeState extends State<Home> {
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',
),
),
),

View file

@ -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<MenuOption> 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,

View file

@ -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)
)
],
)
],
);
}
}

View file

@ -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),

View file

@ -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';
@ -68,44 +67,6 @@ class _TopItemsScreenState extends State<TopItemsScreen> {
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<TopItemsScreen> {
}
}
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<TopItemsScreen> {
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),
],
),
],
)
),
);
}
)

View file

@ -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,44 +66,6 @@ class _TopItemsModalState extends State<TopItemsModal> {
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<TopItemsModal> {
}
}
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<TopItemsModal> {
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),
],
),
],
)
),
);
}
),

View file

@ -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<ServersProvider>(context);
final appConfigProvider = Provider.of<AppConfigProvider>(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<String> 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<MenuOption> 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,
),
),
);
}
}

View file

@ -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<MenuOption> 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)
)
],
)
],
);
}
}

View file

@ -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:

View file

@ -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: