From 5fc90615f1aef31a235d01c9af8d17f45eef8675 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Tue, 4 Oct 2022 15:19:44 +0200 Subject: [PATCH] Added charts on homescreen --- lib/l10n/app_en.arb | 7 ++- lib/l10n/app_es.arb | 7 ++- lib/screens/home/chart.dart | 106 ++++++++++++++++++++++++++++++++++++ lib/screens/home/home.dart | 64 ++++++++++++++++++++++ lib/widgets/line_chart.dart | 103 +++++++++++++++++++++++++++++++++++ pubspec.lock | 14 +++++ pubspec.yaml | 1 + 7 files changed, 300 insertions(+), 2 deletions(-) create mode 100644 lib/screens/home/chart.dart create mode 100644 lib/widgets/line_chart.dart diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 2a783b7..75c31ca 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -157,5 +157,10 @@ "filtered": "Filtered", "checkAppLogs": "Check app logs", "refresh": "Refresh", - "search": "Search" + "search": "Search", + "dnsQueries": "DNS queries", + "average": "Average", + "blockedFilters": "Blocked by filters", + "malwarePhisingBlocked": "Blocked malware/phising", + "blockedAdultWebsites": "Blocked adult websites" } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index f78b21f..601f811 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -157,5 +157,10 @@ "filtered": "Filtrada", "checkAppLogs": "Comprueba los logs de la app", "refresh": "Actualizar", - "search": "Buscar" + "search": "Buscar", + "dnsQueries": "Consultas DNS", + "average": "Promedio", + "blockedFilters": "Bloqueado por filtros", + "malwarePhisingBlocked": "Malware/phising bloqueado", + "blockedAdultWebsites": "Sitios para adultos bloqueados" } \ No newline at end of file diff --git a/lib/screens/home/chart.dart b/lib/screens/home/chart.dart new file mode 100644 index 0000000..7a7a30b --- /dev/null +++ b/lib/screens/home/chart.dart @@ -0,0 +1,106 @@ +import 'package:flutter/material.dart'; + +import 'package:adguard_home_manager/widgets/line_chart.dart'; + +class HomeChart extends StatelessWidget { + final List data; + final String label; + final String primaryValue; + final String secondaryValue; + final Color color; + + const HomeChart({ + Key? key, + required this.data, + required this.label, + required this.primaryValue, + required this.secondaryValue, + required this.color + }) : super(key: key); + + @override + Widget build(BuildContext context) { + bool isEmpty = true; + for (int item in data) { + if (item > 0) { + isEmpty = false; + break; + } + } + + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Column( + children: [ + Padding( + padding: EdgeInsets.only( + bottom: !isEmpty ? 10 : 15 + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.w500 + ), + ), + !isEmpty + ? Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + primaryValue, + style: TextStyle( + color: color, + fontSize: 18, + fontWeight: FontWeight.w500 + ), + ), + Text( + secondaryValue, + style: TextStyle( + fontSize: 12, + color: color + ), + ) + ], + ) + : Row( + children: [ + Text( + primaryValue, + style: TextStyle( + color: color, + fontSize: 18, + fontWeight: FontWeight.w500 + ), + ), + const SizedBox(width: 10), + Text( + "($secondaryValue)", + style: TextStyle( + fontSize: 12, + color: color + ), + ) + ], + ) + ], + ), + ), + if (!isEmpty) SizedBox( + width: double.maxFinite, + height: 150, + child: CustomLineChart( + data: data, + color: color, + ) + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/screens/home/home.dart b/lib/screens/home/home.dart index 8a01dd1..d169b2f 100644 --- a/lib/screens/home/home.dart +++ b/lib/screens/home/home.dart @@ -1,12 +1,16 @@ // ignore_for_file: use_build_context_synchronously +import 'dart:io'; + 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/server_status.dart'; import 'package:adguard_home_manager/screens/home/top_items.dart'; +import 'package:adguard_home_manager/screens/home/chart.dart'; +import 'package:adguard_home_manager/functions/number_format.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/services/http_requests.dart'; import 'package:adguard_home_manager/providers/servers_provider.dart'; @@ -54,6 +58,66 @@ class Home extends StatelessWidget { ), const SizedBox(height: 20), + HomeChart( + data: serversProvider.serverStatus.data!.stats.dnsQueries, + label: AppLocalizations.of(context)!.dnsQueries, + primaryValue: intFormat(serversProvider.serverStatus.data!.stats.numDnsQueries, Platform.localeName), + secondaryValue: "${doubleFormat(serversProvider.serverStatus.data!.stats.avgProcessingTime*1000, Platform.localeName)} ms", + color: Colors.blue, + ), + const Padding( + padding: EdgeInsets.symmetric(horizontal: 20), + child: Divider( + thickness: 1, + ), + ), + const SizedBox(height: 20), + + HomeChart( + data: serversProvider.serverStatus.data!.stats.blockedFiltering, + label: AppLocalizations.of(context)!.blockedFilters, + primaryValue: intFormat(serversProvider.serverStatus.data!.stats.numBlockedFiltering, Platform.localeName), + secondaryValue: "${doubleFormat((serversProvider.serverStatus.data!.stats.numBlockedFiltering/serversProvider.serverStatus.data!.stats.numDnsQueries)*100, Platform.localeName)}%", + color: Colors.red, + ), + const Padding( + padding: EdgeInsets.symmetric(horizontal: 20), + child: Divider( + thickness: 1, + ), + ), + const SizedBox(height: 20), + + HomeChart( + data: serversProvider.serverStatus.data!.stats.replacedSafebrowsing, + label: AppLocalizations.of(context)!.malwarePhisingBlocked, + primaryValue: intFormat(serversProvider.serverStatus.data!.stats.numReplacedSafebrowsing, Platform.localeName), + secondaryValue: "${doubleFormat((serversProvider.serverStatus.data!.stats.numReplacedSafebrowsing/serversProvider.serverStatus.data!.stats.numDnsQueries)*100, Platform.localeName)}%", + color: Colors.green, + ), + const Padding( + padding: EdgeInsets.symmetric(horizontal: 20), + child: Divider( + thickness: 1, + ), + ), + const SizedBox(height: 20), + + HomeChart( + data: serversProvider.serverStatus.data!.stats.replacedParental, + label: AppLocalizations.of(context)!.blockedAdultWebsites, + primaryValue: intFormat(serversProvider.serverStatus.data!.stats.numReplacedParental, Platform.localeName), + secondaryValue: "${doubleFormat((serversProvider.serverStatus.data!.stats.numReplacedParental/serversProvider.serverStatus.data!.stats.numDnsQueries)*100, Platform.localeName)}%", + color: Colors.orange, + ), + const Padding( + padding: EdgeInsets.symmetric(horizontal: 20), + child: Divider( + thickness: 1, + ), + ), + const SizedBox(height: 20), + TopItems( label: AppLocalizations.of(context)!.topQueriedDomains, data: serversProvider.serverStatus.data!.stats.topQueriedDomains, diff --git a/lib/widgets/line_chart.dart b/lib/widgets/line_chart.dart new file mode 100644 index 0000000..78338a5 --- /dev/null +++ b/lib/widgets/line_chart.dart @@ -0,0 +1,103 @@ +import 'package:flutter/material.dart'; +import 'package:fl_chart/fl_chart.dart'; +import 'package:provider/provider.dart'; + +import 'package:adguard_home_manager/providers/app_config_provider.dart'; + + +class CustomLineChart extends StatelessWidget { + final List data; + final Color color; + + const CustomLineChart({ + Key? key, + required this.data, + required this.color + }) : super(key: key); + + LineChartData mainData(Map data, ThemeMode selectedTheme) { + return LineChartData( + gridData: FlGridData( + show: false, + drawVerticalLine: false, + ), + titlesData: FlTitlesData( + show: false, + ), + borderData: FlBorderData( + show: false, + ), + lineBarsData: [ + LineChartBarData( + spots: data['data'], + color: color, + isCurved: true, + barWidth: 2, + isStrokeCapRound: true, + preventCurveOverShooting: true, + dotData: FlDotData( + show: false, + ), + belowBarData: BarAreaData( + show: true, + color: color.withOpacity(0.2) + ), + ), + ], + lineTouchData: LineTouchData( + enabled: true, + touchTooltipData: LineTouchTooltipData( + tooltipBgColor: selectedTheme == ThemeMode.light + ? const Color.fromRGBO(220, 220, 220, 0.9) + : const Color.fromRGBO(35, 35, 35, 0.9), + getTooltipItems: (items) => [ + LineTooltipItem( + items[0].y.toInt().toString(), + TextStyle( + fontWeight: FontWeight.bold, + fontSize: 14, + color: color + ) + ), + ] + ), + ) + ); + } + + @override + Widget build(BuildContext context) { + final appConfigProvider = Provider.of(context); + + Map formatData(List data) { + final List formattedData = []; + + int xPosition = 0; + int topPoint = 0; + for (int i = 0; i < data.length; i++) { + if (data[i] > topPoint) { + topPoint = data[i]; + } + formattedData.add( + FlSpot( + xPosition.toDouble(), + data[i].toDouble() + ) + ); + xPosition++; + } + + return { + 'data': formattedData, + 'topPoint': topPoint + }; + } + + return Padding( + padding: const EdgeInsets.symmetric(vertical: 15), + child: LineChart( + mainData(formatData(data), appConfigProvider.selectedTheme) + ), + ); + } +} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 3679527..f807035 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -141,6 +141,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.5.4" + equatable: + dependency: transitive + description: + name: equatable + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.5" expandable: dependency: "direct main" description: @@ -169,6 +176,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "6.1.4" + fl_chart: + dependency: "direct main" + description: + name: fl_chart + url: "https://pub.dartlang.org" + source: hosted + version: "0.55.2" flutter: dependency: "direct main" description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index 258a063..3de06c9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -44,6 +44,7 @@ dependencies: device_info_plus: ^4.1.2 uuid: ^3.0.6 expandable: ^5.0.1 + fl_chart: ^0.55.2 dev_dependencies: flutter_test: