diff --git a/lib/ui/pages/server_details/charts/bottom_title.dart b/lib/ui/pages/server_details/charts/bottom_title.dart new file mode 100644 index 00000000..3db976ad --- /dev/null +++ b/lib/ui/pages/server_details/charts/bottom_title.dart @@ -0,0 +1,29 @@ +import 'package:selfprivacy/logic/common_enum/common_enum.dart'; +import 'package:selfprivacy/logic/models/hetzner_metrics.dart'; +import 'package:intl/intl.dart'; + +String bottomTitle( + final int value, + final List data, + final Period period, +) { + final hhmm = DateFormat('HH:mm'); + final day = DateFormat('MMMd'); + String res; + + if (value <= 0) { + return ''; + } + + final time = data[value].time; + switch (period) { + case Period.hour: + case Period.day: + res = hhmm.format(time); + break; + case Period.month: + res = day.format(time); + } + + return res; +} diff --git a/lib/ui/pages/server_details/chart.dart b/lib/ui/pages/server_details/charts/chart.dart similarity index 86% rename from lib/ui/pages/server_details/chart.dart rename to lib/ui/pages/server_details/charts/chart.dart index f7972e9c..d5affc08 100644 --- a/lib/ui/pages/server_details/chart.dart +++ b/lib/ui/pages/server_details/charts/chart.dart @@ -1,4 +1,4 @@ -part of 'server_details_screen.dart'; +part of '../server_details_screen.dart'; class _Chart extends StatelessWidget { @override @@ -17,9 +17,25 @@ class _Chart extends StatelessWidget { ]; } else if (state is HetznerMetricsLoaded) { charts = [ - const Legend(color: Colors.red, text: 'CPU %'), - const SizedBox(height: 20), - getCpuChart(state), + BrandCards.filled( + clipped: false, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'CPU Usage', + style: Theme.of(context).textTheme.titleMedium?.copyWith( + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + const SizedBox(height: 16), + getCpuChart(state), + ], + ), + ), + ), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ diff --git a/lib/ui/pages/server_details/charts/cpu_chart.dart b/lib/ui/pages/server_details/charts/cpu_chart.dart new file mode 100644 index 00000000..14e30dcc --- /dev/null +++ b/lib/ui/pages/server_details/charts/cpu_chart.dart @@ -0,0 +1,175 @@ +import 'package:flutter/material.dart'; +import 'package:fl_chart/fl_chart.dart'; +import 'package:selfprivacy/logic/common_enum/common_enum.dart'; +import 'package:selfprivacy/logic/models/hetzner_metrics.dart'; +import 'package:intl/intl.dart'; +import 'package:selfprivacy/ui/pages/server_details/charts/bottom_title.dart'; + +class CpuChart extends StatelessWidget { + const CpuChart({ + required this.data, + required this.period, + required this.start, + final super.key, + }); + + final List data; + final Period period; + final DateTime start; + + List getSpots() { + var i = 0; + final List res = []; + + for (final d in data) { + res.add(FlSpot(i.toDouble(), d.value)); + i++; + } + + return res; + } + + @override + 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 = []; + + for (final spot in touchedBarSpots) { + final value = spot.y; + final date = data[spot.x.toInt()].time; + + res.add( + LineTooltipItem( + '${value.toStringAsFixed(2)}% at ${DateFormat('HH:mm dd.MM.yyyy').format(date)}', + TextStyle( + color: Theme.of(context).colorScheme.onSurface, + fontWeight: FontWeight.bold, + ), + ), + ); + } + + return res; + }, + ), + ), + lineBarsData: [ + LineChartBarData( + spots: getSpots(), + isCurved: false, + barWidth: 2, + color: Theme.of(context).colorScheme.primary, + dotData: FlDotData( + show: 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, + ), + ), + ), + ], + minY: 0, + maxY: 100, + minX: data.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(), + data, + 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( + showTitles: false, + ), + ), + ), + gridData: FlGridData( + show: true, + drawVerticalLine: true, + horizontalInterval: 25, + verticalInterval: 40, + 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, + ), + ), + ), + ), + ); + + bool checkToShowTitle( + final double minValue, + final double maxValue, + final SideTitles sideTitles, + final double appliedInterval, + final double value, + ) { + if (value < 0) { + return false; + } else if (value == 0) { + return true; + } + + final localValue = value - minValue; + final v = localValue / 20; + return v - v.floor() == 0; + } +} diff --git a/lib/ui/pages/server_details/network_charts.dart b/lib/ui/pages/server_details/charts/network_charts.dart similarity index 89% rename from lib/ui/pages/server_details/network_charts.dart rename to lib/ui/pages/server_details/charts/network_charts.dart index d1375ae6..75d866a1 100644 --- a/lib/ui/pages/server_details/network_charts.dart +++ b/lib/ui/pages/server_details/charts/network_charts.dart @@ -6,7 +6,7 @@ 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/hetzner_metrics.dart'; -import 'package:intl/intl.dart'; +import 'package:selfprivacy/ui/pages/server_details/charts/bottom_title.dart'; class NetworkChart extends StatelessWidget { const NetworkChart({ @@ -77,7 +77,11 @@ class NetworkChart extends StatelessWidget { child: RotatedBox( quarterTurns: 1, child: Text( - bottomTitle(value.toInt()), + bottomTitle( + value.toInt(), + listData[0], + period, + ), style: const TextStyle( fontSize: 10, color: Colors.purple, @@ -135,26 +139,4 @@ class NetworkChart extends StatelessWidget { final finalValue = diff / 20; return finalValue - finalValue.floor() == 0; } - - String bottomTitle(final int value) { - final hhmm = DateFormat('HH:mm'); - final day = DateFormat('MMMd'); - String res; - - if (value <= 0) { - return ''; - } - - final time = listData[0][value].time; - switch (period) { - case Period.hour: - case Period.day: - res = hhmm.format(time); - break; - case Period.month: - res = day.format(time); - } - - return res; - } } diff --git a/lib/ui/pages/server_details/cpu_chart.dart b/lib/ui/pages/server_details/cpu_chart.dart deleted file mode 100644 index 11f1eaef..00000000 --- a/lib/ui/pages/server_details/cpu_chart.dart +++ /dev/null @@ -1,135 +0,0 @@ -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/hetzner_metrics.dart'; -import 'package:intl/intl.dart'; - -class CpuChart extends StatelessWidget { - const CpuChart({ - required this.data, - required this.period, - required this.start, - final super.key, - }); - - final List data; - final Period period; - final DateTime start; - - List getSpots() { - var i = 0; - final List res = []; - - for (final d in data) { - res.add(FlSpot(i.toDouble(), d.value)); - i++; - } - - return res; - } - - @override - Widget build(final BuildContext context) => LineChart( - LineChartData( - lineTouchData: LineTouchData(enabled: false), - lineBarsData: [ - LineChartBarData( - spots: getSpots(), - isCurved: true, - barWidth: 1, - color: Colors.red, - dotData: FlDotData( - show: false, - ), - ), - ], - minY: 0, - maxY: 100, - minX: data.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()), - style: const TextStyle( - fontSize: 10, - color: Colors.purple, - fontWeight: FontWeight.bold, - ), - ), - ), - ), - showTitles: true, - ), - ), - leftTitles: AxisTitles( - sideTitles: SideTitles( - getTitlesWidget: (final value, final titleMeta) => Padding( - padding: const EdgeInsets.only(right: 15), - child: Text( - value.toInt().toString(), - style: progressTextStyleLight.copyWith( - color: Theme.of(context).brightness == Brightness.dark - ? BrandColors.gray4 - : null, - ), - ), - ), - interval: 25, - showTitles: false, - ), - ), - ), - gridData: FlGridData(show: true), - ), - ); - - bool checkToShowTitle( - final double minValue, - final double maxValue, - final SideTitles sideTitles, - final double appliedInterval, - final double value, - ) { - if (value < 0) { - return false; - } else if (value == 0) { - return true; - } - - final localValue = value - minValue; - final v = localValue / 20; - return v - v.floor() == 0; - } - - String bottomTitle(final int value) { - final hhmm = DateFormat('HH:mm'); - final day = DateFormat('MMMd'); - String res; - - if (value <= 0) { - return ''; - } - - final time = data[value].time; - switch (period) { - case Period.hour: - case Period.day: - res = hhmm.format(time); - break; - case Period.month: - res = day.format(time); - } - - return res; - } -} diff --git a/lib/ui/pages/server_details/server_details_screen.dart b/lib/ui/pages/server_details/server_details_screen.dart index 00043692..5c60b322 100644 --- a/lib/ui/pages/server_details/server_details_screen.dart +++ b/lib/ui/pages/server_details/server_details_screen.dart @@ -9,6 +9,7 @@ import 'package:selfprivacy/logic/cubit/server_detailed_info/server_detailed_inf import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart'; import 'package:selfprivacy/logic/models/state_types.dart'; +import 'package:selfprivacy/ui/components/brand_cards/brand_cards.dart'; import 'package:selfprivacy/ui/components/brand_header/brand_header.dart'; import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart'; import 'package:selfprivacy/ui/components/brand_loader/brand_loader.dart'; @@ -22,10 +23,10 @@ import 'package:selfprivacy/utils/named_font_weight.dart'; import 'package:selfprivacy/utils/route_transitions/basic.dart'; import 'package:timezone/timezone.dart'; -import 'package:selfprivacy/ui/pages/server_details/cpu_chart.dart'; -import 'package:selfprivacy/ui/pages/server_details/network_charts.dart'; +import 'package:selfprivacy/ui/pages/server_details/charts/cpu_chart.dart'; +import 'package:selfprivacy/ui/pages/server_details/charts/network_charts.dart'; -part 'chart.dart'; +part 'charts/chart.dart'; part 'header.dart'; part 'server_settings.dart'; part 'text_details.dart';