diff --git a/assets/translations/en.json b/assets/translations/en.json index d83fcd17..253d39e1 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -100,7 +100,11 @@ "chart": { "month": "Month", "day": "Day", - "hour": "Hour" + "hour": "Hour", + "cpu_title": "CPU Usage", + "network_title": "Network Usage", + "in": "In", + "out": "Out" }, "settings": { "allow_autoupgrade": "Allow auto-upgrade", diff --git a/assets/translations/ru.json b/assets/translations/ru.json index aa755a7c..df68f75f 100644 --- a/assets/translations/ru.json +++ b/assets/translations/ru.json @@ -100,7 +100,11 @@ "chart": { "month": "Месяц", "day": "День", - "hour": "Час" + "hour": "Час", + "cpu_title": "Использование процессора", + "network_title": "Использование сети", + "in": "Получено", + "out": "Отправлено" }, "settings": { "allow_autoupgrade": "Разрешить авто-обноления", diff --git a/lib/config/bloc_observer.dart b/lib/config/bloc_observer.dart index e68923c9..3f2f26d8 100644 --- a/lib/config/bloc_observer.dart +++ b/lib/config/bloc_observer.dart @@ -1,7 +1,4 @@ -import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:selfprivacy/ui/components/error/error.dart'; -import 'package:selfprivacy/utils/route_transitions/basic.dart'; import 'package:selfprivacy/config/get_it_config.dart'; @@ -14,15 +11,9 @@ class SimpleBlocObserver extends BlocObserver { final Object error, final StackTrace stackTrace, ) { - final NavigatorState navigator = getIt.get().navigator!; - navigator.push( - materialRoute( - BrandError( - error: error, - stackTrace: stackTrace, - ), - ), + getIt().showSnackBar( + 'Bloc error: ${error.toString()}', ); super.onError(bloc, error, stackTrace); } diff --git a/lib/ui/pages/server_details/charts/chart.dart b/lib/ui/pages/server_details/charts/chart.dart index 0ff2dd40..12528767 100644 --- a/lib/ui/pages/server_details/charts/chart.dart +++ b/lib/ui/pages/server_details/charts/chart.dart @@ -25,7 +25,7 @@ class _Chart extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'CPU Usage', + 'providers.server.chart.cpu_title'.tr(), style: Theme.of(context).textTheme.titleMedium?.copyWith( color: Theme.of(context).colorScheme.onSurfaceVariant, ), @@ -36,19 +36,40 @@ class _Chart extends StatelessWidget { ), ), ), - const SizedBox(height: 1), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - BrandText.small('Public Network interface bytes per sec'), - const SizedBox(width: 10), - const Legend(color: Colors.red, text: 'IN'), - const SizedBox(width: 5), - const Legend(color: Colors.green, text: 'OUT'), - ], + const SizedBox(height: 8), + FilledCard( + clipped: false, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + 'providers.server.chart.network_title'.tr(), + style: Theme.of(context).textTheme.titleMedium?.copyWith( + color: + Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + const Spacer(), + Legend( + color: Theme.of(context).colorScheme.primary, + text: 'providers.server.chart.in'.tr()), + const SizedBox(width: 5), + Legend( + color: Theme.of(context).colorScheme.tertiary, + text: 'providers.server.chart.out'.tr()), + ], + ), + const SizedBox(height: 20), + getBandwidthChart(state), + ], + ), + ), ), - const SizedBox(height: 20), - getBandwidthChart(state), ]; } else { throw 'wrong state'; @@ -81,6 +102,7 @@ class _Chart extends StatelessWidget { 'providers.server.chart.hour'.tr() ], ), + const SizedBox(height: 8), ...charts, ], ); @@ -129,7 +151,10 @@ class Legend extends StatelessWidget { children: [ _ColoredBox(color: color), const SizedBox(width: 5), - BrandText.small(text), + Text( + text, + style: Theme.of(context).textTheme.labelSmall, + ), ], ); } @@ -146,9 +171,11 @@ class _ColoredBox extends StatelessWidget { width: 10, height: 10, decoration: BoxDecoration( - color: color.withOpacity(0.3), + borderRadius: BorderRadius.circular(5), + color: color.withOpacity(0.4), border: Border.all( color: color, + width: 1.5, ), ), ); diff --git a/lib/ui/pages/server_details/charts/network_charts.dart b/lib/ui/pages/server_details/charts/network_charts.dart index 75d866a1..7487fd32 100644 --- a/lib/ui/pages/server_details/charts/network_charts.dart +++ b/lib/ui/pages/server_details/charts/network_charts.dart @@ -1,10 +1,10 @@ import 'dart:math'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:fl_chart/fl_chart.dart'; -import 'package:selfprivacy/config/brand_colors.dart'; -import 'package:selfprivacy/config/text_themes.dart'; import 'package:selfprivacy/logic/common_enum/common_enum.dart'; +import 'package:selfprivacy/logic/models/disk_size.dart'; import 'package:selfprivacy/logic/models/hetzner_metrics.dart'; import 'package:selfprivacy/ui/pages/server_details/charts/bottom_title.dart'; @@ -33,91 +33,174 @@ class NetworkChart extends StatelessWidget { } @override - Widget build(final BuildContext context) => SizedBox( - height: 150, - width: MediaQuery.of(context).size.width, - child: LineChart( - LineChartData( - lineTouchData: LineTouchData(enabled: false), - lineBarsData: [ - LineChartBarData( - spots: getSpots(listData[0]), - isCurved: true, - barWidth: 1, - color: Colors.red, - dotData: FlDotData( - show: false, - ), - ), - LineChartBarData( - spots: getSpots(listData[1]), - isCurved: true, - barWidth: 1, - color: Colors.green, - dotData: FlDotData( - show: false, - ), - ), - ], - minY: 0, - maxY: [ - ...listData[0].map((final e) => e.value), - ...listData[1].map((final e) => e.value) - ].reduce(max) * - 1.2, - minX: listData[0].length - 200, - titlesData: FlTitlesData( - topTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)), - bottomTitles: AxisTitles( - sideTitles: SideTitles( - interval: 20, - reservedSize: 50, - getTitlesWidget: (final value, final titleMeta) => Padding( - padding: const EdgeInsets.all(8.0), - child: RotatedBox( - quarterTurns: 1, - child: Text( - bottomTitle( - value.toInt(), - listData[0], - period, - ), - style: const TextStyle( - fontSize: 10, - color: Colors.purple, - fontWeight: FontWeight.bold, - ), + Widget build(final BuildContext context) => LineChart( + LineChartData( + lineTouchData: LineTouchData( + enabled: true, + touchTooltipData: LineTouchTooltipData( + tooltipBgColor: Theme.of(context).colorScheme.surface, + tooltipPadding: const EdgeInsets.all(8), + getTooltipItems: (final List touchedBarSpots) { + final List res = []; + + bool timeShown = false; + + for (final spot in touchedBarSpots) { + final value = spot.y; + final date = listData[0][spot.x.toInt()].time; + + res.add( + LineTooltipItem( + '${timeShown ? '' : DateFormat('HH:mm dd.MM.yyyy').format(date)} ${spot.barIndex == 0 ? 'providers.server.chart.in'.tr() : 'providers.server.chart.out'.tr()} ${DiskSize(byte: value.toInt()).toString()}', + TextStyle( + color: Theme.of(context).colorScheme.onSurface, + fontWeight: FontWeight.bold, ), ), - ), - showTitles: true, - ), + ); + + timeShown = true; + } + + return res; + }, + ), + ), + lineBarsData: [ + // IN + LineChartBarData( + spots: getSpots(listData[0]), + isCurved: false, + barWidth: 2, + color: Theme.of(context).colorScheme.primary, + dotData: FlDotData( + show: false, ), - leftTitles: AxisTitles( - sideTitles: SideTitles( - reservedSize: 50, - getTitlesWidget: (final value, final titleMeta) => Padding( - padding: const EdgeInsets.only(right: 5), - child: Text( - value.toInt().toString(), - style: progressTextStyleLight.copyWith( - color: Theme.of(context).brightness == Brightness.dark - ? BrandColors.gray4 - : null, - ), - ), - ), - interval: [ - ...listData[0].map((final e) => e.value), - ...listData[1].map((final e) => e.value) - ].reduce(max) * - 2 / - 10, - showTitles: false, + belowBarData: BarAreaData( + show: true, + gradient: LinearGradient( + colors: [ + Theme.of(context).colorScheme.primary.withOpacity(0.5), + Theme.of(context).colorScheme.primary.withOpacity(0.0), + ], + begin: Alignment.bottomCenter, + end: Alignment.topCenter, ), ), ), - gridData: FlGridData(show: true), + // OUT + LineChartBarData( + spots: getSpots(listData[1]), + isCurved: false, + barWidth: 2, + color: Theme.of(context).colorScheme.tertiary, + dotData: FlDotData( + show: false, + ), + belowBarData: BarAreaData( + show: true, + gradient: LinearGradient( + colors: [ + Theme.of(context).colorScheme.tertiary.withOpacity(0.5), + Theme.of(context).colorScheme.tertiary.withOpacity(0.0), + ], + begin: Alignment.bottomCenter, + end: Alignment.topCenter, + ), + ), + ), + ], + minY: 0, + maxY: [ + ...listData[0].map((final e) => e.value), + ...listData[1].map((final e) => e.value) + ].reduce(max) * + 1.2, + minX: listData[0].length - 200, + titlesData: FlTitlesData( + topTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + interval: 40, + reservedSize: 30, + getTitlesWidget: (final value, final titleMeta) => Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + bottomTitle( + value.toInt(), + listData[0], + period, + ), + style: Theme.of(context).textTheme.labelSmall?.copyWith( + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + ), + showTitles: true, + ), + ), + leftTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)), + rightTitles: AxisTitles( + sideTitles: SideTitles( + reservedSize: 50, + getTitlesWidget: (final value, final titleMeta) => Padding( + padding: const EdgeInsets.only(left: 5), + child: Text( + DiskSize(byte: value.toInt()).toString(), + style: Theme.of(context).textTheme.labelSmall?.copyWith( + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + ), + interval: [ + ...listData[0].map((final e) => e.value), + ...listData[1].map((final e) => e.value) + ].reduce(max) * + 2 / + 6.5, + showTitles: true, + ), + ), + ), + gridData: FlGridData( + show: true, + drawVerticalLine: true, + verticalInterval: 40, + horizontalInterval: [ + ...listData[0].map((final e) => e.value), + ...listData[1].map((final e) => e.value) + ].reduce(max) * + 2 / + 6.5, + getDrawingHorizontalLine: (final value) => FlLine( + color: Theme.of(context).colorScheme.outline.withOpacity(0.3), + strokeWidth: 1, + ), + getDrawingVerticalLine: (final value) => FlLine( + color: Theme.of(context).colorScheme.outline.withOpacity(0.3), + strokeWidth: 1, + ), + ), + borderData: FlBorderData( + show: true, + border: Border( + bottom: BorderSide( + color: Theme.of(context).colorScheme.outline.withOpacity(0.3), + width: 1, + ), + left: BorderSide( + color: Theme.of(context).colorScheme.outline.withOpacity(0.3), + width: 1, + ), + right: BorderSide( + color: Theme.of(context).colorScheme.outline.withOpacity(0.3), + width: 1, + ), + top: BorderSide( + color: Theme.of(context).colorScheme.outline.withOpacity(0.3), + width: 1, + ), + ), ), ), );