mirror of
https://github.com/JGeek00/adguard-home-manager.git
synced 2025-05-14 05:52:51 +00:00
Refactor clients
This commit is contained in:
parent
36bd7acfed
commit
adaa75e717
22 changed files with 1009 additions and 851 deletions
166
lib/screens/clients/client/active_client_tile.dart
Normal file
166
lib/screens/clients/client/active_client_tile.dart
Normal file
|
@ -0,0 +1,166 @@
|
|||
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/widgets/options_modal.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/clients.dart';
|
||||
|
||||
class ActiveClientTile extends StatelessWidget {
|
||||
final AutoClient client;
|
||||
final void Function(AutoClient) onTap;
|
||||
final bool splitView;
|
||||
final AutoClient? selectedClient;
|
||||
|
||||
const ActiveClientTile({
|
||||
Key? key,
|
||||
required this.client,
|
||||
required this.onTap,
|
||||
required this.splitView,
|
||||
this.selectedClient
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
void openOptionsModal() {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => OptionsModal(
|
||||
options: [
|
||||
MenuOption(
|
||||
title: AppLocalizations.of(context)!.copyClipboard,
|
||||
icon: Icons.copy_rounded,
|
||||
action: () {
|
||||
copyToClipboard(
|
||||
value: client.name != ''
|
||||
? client.name!
|
||||
: client.ip,
|
||||
successMessage: AppLocalizations.of(context)!.copiedClipboard,
|
||||
);
|
||||
},
|
||||
)
|
||||
]
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (splitView == true) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: ContextMenuArea(
|
||||
builder: (context) => [
|
||||
CustomListTile(
|
||||
title: AppLocalizations.of(context)!.copyClipboard,
|
||||
icon: Icons.copy_rounded,
|
||||
onTap: () {
|
||||
copyToClipboard(
|
||||
value: client.name != ''
|
||||
? client.name!
|
||||
: client.ip,
|
||||
successMessage: AppLocalizations.of(context)!.copiedClipboard,
|
||||
);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
)
|
||||
],
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
onTap: () => onTap(client),
|
||||
onLongPress: () {
|
||||
Navigator.pop(context);
|
||||
openOptionsModal();
|
||||
},
|
||||
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)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Text(
|
||||
client.source,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
else {
|
||||
return ContextMenuArea(
|
||||
builder: (context) => [
|
||||
CustomListTile(
|
||||
title: AppLocalizations.of(context)!.copyClipboard,
|
||||
icon: Icons.copy_rounded,
|
||||
onTap: () {
|
||||
copyToClipboard(
|
||||
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),
|
||||
onLongPress: openOptionsModal,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
291
lib/screens/clients/client/added_client_tile.dart
Normal file
291
lib/screens/clients/client/added_client_tile.dart
Normal file
|
@ -0,0 +1,291 @@
|
|||
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/app_config_provider.dart';
|
||||
|
||||
class AddedClientTile extends StatelessWidget {
|
||||
final Client client;
|
||||
final void Function(Client) onTap;
|
||||
final void Function(Client) onLongPress;
|
||||
final void Function(Client) onEdit;
|
||||
final void Function(Client) onDelete;
|
||||
final Client? selectedClient;
|
||||
final bool? splitView;
|
||||
final String serverVersion;
|
||||
|
||||
const AddedClientTile({
|
||||
Key? key,
|
||||
required this.client,
|
||||
required this.onTap,
|
||||
required this.onLongPress,
|
||||
required this.onEdit,
|
||||
required this.onDelete,
|
||||
this.selectedClient,
|
||||
required this.splitView,
|
||||
required this.serverVersion
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final appConfigProvider = Provider.of<AppConfigProvider>(context);
|
||||
|
||||
if (splitView == true) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
child: ContextMenuArea(
|
||||
builder: (context) => [
|
||||
CustomListTile(
|
||||
title: AppLocalizations.of(context)!.edit,
|
||||
icon: Icons.edit_rounded,
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
onEdit(client);
|
||||
}
|
||||
),
|
||||
CustomListTile(
|
||||
title: AppLocalizations.of(context)!.delete,
|
||||
icon: Icons.delete_rounded,
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
onDelete(client);
|
||||
}
|
||||
),
|
||||
CustomListTile(
|
||||
title: AppLocalizations.of(context)!.copyClipboard,
|
||||
icon: Icons.copy_rounded,
|
||||
onTap: () {
|
||||
copyToClipboard(
|
||||
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: 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: 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,
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
else {
|
||||
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(
|
||||
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: 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: 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,
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
114
lib/screens/clients/client/blocked_services_section.dart
Normal file
114
lib/screens/clients/client/blocked_services_section.dart
Normal file
|
@ -0,0 +1,114 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/screens/clients/client/client_screen_functions.dart';
|
||||
|
||||
class BlockedServicesSection extends StatelessWidget {
|
||||
final bool useGlobalSettingsServices;
|
||||
final List<String> blockedServices;
|
||||
final void Function(List<String>) onUpdatedBlockedServices;
|
||||
final void Function(bool) onUpdateServicesGlobalSettings;
|
||||
|
||||
const BlockedServicesSection({
|
||||
Key? key,
|
||||
required this.useGlobalSettingsServices,
|
||||
required this.blockedServices,
|
||||
required this.onUpdatedBlockedServices,
|
||||
required this.onUpdateServicesGlobalSettings
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Material(
|
||||
color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
child: InkWell(
|
||||
onTap: () => onUpdateServicesGlobalSettings(!useGlobalSettingsServices),
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
vertical: 5
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context)!.useGlobalSettings,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
),
|
||||
Switch(
|
||||
value: useGlobalSettingsServices,
|
||||
onChanged: (value) => onUpdateServicesGlobalSettings(value),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: useGlobalSettingsServices == false
|
||||
? () => openServicesModal(
|
||||
context: context,
|
||||
blockedServices: blockedServices,
|
||||
onUpdateBlockedServices: onUpdatedBlockedServices
|
||||
)
|
||||
: null,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 8, horizontal: 24
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.public,
|
||||
color: useGlobalSettingsServices == false
|
||||
? Theme.of(context).listTileTheme.iconColor
|
||||
: Theme.of(context).colorScheme.onSurface.withOpacity(0.38),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context)!.selectBlockedServices,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: useGlobalSettingsServices == false
|
||||
? Theme.of(context).colorScheme.onSurface
|
||||
: Theme.of(context).colorScheme.onSurface.withOpacity(0.38),
|
||||
),
|
||||
),
|
||||
if (useGlobalSettingsServices == false) ...[
|
||||
const SizedBox(height: 5),
|
||||
Text(
|
||||
blockedServices.isNotEmpty
|
||||
? "${blockedServices.length} ${AppLocalizations.of(context)!.servicesBlocked}"
|
||||
: AppLocalizations.of(context)!.noBlockedServicesSelected,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).listTileTheme.iconColor
|
||||
),
|
||||
)
|
||||
]
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
442
lib/screens/clients/client/client_screen.dart
Normal file
442
lib/screens/clients/client/client_screen.dart
Normal file
|
@ -0,0 +1,442 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/screens/clients/client/upstream_servers_section.dart';
|
||||
import 'package:adguard_home_manager/screens/clients/client/identifiers_section.dart';
|
||||
import 'package:adguard_home_manager/screens/clients/client/blocked_services_section.dart';
|
||||
import 'package:adguard_home_manager/screens/clients/client/tags_section.dart';
|
||||
import 'package:adguard_home_manager/screens/clients/client/settings_tile.dart';
|
||||
import 'package:adguard_home_manager/screens/clients/client/client_screen_functions.dart';
|
||||
import 'package:adguard_home_manager/widgets/section_label.dart';
|
||||
import 'package:adguard_home_manager/widgets/custom_list_tile.dart';
|
||||
|
||||
import 'package:adguard_home_manager/functions/compare_versions.dart';
|
||||
import 'package:adguard_home_manager/models/safe_search.dart';
|
||||
import 'package:adguard_home_manager/providers/clients_provider.dart';
|
||||
import 'package:adguard_home_manager/providers/status_provider.dart';
|
||||
import 'package:adguard_home_manager/models/clients.dart';
|
||||
|
||||
class ClientScreen extends StatefulWidget {
|
||||
final Client? client;
|
||||
final String serverVersion;
|
||||
final void Function(Client) onConfirm;
|
||||
final void Function(Client)? onDelete;
|
||||
final bool dialog;
|
||||
|
||||
const ClientScreen({
|
||||
Key? key,
|
||||
this.client,
|
||||
required this.serverVersion,
|
||||
required this.onConfirm,
|
||||
this.onDelete,
|
||||
required this.dialog
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<ClientScreen> createState() => _ClientScreenState();
|
||||
}
|
||||
|
||||
class _ClientScreenState extends State<ClientScreen> {
|
||||
final Uuid uuid = const Uuid();
|
||||
|
||||
bool validValues = false;
|
||||
|
||||
TextEditingController nameController = TextEditingController();
|
||||
|
||||
List<String> selectedTags = [];
|
||||
|
||||
List<Map<dynamic, dynamic>> identifiersControllers = [
|
||||
{
|
||||
'id': 0,
|
||||
'controller': TextEditingController()
|
||||
}
|
||||
];
|
||||
|
||||
bool useGlobalSettingsFiltering = true;
|
||||
bool? enableFiltering;
|
||||
bool? enableSafeBrowsing;
|
||||
bool? enableParentalControl;
|
||||
bool? enableSafeSearch;
|
||||
SafeSearch? safeSearch;
|
||||
|
||||
final SafeSearch defaultSafeSearch = SafeSearch(
|
||||
enabled: false,
|
||||
bing: false,
|
||||
duckduckgo: false,
|
||||
google: false,
|
||||
pixabay: false,
|
||||
yandex: false,
|
||||
youtube: false
|
||||
);
|
||||
|
||||
bool useGlobalSettingsServices = true;
|
||||
List<String> blockedServices = [];
|
||||
|
||||
List<Map<dynamic, dynamic>> upstreamServers = [];
|
||||
|
||||
bool version = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
version = serverVersionIsAhead(
|
||||
currentVersion: widget.serverVersion,
|
||||
referenceVersion: 'v0.107.28',
|
||||
referenceVersionBeta: 'v0.108.0-b.33'
|
||||
);
|
||||
|
||||
if (widget.client != null) {
|
||||
validValues = true;
|
||||
|
||||
nameController.text = widget.client!.name;
|
||||
selectedTags = widget.client!.tags;
|
||||
identifiersControllers = widget.client!.ids.map((e) => {
|
||||
'id': uuid.v4(),
|
||||
'controller': TextEditingController(text: e)
|
||||
}).toList();
|
||||
useGlobalSettingsFiltering = widget.client!.useGlobalSettings;
|
||||
enableFiltering = widget.client!.filteringEnabled;
|
||||
enableParentalControl = widget.client!.parentalEnabled;
|
||||
enableSafeBrowsing = widget.client!.safebrowsingEnabled;
|
||||
if (version == true) {
|
||||
safeSearch = widget.client!.safeSearch;
|
||||
}
|
||||
else {
|
||||
enableSafeSearch = widget.client!.safesearchEnabled ?? false;
|
||||
}
|
||||
useGlobalSettingsServices = widget.client!.useGlobalBlockedServices;
|
||||
blockedServices = widget.client!.blockedServices;
|
||||
upstreamServers = widget.client!.upstreams.map((e) => {
|
||||
'id': uuid.v4(),
|
||||
'controller': TextEditingController(text: e)
|
||||
}).toList();
|
||||
}
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final clientsProvider = Provider.of<ClientsProvider>(context);
|
||||
final statusProvider = Provider.of<StatusProvider>(context);
|
||||
|
||||
void createClient() {
|
||||
final Client client = Client(
|
||||
name: nameController.text,
|
||||
ids: List<String>.from(identifiersControllers.map((e) => e['controller'].text)),
|
||||
useGlobalSettings: useGlobalSettingsFiltering,
|
||||
filteringEnabled: enableFiltering ?? false,
|
||||
parentalEnabled: enableParentalControl ?? false,
|
||||
safebrowsingEnabled: enableSafeBrowsing ?? false,
|
||||
safesearchEnabled: version == false ? enableSafeSearch : null,
|
||||
safeSearch: version == true ? safeSearch : null,
|
||||
useGlobalBlockedServices: useGlobalSettingsServices,
|
||||
blockedServices: blockedServices,
|
||||
upstreams: List<String>.from(upstreamServers.map((e) => e['controller'].text)),
|
||||
tags: selectedTags
|
||||
);
|
||||
widget.onConfirm(client);
|
||||
}
|
||||
|
||||
void enableDisableGlobalSettingsFiltering() {
|
||||
if (useGlobalSettingsFiltering == true) {
|
||||
setState(() {
|
||||
useGlobalSettingsFiltering = false;
|
||||
|
||||
enableFiltering = false;
|
||||
enableSafeBrowsing = false;
|
||||
enableParentalControl = false;
|
||||
enableSafeSearch = false;
|
||||
safeSearch = defaultSafeSearch;
|
||||
});
|
||||
}
|
||||
else if (useGlobalSettingsFiltering == false) {
|
||||
setState(() {
|
||||
useGlobalSettingsFiltering = true;
|
||||
|
||||
enableFiltering = null;
|
||||
enableSafeBrowsing = null;
|
||||
enableParentalControl = null;
|
||||
enableSafeSearch = null;
|
||||
safeSearch = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void updateServicesGlobalSettings(bool value) {
|
||||
if (value == true) {
|
||||
setState(() {
|
||||
blockedServices = [];
|
||||
useGlobalSettingsServices = true;
|
||||
});
|
||||
}
|
||||
else if (value == false) {
|
||||
setState(() {
|
||||
useGlobalSettingsServices = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
List<Widget> actions() {
|
||||
return [
|
||||
IconButton(
|
||||
onPressed: validValues == true
|
||||
? () {
|
||||
createClient();
|
||||
Navigator.pop(context);
|
||||
}
|
||||
: null,
|
||||
icon: const Icon(Icons.save_rounded),
|
||||
tooltip: AppLocalizations.of(context)!.save,
|
||||
),
|
||||
if (widget.client != null) IconButton(
|
||||
onPressed: () => openDeleteClientScreen(
|
||||
context: context,
|
||||
onDelete: () => clientsProvider.deleteClient(widget.client!),
|
||||
),
|
||||
icon: const Icon(Icons.delete_rounded),
|
||||
tooltip: AppLocalizations.of(context)!.delete,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
];
|
||||
}
|
||||
|
||||
Widget content(bool withPaddingTop) {
|
||||
return ListView(
|
||||
padding: const EdgeInsets.only(top: 0),
|
||||
children: [
|
||||
if (withPaddingTop == true) const SizedBox(height: 24),
|
||||
if (withPaddingTop == false) const SizedBox(height: 6),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: TextFormField(
|
||||
enabled: widget.client != null ? false : true,
|
||||
controller: nameController,
|
||||
onChanged: (_) => setState(() {
|
||||
validValues = checkValidValues(
|
||||
identifiersControllers: identifiersControllers,
|
||||
nameController: nameController
|
||||
);
|
||||
}),
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.badge_rounded),
|
||||
border: const OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(10)
|
||||
)
|
||||
),
|
||||
labelText: AppLocalizations.of(context)!.name,
|
||||
),
|
||||
),
|
||||
),
|
||||
SectionLabel(
|
||||
label: AppLocalizations.of(context)!.tags,
|
||||
padding: const EdgeInsets.all(24),
|
||||
),
|
||||
TagsSection(
|
||||
selectedTags: selectedTags,
|
||||
onTagsSelected: (tags) => setState(() => selectedTags = tags)
|
||||
),
|
||||
IdentifiersSection(
|
||||
identifiersControllers: identifiersControllers,
|
||||
onUpdateIdentifiersControllers: (c) => setState(() {
|
||||
identifiersControllers = c;
|
||||
validValues = checkValidValues(
|
||||
nameController: nameController,
|
||||
identifiersControllers: identifiersControllers
|
||||
);
|
||||
}),
|
||||
onCheckValidValues: () => setState(() {
|
||||
validValues = checkValidValues(
|
||||
identifiersControllers: identifiersControllers,
|
||||
nameController: nameController
|
||||
);
|
||||
}),
|
||||
),
|
||||
SectionLabel(
|
||||
label: AppLocalizations.of(context)!.settings,
|
||||
padding: const EdgeInsets.only(
|
||||
left: 24, right: 24, top: 12, bottom: 24
|
||||
)
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Material(
|
||||
color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
child: InkWell(
|
||||
onTap: () => enableDisableGlobalSettingsFiltering(),
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
vertical: 5
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context)!.useGlobalSettings,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
),
|
||||
Switch(
|
||||
value: useGlobalSettingsFiltering,
|
||||
onChanged: (value) => enableDisableGlobalSettingsFiltering()
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
SettingsTile(
|
||||
label: AppLocalizations.of(context)!.enableFiltering,
|
||||
value: enableFiltering,
|
||||
onChange: (value) => setState(() => enableFiltering = value),
|
||||
useGlobalSettingsFiltering: useGlobalSettingsFiltering,
|
||||
),
|
||||
SettingsTile(
|
||||
label: AppLocalizations.of(context)!.enableSafeBrowsing,
|
||||
value: enableSafeBrowsing,
|
||||
onChange: (value) => setState(() => enableSafeBrowsing = value),
|
||||
useGlobalSettingsFiltering: useGlobalSettingsFiltering,
|
||||
),
|
||||
SettingsTile(
|
||||
label: AppLocalizations.of(context)!.enableParentalControl,
|
||||
value: enableParentalControl,
|
||||
onChange: (value) => setState(() => enableParentalControl = value),
|
||||
useGlobalSettingsFiltering: useGlobalSettingsFiltering,
|
||||
),
|
||||
if (
|
||||
serverVersionIsAhead(
|
||||
currentVersion: statusProvider.serverStatus!.serverVersion,
|
||||
referenceVersion: 'v0.107.28',
|
||||
referenceVersionBeta: 'v0.108.0-b.33'
|
||||
) == true
|
||||
) CustomListTile(
|
||||
title: AppLocalizations.of(context)!.safeSearch,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 42,
|
||||
vertical: 16
|
||||
),
|
||||
trailing: Padding(
|
||||
padding: const EdgeInsets.only(right: 16),
|
||||
child: Icon(
|
||||
Icons.chevron_right_rounded,
|
||||
color: useGlobalSettingsFiltering == true
|
||||
? Colors.grey
|
||||
: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
onTap: useGlobalSettingsFiltering == false
|
||||
? () => openSafeSearchModal(
|
||||
context: context,
|
||||
blockedServices: blockedServices,
|
||||
defaultSafeSearch: defaultSafeSearch,
|
||||
safeSearch: safeSearch,
|
||||
onUpdateSafeSearch: (s) => setState(() => safeSearch = s)
|
||||
)
|
||||
: null,
|
||||
),
|
||||
if (
|
||||
serverVersionIsAhead(
|
||||
currentVersion: statusProvider.serverStatus!.serverVersion,
|
||||
referenceVersion: 'v0.107.28',
|
||||
referenceVersionBeta: 'v0.108.0-b.33'
|
||||
) == false
|
||||
) SettingsTile(
|
||||
label: AppLocalizations.of(context)!.enableSafeSearch,
|
||||
value: enableSafeSearch,
|
||||
onChange: (value) => setState(() => enableSafeSearch = value),
|
||||
useGlobalSettingsFiltering: useGlobalSettingsFiltering,
|
||||
),
|
||||
SectionLabel(
|
||||
label: AppLocalizations.of(context)!.blockedServices,
|
||||
padding: const EdgeInsets.all(24),
|
||||
),
|
||||
BlockedServicesSection(
|
||||
useGlobalSettingsServices: useGlobalSettingsServices,
|
||||
blockedServices: blockedServices,
|
||||
onUpdatedBlockedServices: (s) => setState(() => blockedServices = s),
|
||||
onUpdateServicesGlobalSettings: (v) => setState(() => useGlobalSettingsServices = v),
|
||||
),
|
||||
UpstreamServersSection(
|
||||
upstreamServers: upstreamServers,
|
||||
onCheckValidValues: () => setState(() {
|
||||
validValues = checkValidValues(
|
||||
identifiersControllers: identifiersControllers,
|
||||
nameController: nameController
|
||||
);
|
||||
}),
|
||||
onUpdateUpstreamServers: (v) => setState(() => upstreamServers = v)
|
||||
),
|
||||
const SizedBox(height: 20)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
if (widget.dialog == true) {
|
||||
return Dialog(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 500
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
CloseButton(onPressed: () => Navigator.pop(context)),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
widget.client != null
|
||||
? AppLocalizations.of(context)!.client
|
||||
: AppLocalizations.of(context)!.addClient,
|
||||
style: const TextStyle(
|
||||
fontSize: 22
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: actions()
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: content(false)
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
else {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
leading: IconButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
icon: const Icon(Icons.close)
|
||||
),
|
||||
title: Text(
|
||||
widget.client != null
|
||||
? AppLocalizations.of(context)!.client
|
||||
: AppLocalizations.of(context)!.addClient
|
||||
),
|
||||
actions: actions(),
|
||||
),
|
||||
body: content(true)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
87
lib/screens/clients/client/client_screen_functions.dart
Normal file
87
lib/screens/clients/client/client_screen_functions.dart
Normal file
|
@ -0,0 +1,87 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:adguard_home_manager/screens/clients/client/remove_client_modal.dart';
|
||||
import 'package:adguard_home_manager/screens/clients/client/safe_search_modal.dart';
|
||||
import 'package:adguard_home_manager/screens/clients/client/services_modal.dart';
|
||||
import 'package:adguard_home_manager/screens/clients/client/tags_modal.dart';
|
||||
|
||||
import 'package:adguard_home_manager/providers/clients_provider.dart';
|
||||
import 'package:adguard_home_manager/models/safe_search.dart';
|
||||
|
||||
void openTagsModal({
|
||||
required BuildContext context,
|
||||
required List<String> selectedTags,
|
||||
required void Function(List<String>) onSelectedTags
|
||||
}) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => TagsModal(
|
||||
selectedTags: selectedTags,
|
||||
tags: Provider.of<ClientsProvider>(context, listen: false).clients!.supportedTags,
|
||||
onConfirm: onSelectedTags,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void openServicesModal({
|
||||
required BuildContext context,
|
||||
required List<String> blockedServices,
|
||||
required void Function(List<String>) onUpdateBlockedServices
|
||||
}) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => ServicesModal(
|
||||
blockedServices: blockedServices,
|
||||
onConfirm: onUpdateBlockedServices,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void openDeleteClientScreen({
|
||||
required BuildContext context,
|
||||
required void Function() onDelete
|
||||
}) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => RemoveClientModal(
|
||||
onConfirm: () {
|
||||
Navigator.pop(context);
|
||||
onDelete();
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void openSafeSearchModal({
|
||||
required BuildContext context,
|
||||
required List<String> blockedServices,
|
||||
required void Function(SafeSearch) onUpdateSafeSearch,
|
||||
required SafeSearch? safeSearch,
|
||||
required SafeSearch defaultSafeSearch
|
||||
}) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => SafeSearchModal(
|
||||
safeSearch: safeSearch ?? defaultSafeSearch,
|
||||
disabled: false,
|
||||
onConfirm: onUpdateSafeSearch
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
bool checkValidValues({
|
||||
required TextEditingController nameController,
|
||||
required List<Map<dynamic, dynamic>> identifiersControllers
|
||||
}) {
|
||||
if (
|
||||
nameController.text != '' &&
|
||||
identifiersControllers.isNotEmpty &&
|
||||
identifiersControllers[0]['controller']!.text != ''
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
103
lib/screens/clients/client/identifiers_section.dart
Normal file
103
lib/screens/clients/client/identifiers_section.dart
Normal file
|
@ -0,0 +1,103 @@
|
|||
import 'package:adguard_home_manager/screens/clients/client/client_screen_functions.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/widgets/section_label.dart';
|
||||
|
||||
class IdentifiersSection extends StatefulWidget {
|
||||
final List<Map<dynamic, dynamic>> identifiersControllers;
|
||||
final void Function(List<Map<dynamic, dynamic>>) onUpdateIdentifiersControllers;
|
||||
final void Function() onCheckValidValues;
|
||||
|
||||
const IdentifiersSection({
|
||||
Key? key,
|
||||
required this.identifiersControllers,
|
||||
required this.onUpdateIdentifiersControllers,
|
||||
required this.onCheckValidValues
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<IdentifiersSection> createState() => _IdentifiersSectionState();
|
||||
}
|
||||
|
||||
class _IdentifiersSectionState extends State<IdentifiersSection> {
|
||||
final Uuid uuid = const Uuid();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
SectionLabel(
|
||||
label: AppLocalizations.of(context)!.identifiers,
|
||||
padding: const EdgeInsets.only(
|
||||
left: 24, right: 24, top: 24, bottom: 12
|
||||
)
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 20),
|
||||
child: IconButton(
|
||||
onPressed: () => widget.onUpdateIdentifiersControllers([
|
||||
...widget.identifiersControllers,
|
||||
Map<String, Object>.from({
|
||||
'id': uuid.v4(),
|
||||
'controller': TextEditingController()
|
||||
})
|
||||
]),
|
||||
icon: const Icon(Icons.add)
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
if (widget.identifiersControllers.isNotEmpty) ...widget.identifiersControllers.map((controller) => Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
controller: controller['controller'],
|
||||
onChanged: (_) => widget.onCheckValidValues(),
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.tag),
|
||||
border: const OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(10)
|
||||
)
|
||||
),
|
||||
helperText: AppLocalizations.of(context)!.identifierHelper,
|
||||
labelText: AppLocalizations.of(context)!.identifier,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 25),
|
||||
child: IconButton(
|
||||
onPressed: () => widget.onUpdateIdentifiersControllers(
|
||||
widget.identifiersControllers.where((e) => e['id'] != controller['id']).toList()
|
||||
),
|
||||
icon: const Icon(Icons.remove_circle_outline_outlined)
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)).toList(),
|
||||
if (widget.identifiersControllers.isEmpty) Container(
|
||||
padding: const EdgeInsets.only(top: 10),
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.noIdentifiers,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
268
lib/screens/clients/client/logs_list_client.dart
Normal file
268
lib/screens/clients/client/logs_list_client.dart
Normal file
|
@ -0,0 +1,268 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:async/async.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/screens/logs/log_tile.dart';
|
||||
import 'package:adguard_home_manager/screens/logs/log_details_screen.dart';
|
||||
|
||||
import 'package:adguard_home_manager/models/logs.dart';
|
||||
import 'package:adguard_home_manager/providers/app_config_provider.dart';
|
||||
import 'package:adguard_home_manager/providers/servers_provider.dart';
|
||||
|
||||
class LogsListClient extends StatefulWidget {
|
||||
final String ip;
|
||||
final String? name;
|
||||
final ServersProvider serversProvider;
|
||||
final AppConfigProvider appConfigProvider;
|
||||
|
||||
const LogsListClient({
|
||||
Key? key,
|
||||
required this.ip,
|
||||
this.name,
|
||||
required this.serversProvider,
|
||||
required this.appConfigProvider
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<LogsListClient> createState() => _LogsListClientState();
|
||||
}
|
||||
|
||||
class _LogsListClientState extends State<LogsListClient> {
|
||||
late ScrollController scrollController;
|
||||
|
||||
bool isLoadingMore = false;
|
||||
|
||||
int logsQuantity = 100;
|
||||
int offset = 0;
|
||||
|
||||
int loadStatus = 0;
|
||||
LogsData? logsData;
|
||||
|
||||
String previousIp = "";
|
||||
|
||||
bool showDivider = true;
|
||||
|
||||
CancelableOperation? cancelableRequest;
|
||||
|
||||
Future fetchLogs({
|
||||
int? inOffset,
|
||||
bool? loadingMore,
|
||||
String? responseStatus,
|
||||
String? searchText,
|
||||
}) async {
|
||||
final serversProvider = Provider.of<ServersProvider>(context, listen: false);
|
||||
|
||||
int offst = inOffset ?? offset;
|
||||
|
||||
if (loadingMore != null && loadingMore == true) {
|
||||
setState(() => isLoadingMore = true);
|
||||
}
|
||||
|
||||
if (cancelableRequest != null) cancelableRequest!.cancel();
|
||||
|
||||
cancelableRequest = CancelableOperation.fromFuture(
|
||||
serversProvider.apiClient!.getLogs(
|
||||
count: logsQuantity,
|
||||
offset: offst,
|
||||
search: '"${widget.ip}"'
|
||||
)
|
||||
);
|
||||
|
||||
final result = await cancelableRequest?.value;
|
||||
|
||||
if (result != null) {
|
||||
if (loadingMore != null && loadingMore == true && mounted) {
|
||||
setState(() => isLoadingMore = false);
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
if (result['result'] == 'success') {
|
||||
setState(() => offset = inOffset != null ? inOffset+logsQuantity : offset+logsQuantity);
|
||||
if (loadingMore != null && loadingMore == true && logsData != null) {
|
||||
LogsData newLogsData = result['data'];
|
||||
newLogsData.data = [...logsData!.data, ...result['data'].data];
|
||||
setState(() => logsData = newLogsData);
|
||||
}
|
||||
else {
|
||||
LogsData newLogsData = result['data'];
|
||||
setState(() => logsData = newLogsData);
|
||||
}
|
||||
setState(() => loadStatus = 1);
|
||||
}
|
||||
else {
|
||||
setState(() => loadStatus = 2);
|
||||
widget.appConfigProvider.addLog(result['log']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void scrollListener() {
|
||||
if (scrollController.position.extentAfter < 500 && isLoadingMore == false) {
|
||||
fetchLogs(loadingMore: true);
|
||||
}
|
||||
if (scrollController.position.pixels > 0) {
|
||||
setState(() => showDivider = false);
|
||||
}
|
||||
else {
|
||||
setState(() => showDivider = true);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
scrollController = ScrollController()..addListener(scrollListener);
|
||||
fetchLogs(inOffset: 0);
|
||||
setState(() => previousIp = widget.ip);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
|
||||
if (widget.ip != previousIp) {
|
||||
setState(() => loadStatus = 0);
|
||||
fetchLogs(inOffset: 0);
|
||||
setState(() => previousIp = widget.ip);
|
||||
}
|
||||
|
||||
Widget status() {
|
||||
switch (loadStatus) {
|
||||
case 0:
|
||||
return SizedBox(
|
||||
width: double.maxFinite,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const CircularProgressIndicator(),
|
||||
const SizedBox(height: 30),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.loadingLogs,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
case 1:
|
||||
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 {
|
||||
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(
|
||||
width: double.maxFinite,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.error,
|
||||
color: Colors.red,
|
||||
size: 50,
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.logsNotLoaded,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
default:
|
||||
return const SizedBox();
|
||||
}
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(widget.name != null && widget.name != '' ? widget.name! : widget.ip),
|
||||
centerTitle: true,
|
||||
actions: [
|
||||
if (!(Platform.isAndroid || Platform.isIOS)) ...[
|
||||
IconButton(
|
||||
onPressed: fetchLogs,
|
||||
icon: const Icon(Icons.refresh_rounded),
|
||||
tooltip: AppLocalizations.of(context)!.refresh,
|
||||
),
|
||||
const SizedBox(width: 8)
|
||||
]
|
||||
],
|
||||
),
|
||||
body: status(),
|
||||
);
|
||||
}
|
||||
}
|
52
lib/screens/clients/client/remove_client_modal.dart
Normal file
52
lib/screens/clients/client/remove_client_modal.dart
Normal file
|
@ -0,0 +1,52 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
class RemoveClientModal extends StatelessWidget {
|
||||
final void Function() onConfirm;
|
||||
|
||||
const RemoveClientModal({
|
||||
Key? key,
|
||||
required this.onConfirm
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Column(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.delete_rounded,
|
||||
size: 24,
|
||||
color: Theme.of(context).listTileTheme.iconColor
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.removeClient,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
content: Text(
|
||||
AppLocalizations.of(context)!.removeClientMessage,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(AppLocalizations.of(context)!.cancel)
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
onConfirm();
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(AppLocalizations.of(context)!.confirm)
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
201
lib/screens/clients/client/safe_search_modal.dart
Normal file
201
lib/screens/clients/client/safe_search_modal.dart
Normal file
|
@ -0,0 +1,201 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/widgets/custom_checkbox_list_tile.dart';
|
||||
|
||||
import 'package:adguard_home_manager/models/safe_search.dart';
|
||||
|
||||
class SafeSearchModal extends StatefulWidget {
|
||||
final SafeSearch safeSearch;
|
||||
final bool disabled;
|
||||
final void Function(SafeSearch) onConfirm;
|
||||
|
||||
const SafeSearchModal({
|
||||
Key? key,
|
||||
required this.safeSearch,
|
||||
required this.disabled,
|
||||
required this.onConfirm
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<SafeSearchModal> createState() => _SafeSearchModalState();
|
||||
}
|
||||
|
||||
class _SafeSearchModalState extends State<SafeSearchModal> {
|
||||
bool generalEnabled = false;
|
||||
bool bingEnabled = false;
|
||||
bool duckduckgoEnabled = false;
|
||||
bool googleEnabled = false;
|
||||
bool pixabayEnabled = false;
|
||||
bool yandexEnabled = false;
|
||||
bool youtubeEnabled = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
generalEnabled = widget.safeSearch.enabled;
|
||||
bingEnabled = widget.safeSearch.bing;
|
||||
duckduckgoEnabled = widget.safeSearch.duckduckgo;
|
||||
googleEnabled = widget.safeSearch.google;
|
||||
pixabayEnabled = widget.safeSearch.pixabay;
|
||||
yandexEnabled = widget.safeSearch.yandex;
|
||||
youtubeEnabled = widget.safeSearch.youtube;
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 0, vertical: 16),
|
||||
title: Column(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.search_rounded,
|
||||
size: 24,
|
||||
color: Theme.of(context).listTileTheme.iconColor
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.safeSearch,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
content: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 400
|
||||
),
|
||||
child: Wrap(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Material(
|
||||
color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
child: InkWell(
|
||||
onTap: widget.disabled == true
|
||||
? null
|
||||
: () => setState(() => generalEnabled = !generalEnabled),
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
vertical: 5
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context)!.enable,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: widget.disabled == true
|
||||
? Colors.grey
|
||||
: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
),
|
||||
Switch(
|
||||
value: generalEnabled,
|
||||
onChanged: widget.disabled == true
|
||||
? null
|
||||
: (value) => setState(() => generalEnabled = value),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4, width: double.maxFinite),
|
||||
CustomCheckboxListTile(
|
||||
value: bingEnabled,
|
||||
onChanged: (value) => setState(() => bingEnabled = value),
|
||||
title: "Bing",
|
||||
disabled: widget.disabled || !generalEnabled,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 36,
|
||||
vertical: 4
|
||||
),
|
||||
),
|
||||
CustomCheckboxListTile(
|
||||
value: duckduckgoEnabled,
|
||||
onChanged: (value) => setState(() => duckduckgoEnabled = value),
|
||||
title: "DuckDuckGo",
|
||||
disabled: widget.disabled || !generalEnabled,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 36,
|
||||
vertical: 4
|
||||
),
|
||||
),
|
||||
CustomCheckboxListTile(
|
||||
value: googleEnabled,
|
||||
onChanged: (value) => setState(() => googleEnabled = value),
|
||||
title: "Google",
|
||||
disabled: widget.disabled || !generalEnabled,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 36,
|
||||
vertical: 4
|
||||
),
|
||||
),
|
||||
CustomCheckboxListTile(
|
||||
value: pixabayEnabled,
|
||||
onChanged: (value) => setState(() => pixabayEnabled = value),
|
||||
title: "Pixabay",
|
||||
disabled: widget.disabled || !generalEnabled,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 36,
|
||||
vertical: 4
|
||||
),
|
||||
),
|
||||
CustomCheckboxListTile(
|
||||
value: yandexEnabled,
|
||||
onChanged: (value) => setState(() => yandexEnabled = value),
|
||||
title: "Yandex",
|
||||
disabled: widget.disabled || !generalEnabled,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 36,
|
||||
vertical: 4
|
||||
),
|
||||
),
|
||||
CustomCheckboxListTile(
|
||||
value: youtubeEnabled,
|
||||
onChanged: (value) => setState(() => youtubeEnabled = value),
|
||||
title: "YouTube",
|
||||
disabled: widget.disabled || !generalEnabled,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 36,
|
||||
vertical: 4
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(AppLocalizations.of(context)!.cancel)
|
||||
),
|
||||
TextButton(
|
||||
onPressed: widget.disabled == false ? () {
|
||||
widget.onConfirm(
|
||||
SafeSearch(
|
||||
enabled: generalEnabled,
|
||||
bing: bingEnabled,
|
||||
duckduckgo: duckduckgoEnabled,
|
||||
google: googleEnabled,
|
||||
pixabay: pixabayEnabled,
|
||||
yandex: yandexEnabled,
|
||||
youtube: youtubeEnabled
|
||||
)
|
||||
);
|
||||
Navigator.pop(context);
|
||||
} : null,
|
||||
child: Text(AppLocalizations.of(context)!.confirm)
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
188
lib/screens/clients/client/services_modal.dart
Normal file
188
lib/screens/clients/client/services_modal.dart
Normal file
|
@ -0,0 +1,188 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/constants/enums.dart';
|
||||
import 'package:adguard_home_manager/providers/filtering_provider.dart';
|
||||
|
||||
class ServicesModal extends StatefulWidget {
|
||||
final List<String> blockedServices;
|
||||
final void Function(List<String>) onConfirm;
|
||||
|
||||
const ServicesModal({
|
||||
Key? key,
|
||||
required this.blockedServices,
|
||||
required this.onConfirm,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<ServicesModal> createState() => _ServicesModalStateWidget();
|
||||
}
|
||||
|
||||
class _ServicesModalStateWidget extends State<ServicesModal> {
|
||||
List<String> blockedServices = [];
|
||||
|
||||
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final filteringProvider = Provider.of<FilteringProvider>(context, listen: false);
|
||||
|
||||
if (filteringProvider.blockedServicesLoadStatus != LoadStatus.loaded) {
|
||||
filteringProvider.getBlockedServices();
|
||||
}
|
||||
|
||||
blockedServices = widget.blockedServices;
|
||||
super.initState();
|
||||
}
|
||||
|
||||
void checkUncheckService(bool value, String service) {
|
||||
if (value == true) {
|
||||
setState(() {
|
||||
blockedServices.add(service);
|
||||
});
|
||||
}
|
||||
else if (value == false) {
|
||||
setState(() {
|
||||
blockedServices = blockedServices.where((s) => s != service).toList();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final filteringProvider = Provider.of<FilteringProvider>(context);
|
||||
|
||||
Widget content() {
|
||||
switch (filteringProvider.blockedServicesLoadStatus) {
|
||||
case LoadStatus.loading:
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: SizedBox(
|
||||
width: double.maxFinite,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const CircularProgressIndicator(),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.loadingBlockedServicesList,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
case LoadStatus.loaded:
|
||||
return SizedBox(
|
||||
width: double.minPositive,
|
||||
height: MediaQuery.of(context).size.height*0.5,
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: filteringProvider.blockedServices!.services.length,
|
||||
itemBuilder: (context, index) => CheckboxListTile(
|
||||
title: Padding(
|
||||
padding: const EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
filteringProvider.blockedServices!.services[index].name,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
),
|
||||
),
|
||||
value: blockedServices.contains(filteringProvider.blockedServices!.services[index].id),
|
||||
checkboxShape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(5)
|
||||
),
|
||||
onChanged: (value) => checkUncheckService(value!, filteringProvider.blockedServices!.services[index].id)
|
||||
)
|
||||
),
|
||||
);
|
||||
|
||||
case LoadStatus.error:
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: SizedBox(
|
||||
width: double.maxFinite,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.error,
|
||||
color: Colors.red,
|
||||
size: 26,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.blockedServicesListNotLoaded,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
default:
|
||||
return const SizedBox();
|
||||
}
|
||||
}
|
||||
|
||||
return AlertDialog(
|
||||
scrollable: true,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 0,
|
||||
vertical: 16
|
||||
),
|
||||
title: Column(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.public,
|
||||
color: Theme.of(context).listTileTheme.iconColor
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.services,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
content: content(),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(AppLocalizations.of(context)!.cancel)
|
||||
),
|
||||
TextButton(
|
||||
onPressed: blockedServices.isNotEmpty
|
||||
? () {
|
||||
widget.onConfirm(blockedServices);
|
||||
Navigator.pop(context);
|
||||
}
|
||||
: null,
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.confirm,
|
||||
style: TextStyle(
|
||||
color: blockedServices.isNotEmpty
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.onSurface.withOpacity(0.38)
|
||||
),
|
||||
)
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
63
lib/screens/clients/client/settings_tile.dart
Normal file
63
lib/screens/clients/client/settings_tile.dart
Normal file
|
@ -0,0 +1,63 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class SettingsTile extends StatelessWidget {
|
||||
final String label;
|
||||
final bool? value;
|
||||
final void Function(bool)? onChange;
|
||||
final bool useGlobalSettingsFiltering;
|
||||
|
||||
const SettingsTile({
|
||||
Key? key,
|
||||
required this.label,
|
||||
required this.value,
|
||||
this.onChange,
|
||||
required this.useGlobalSettingsFiltering
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: onChange != null
|
||||
? value != null ? () => onChange!(!value!) : null
|
||||
: null,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 42,
|
||||
vertical: 5
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
useGlobalSettingsFiltering == false
|
||||
? Switch(
|
||||
value: value!,
|
||||
onChanged: onChange,
|
||||
)
|
||||
: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 14,
|
||||
horizontal: 12
|
||||
),
|
||||
child: Text(
|
||||
"Global",
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
145
lib/screens/clients/client/tags_modal.dart
Normal file
145
lib/screens/clients/client/tags_modal.dart
Normal file
|
@ -0,0 +1,145 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
class TagsModal extends StatefulWidget {
|
||||
final List<String> selectedTags;
|
||||
final List<String> tags;
|
||||
final void Function(List<String>) onConfirm;
|
||||
|
||||
const TagsModal({
|
||||
Key? key,
|
||||
required this.selectedTags,
|
||||
required this.tags,
|
||||
required this.onConfirm,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<TagsModal> createState() => _TagsModalState();
|
||||
}
|
||||
|
||||
class _TagsModalState extends State<TagsModal> {
|
||||
List<String> selectedTags = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
selectedTags = widget.selectedTags;
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
void checkUncheckTag(bool value, String tag) {
|
||||
if (value == true) {
|
||||
setState(() {
|
||||
selectedTags.add(tag);
|
||||
});
|
||||
}
|
||||
else if (value == false) {
|
||||
setState(() {
|
||||
selectedTags = selectedTags.where((s) => s != tag).toList();
|
||||
});
|
||||
}
|
||||
}
|
||||
return AlertDialog(
|
||||
scrollable: true,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 0,
|
||||
vertical: 16
|
||||
),
|
||||
title: Column(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.label_rounded,
|
||||
color: Theme.of(context).listTileTheme.iconColor
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.tags,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
content: SizedBox(
|
||||
width: double.minPositive,
|
||||
height: MediaQuery.of(context).size.height*0.5,
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: widget.tags.length,
|
||||
itemBuilder: (context, index) => Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () => checkUncheckTag(!selectedTags.contains(widget.tags[index]), widget.tags[index]),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 24,
|
||||
top: 8,
|
||||
bottom: 8,
|
||||
right: 12
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
widget.tags[index],
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
Checkbox(
|
||||
value: selectedTags.contains(widget.tags[index]),
|
||||
onChanged: (value) => checkUncheckTag(value!, widget.tags[index]),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(5)
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
// itemBuilder: (context, index) => CheckboxListTile(
|
||||
// title: Text(
|
||||
// widget.tags[index],
|
||||
// style: TextStyle(
|
||||
// fontWeight: FontWeight.normal,
|
||||
// color: Theme.of(context).colorScheme.onSurface
|
||||
// ),
|
||||
// ),
|
||||
// value: selectedTags.contains(widget.tags[index]),
|
||||
// checkboxShape: RoundedRectangleBorder(
|
||||
// borderRadius: BorderRadius.circular(5)
|
||||
// ),
|
||||
// onChanged: (value) => checkUncheckTag(value!, widget.tags[index])
|
||||
// )
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(AppLocalizations.of(context)!.cancel)
|
||||
),
|
||||
TextButton(
|
||||
onPressed: selectedTags.isNotEmpty
|
||||
? () {
|
||||
widget.onConfirm(selectedTags);
|
||||
Navigator.pop(context);
|
||||
}
|
||||
: null,
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.confirm,
|
||||
style: TextStyle(
|
||||
color: selectedTags.isNotEmpty
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.onSurface.withOpacity(0.38)
|
||||
),
|
||||
)
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
63
lib/screens/clients/client/tags_section.dart
Normal file
63
lib/screens/clients/client/tags_section.dart
Normal file
|
@ -0,0 +1,63 @@
|
|||
import 'package:adguard_home_manager/screens/clients/client/client_screen_functions.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
class TagsSection extends StatelessWidget {
|
||||
final List<String> selectedTags;
|
||||
final void Function(List<String>) onTagsSelected;
|
||||
|
||||
const TagsSection({
|
||||
Key? key,
|
||||
required this.selectedTags,
|
||||
required this.onTagsSelected
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () => openTagsModal(
|
||||
context: context,
|
||||
selectedTags: selectedTags,
|
||||
onSelectedTags: onTagsSelected
|
||||
) ,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 0, horizontal: 24
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.label_rounded,
|
||||
color: Theme.of(context).listTileTheme.iconColor
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context)!.selectTags,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 3),
|
||||
Text(
|
||||
selectedTags.isNotEmpty
|
||||
? "${selectedTags.length} ${AppLocalizations.of(context)!.tagsSelected}"
|
||||
: AppLocalizations.of(context)!.noTagsSelected,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).listTileTheme.iconColor
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
111
lib/screens/clients/client/upstream_servers_section.dart
Normal file
111
lib/screens/clients/client/upstream_servers_section.dart
Normal file
|
@ -0,0 +1,111 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:adguard_home_manager/widgets/section_label.dart';
|
||||
|
||||
class UpstreamServersSection extends StatefulWidget {
|
||||
final List<Map<dynamic, dynamic>> upstreamServers;
|
||||
final void Function() onCheckValidValues;
|
||||
final void Function(List<Map<dynamic, dynamic>>) onUpdateUpstreamServers;
|
||||
|
||||
const UpstreamServersSection({
|
||||
Key? key,
|
||||
required this.upstreamServers,
|
||||
required this.onCheckValidValues,
|
||||
required this.onUpdateUpstreamServers
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<UpstreamServersSection> createState() => _UpstreamServersSectionState();
|
||||
}
|
||||
|
||||
class _UpstreamServersSectionState extends State<UpstreamServersSection> {
|
||||
final Uuid uuid = const Uuid();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
SectionLabel(
|
||||
label: AppLocalizations.of(context)!.upstreamServers,
|
||||
padding: const EdgeInsets.all(24),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 20),
|
||||
child: IconButton(
|
||||
onPressed: () => setState(() => widget.upstreamServers.add(
|
||||
Map<String, Object>.from({
|
||||
'id': uuid.v4(),
|
||||
'controller': TextEditingController()
|
||||
})
|
||||
)),
|
||||
icon: const Icon(Icons.add)
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
if (widget.upstreamServers.isNotEmpty) ...widget.upstreamServers.map((controller) => Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 20),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
controller: controller['controller'],
|
||||
onChanged: (_) => widget.onCheckValidValues,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.dns_rounded),
|
||||
border: const OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(10)
|
||||
)
|
||||
),
|
||||
labelText: AppLocalizations.of(context)!.serverAddress,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
IconButton(
|
||||
onPressed: () => widget.onUpdateUpstreamServers(
|
||||
widget.upstreamServers.where((e) => e['id'] != controller['id']).toList()
|
||||
),
|
||||
icon: const Icon(Icons.remove_circle_outline_outlined)
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
)).toList(),
|
||||
if (widget.upstreamServers.isEmpty) Container(
|
||||
padding: const EdgeInsets.only(top: 12),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context)!.noUpstreamServers,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.willBeUsedGeneralServers,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue