From d8a791cadb583a0d892251e225d05f630e64fd17 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Mon, 10 Oct 2022 15:57:47 +0200 Subject: [PATCH] Added blocked services modal --- lib/l10n/app_en.arb | 5 +- lib/l10n/app_es.arb | 5 +- lib/models/filtering.dart | 4 + lib/providers/servers_provider.dart | 5 + .../filters/blocked_services_modal.dart | 169 ++++++++++++++++++ lib/screens/filters/filters.dart | 68 ++++++- lib/services/http_requests.dart | 80 +++++++-- 7 files changed, 317 insertions(+), 19 deletions(-) create mode 100644 lib/screens/filters/blocked_services_modal.dart diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 2eebc8f..0b46059 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -321,5 +321,8 @@ "days7": "7 days", "changingUpdateFrequency": "Changing...", "updateFrequencyChanged": "Update frequency changed successfully", - "updateFrequencyNotChanged": "Updare frecuency couldn't be changed" + "updateFrequencyNotChanged": "Updare frecuency couldn't be changed", + "updating": "Updating values...", + "blockedServicesUpdated": "Blocked services updated successfully", + "blockedServicesNotUpdated": "Blocked services couldn't be updated" } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 1eff986..af133a7 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -321,5 +321,8 @@ "days7": "7 días", "changingUpdateFrequency": "Cambiando...", "updateFrequencyChanged": "Frecuencia de actualización cambiada correctamente", - "updateFrequencyNotChanged": "La frecuencia de actualización no pudo ser cambiada" + "updateFrequencyNotChanged": "La frecuencia de actualización no pudo ser cambiada", + "updating": "Actualizando valores...", + "blockedServicesUpdated": "Servicios bloqueados actualizados correctamente", + "blockedServicesNotUpdated": "No se pudieron actualizar los servicios bloqueados" } \ No newline at end of file diff --git a/lib/models/filtering.dart b/lib/models/filtering.dart index ed5d915..3935f17 100644 --- a/lib/models/filtering.dart +++ b/lib/models/filtering.dart @@ -18,6 +18,7 @@ class FilteringData { final List filters; final List whitelistFilters; List userRules; + List blockedServices; int interval; bool enabled; @@ -25,6 +26,7 @@ class FilteringData { required this.filters, required this.whitelistFilters, required this.userRules, + required this.blockedServices, required this.interval, required this.enabled, }); @@ -33,6 +35,7 @@ class FilteringData { filters: json["filters"] != null ? List.from(json["filters"].map((x) => Filter.fromJson(x))) : [], whitelistFilters: json["whitelist_filters"] != null ? List.from(json["whitelist_filters"].map((x) => Filter.fromJson(x))) : [], userRules: json["user_rules"] != null ? List.from(json["user_rules"].map((x) => x)).where((i) => i != '').toList() : [], + blockedServices: json["blocked_services"] != null ? List.from(json["blocked_services"].map((x) => x)).where((i) => i != '').toList() : [], interval: json["interval"], enabled: json["enabled"], ); @@ -41,6 +44,7 @@ class FilteringData { "filters": List.from(filters.map((x) => x.toJson())), "whitelist_filters": List.from(whitelistFilters.map((x) => x.toJson())), "user_rules": List.from(userRules.map((x) => x)), + "blocked_services": List.from(blockedServices.map((x) => x)), "interval": interval, "enabled": enabled, }; diff --git a/lib/providers/servers_provider.dart b/lib/providers/servers_provider.dart index 83fcd3a..0892dfe 100644 --- a/lib/providers/servers_provider.dart +++ b/lib/providers/servers_provider.dart @@ -129,6 +129,11 @@ class ServersProvider with ChangeNotifier { _filtering.data!.interval = frequency; notifyListeners(); } + + void setBlockedServices(List blockedServices) { + _filtering.data!.blockedServices = blockedServices; + notifyListeners(); + } Future createServer(Server server) async { final saved = await saveServerIntoDb(server); diff --git a/lib/screens/filters/blocked_services_modal.dart b/lib/screens/filters/blocked_services_modal.dart new file mode 100644 index 0000000..a72bc7f --- /dev/null +++ b/lib/screens/filters/blocked_services_modal.dart @@ -0,0 +1,169 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +import 'package:adguard_home_manager/constants/services.dart'; + +class BlockedServicesModal extends StatefulWidget { + final ScrollController scrollController; + final List blockedServices; + final void Function(List) onApply; + + const BlockedServicesModal({ + Key? key, + required this.scrollController, + required this.blockedServices, + required this.onApply, + }) : super(key: key); + + @override + State createState() => _BlockedServicesModalState(); +} + +class _BlockedServicesModalState extends State { + List> values = []; + + List convertFinalValues() { + return List.from( + values.map((v) => v['checked'] == true ? v['id'] : '').where((v) => v != '') + ); + } + + void updateValues(bool value, Map item) { + setState(() => values = values.map((v) { + if (v['id'] == item['id']) { + return { + 'id': v['id'], + 'checked': value + }; + } + else { + return v; + } + }).toList()); + } + + @override + void initState() { + for (var service in services) { + values.add({ + "id": service['id'], + "checked": widget.blockedServices.contains(service['id']) + }); + } + super.initState(); + } + + @override + Widget build(BuildContext context) { + final allSelected = convertFinalValues().length == services.length ? true : false; + + return Container( + decoration: BoxDecoration( + color: Theme.of(context).dialogBackgroundColor, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(28), + topRight: Radius.circular(28) + ) + ), + child: Column( + children: [ + Column( + children: [ + const Padding( + padding: EdgeInsets.only(top: 28), + child: Icon( + Icons.block, + size: 26, + ), + ), + const SizedBox(height: 20), + Text( + AppLocalizations.of(context)!.blockedServices, + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 24 + ), + ) + ], + ), + const SizedBox(height: 20), + Expanded( + child: ListView.builder( + controller: widget.scrollController, + itemCount: services.length, + itemBuilder: (context, index) => Material( + color: Colors.transparent, + child: InkWell( + onTap: () => updateValues(!(values[index]['checked'] as bool), services[index]), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 5), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + services[index]['label']!, + style: const TextStyle( + fontSize: 16 + ), + ), + Checkbox( + value: values[index]['checked'], + onChanged: (value) => updateValues(value!, services[index]), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5) + ), + ) + ], + ), + ), + ), + ) + ), + ), + Padding( + padding: const EdgeInsets.all(20), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + TextButton( + onPressed: () => { + allSelected == true + ? setState(() => values = values.map((v) => { + 'id': v['id'], + 'checked': false + }).toList()) + : setState(() => values = values.map((v) => { + 'id': v['id'], + 'checked': true + }).toList()) + }, + child: Text( + allSelected == true + ? AppLocalizations.of(context)!.unselectAll + : AppLocalizations.of(context)!.selectAll + ) + ), + Row( + children: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(AppLocalizations.of(context)!.cancel) + ), + const SizedBox(width: 20), + TextButton( + onPressed: () { + Navigator.pop(context); + widget.onApply(convertFinalValues()); + }, + child: Text(AppLocalizations.of(context)!.apply) + ), + ], + ) + ], + ), + ) + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/screens/filters/filters.dart b/lib/screens/filters/filters.dart index 2536ab9..8195f60 100644 --- a/lib/screens/filters/filters.dart +++ b/lib/screens/filters/filters.dart @@ -2,10 +2,12 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:bottom_sheet/bottom_sheet.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/screens/filters/filters_list.dart'; import 'package:adguard_home_manager/screens/filters/check_host_modal.dart'; +import 'package:adguard_home_manager/screens/filters/blocked_services_modal.dart'; import 'package:adguard_home_manager/screens/filters/custom_rules_list.dart'; import 'package:adguard_home_manager/screens/filters/update_interval_lists_modal.dart'; @@ -211,6 +213,54 @@ class _FiltersWidgetState extends State with TickerProviderStateM } } + void updateBlockedServices(List values) async { + ProcessModal processModal = ProcessModal(context: context); + processModal.open(AppLocalizations.of(context)!.updating); + + final result = await setBlockedServices(server: serversProvider.selectedServer!, data: values); + + processModal.close(); + + if (result['result'] == 'success') { + serversProvider.setBlockedServices(values); + + showSnacbkar( + context: context, + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.blockedServicesUpdated, + color: Colors.green + ); + } + else { + showSnacbkar( + context: context, + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.blockedServicesNotUpdated, + color: Colors.red + ); + } + } + + void openBlockedServicesModal() { + Future.delayed(const Duration(seconds: 0), () { + showFlexibleBottomSheet( + minHeight: 0.6, + initHeight: 0.6, + maxHeight: 0.95, + isCollapsible: true, + duration: const Duration(milliseconds: 250), + anchors: [0.95], + context: context, + builder: (ctx, controller, offset) => BlockedServicesModal( + scrollController: controller, + blockedServices: serversProvider.filtering.data!.blockedServices, + onApply: updateBlockedServices, + ), + bottomSheetColor: Colors.transparent + ); + }); + } + return DefaultTabController( length: 3, child: NestedScrollView( @@ -226,8 +276,8 @@ class _FiltersWidgetState extends State with TickerProviderStateM pinned: true, floating: true, forceElevated: innerBoxIsScrolled, - actions: [ - if (serversProvider.filtering.loadStatus == 1) IconButton( + actions: serversProvider.filtering.loadStatus == 1 ? [ + IconButton( onPressed: enableDisableFiltering, tooltip: serversProvider.filtering.data!.enabled == true ? AppLocalizations.of(context)!.disableFiltering @@ -261,7 +311,7 @@ class _FiltersWidgetState extends State with TickerProviderStateM ], ) ), - if (serversProvider.filtering.loadStatus == 1) IconButton( + IconButton( onPressed: () { showModalBottomSheet( context: context, @@ -287,6 +337,16 @@ class _FiltersWidgetState extends State with TickerProviderStateM ], ) ), + PopupMenuItem( + onTap: openBlockedServicesModal, + child: Row( + children: [ + const Icon(Icons.block), + const SizedBox(width: 10), + Text(AppLocalizations.of(context)!.blockedServices) + ], + ) + ), PopupMenuItem( onTap: showCheckHostModal, child: Row( @@ -300,7 +360,7 @@ class _FiltersWidgetState extends State with TickerProviderStateM ] ), const SizedBox(width: 5), - ], + ] : [], bottom: TabBar( controller: tabController, tabs: [ diff --git a/lib/services/http_requests.dart b/lib/services/http_requests.dart index 59b4b59..1fae505 100644 --- a/lib/services/http_requests.dart +++ b/lib/services/http_requests.dart @@ -19,7 +19,7 @@ Future> apiRequest({ required Server server, required String method, required String urlPath, - Map? body, + dynamic body, required String type, }) async { try { @@ -710,18 +710,29 @@ Future postDeleteClient({ Future getFiltering({ required Server server, }) async { - final result = await apiRequest( - urlPath: '/filtering/status', - method: 'get', - server: server, - type: 'get_filtering_status' - ); + final result = await Future.wait([ + apiRequest( + urlPath: '/filtering/status', + method: 'get', + server: server, + type: 'get_filtering_status' + ), + apiRequest( + urlPath: '/blocked_services/list', + method: 'get', + server: server, + type: 'get_filtering_status' + ), + ]); - if (result['hasResponse'] == true) { - if (result['statusCode'] == 200) { + if (result[0]['hasResponse'] == true && result[0]['hasResponse'] == true) { + if (result[0]['statusCode'] == 200 && result[0]['statusCode'] == 200) { return { 'result': 'success', - 'data': FilteringData.fromJson(jsonDecode(result['body'])) + 'data': FilteringData.fromJson({ + ...jsonDecode(result[0]['body']), + "blocked_services": jsonDecode(result[1]['body']), + }) }; } else { @@ -731,14 +742,23 @@ Future getFiltering({ type: 'get_filtering_status', dateTime: DateTime.now(), message: 'error_code_not_expected', - statusCode: result['statusCode'].toString(), - resBody: result['body'] + statusCode: result.map((res) => res['statusCode'] ?? 'null').toString(), + resBody: result.map((res) => res['body'] ?? 'null').toString(), ) }; } } else { - return result; + return { + 'result': 'error', + 'log': AppLog( + type: 'get_filtering_status', + dateTime: DateTime.now(), + message: 'no_response', + statusCode: result.map((res) => res['statusCode'] ?? 'null').toString(), + resBody: result.map((res) => res['body'] ?? 'null').toString(), + ) + }; } } @@ -1039,4 +1059,38 @@ Future requestChangeUpdateFrequency({ else { return result; } +} + +Future setBlockedServices({ + required Server server, + required List data, +}) async { + final result = await apiRequest( + urlPath: '/blocked_services/set', + method: 'post', + server: server, + body: data, + type: 'update_blocked_services' + ); + + if (result['hasResponse'] == true) { + if (result['statusCode'] == 200) { + return {'result': 'success'}; + } + else { + return { + 'result': 'error', + 'log': AppLog( + type: 'update_blocked_services', + dateTime: DateTime.now(), + message: 'error_code_not_expected', + statusCode: result['statusCode'], + resBody: result['body'], + ) + }; + } + } + else { + return result; + } } \ No newline at end of file