diff --git a/lib/config/app_controller/inherited_app_controller.dart b/lib/config/app_controller/inherited_app_controller.dart index e7eeac7b..664c67ba 100644 --- a/lib/config/app_controller/inherited_app_controller.dart +++ b/lib/config/app_controller/inherited_app_controller.dart @@ -5,7 +5,7 @@ import 'package:selfprivacy/config/app_controller/app_controller.dart'; import 'package:selfprivacy/config/brand_colors.dart'; import 'package:selfprivacy/config/preferences_repository/inherited_preferences_repository.dart'; import 'package:selfprivacy/config/preferences_repository/preferences_repository.dart'; -import 'package:selfprivacy/theming/factory/app_theme_factory.dart'; +import 'package:selfprivacy/theming/app_theme_factory.dart'; class _AppControllerInjector extends InheritedNotifier { const _AppControllerInjector({ diff --git a/lib/theming/factory/app_theme_factory.dart b/lib/theming/app_theme_factory.dart similarity index 100% rename from lib/theming/factory/app_theme_factory.dart rename to lib/theming/app_theme_factory.dart diff --git a/lib/theming/harmonized_basic_colors.dart b/lib/theming/harmonized_basic_colors.dart new file mode 100644 index 00000000..30ac672d --- /dev/null +++ b/lib/theming/harmonized_basic_colors.dart @@ -0,0 +1,24 @@ +import 'package:dynamic_color/dynamic_color.dart'; +import 'package:flutter/material.dart'; + +List harmonizedBasicColors(final BuildContext context) => [ + Colors.red.harmonizeWith(Theme.of(context).colorScheme.primary), + Colors.green.harmonizeWith(Theme.of(context).colorScheme.primary), + Colors.blue.harmonizeWith(Theme.of(context).colorScheme.primary), + Colors.purple.harmonizeWith(Theme.of(context).colorScheme.primary), + Colors.indigo.harmonizeWith(Theme.of(context).colorScheme.primary), + Colors.teal.harmonizeWith(Theme.of(context).colorScheme.primary), + Colors.amber.harmonizeWith(Theme.of(context).colorScheme.primary), + Colors.deepOrange.harmonizeWith(Theme.of(context).colorScheme.primary), + Colors.pink.harmonizeWith(Theme.of(context).colorScheme.primary), + Colors.deepPurple.harmonizeWith(Theme.of(context).colorScheme.primary), + Colors.cyan.harmonizeWith(Theme.of(context).colorScheme.primary), + Colors.lime.harmonizeWith(Theme.of(context).colorScheme.primary), + Colors.yellow.harmonizeWith(Theme.of(context).colorScheme.primary), + Colors.orange.harmonizeWith(Theme.of(context).colorScheme.primary), + Colors.lightBlue.harmonizeWith(Theme.of(context).colorScheme.primary), + Colors.lightGreen.harmonizeWith(Theme.of(context).colorScheme.primary), + Colors.brown.harmonizeWith(Theme.of(context).colorScheme.primary), + Colors.blueGrey.harmonizeWith(Theme.of(context).colorScheme.primary), + Colors.grey.harmonizeWith(Theme.of(context).colorScheme.primary), + ]; diff --git a/lib/ui/pages/server_details/charts/chart.dart b/lib/ui/pages/server_details/charts/chart.dart index 666d3bd5..ca2e23d8 100644 --- a/lib/ui/pages/server_details/charts/chart.dart +++ b/lib/ui/pages/server_details/charts/chart.dart @@ -7,6 +7,24 @@ class _Chart extends StatelessWidget { final Period period = cubit.state.period; final MetricsState state = cubit.state; List charts; + + List getGraphColors(final BuildContext context, final int length) { + final colors = [ + Theme.of(context).colorScheme.primary, + Theme.of(context).colorScheme.tertiary, + Theme.of(context).colorScheme.secondary, + ...harmonizedBasicColors(context), + ]; + if (length <= colors.length) { + return colors.sublist(0, length); + } else { + return List.generate( + length, + (final index) => colors[index % colors.length], + ); + } + } + if (state is MetricsLoaded || state is MetricsLoading) { charts = [ FilledCard( @@ -146,66 +164,103 @@ class _Chart extends StatelessWidget { ), ), const SizedBox(height: 8), - if (state is MetricsLoaded && state.diskMetrics != null) - FilledCard( - clipped: false, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.spaceBetween, + if (!(state is MetricsLoaded && state.diskMetrics == null)) + Builder( + builder: (final context) { + List getDisksGraphData( + final BuildContext context, + ) { + if (state is! MetricsLoaded) { + return []; + } + final diskData = state.diskMetrics?.diskMetrics; + if (diskData == null) { + return []; + } + final List res = []; + final colors = getGraphColors(context, diskData.keys.length); + for (final entry in diskData.entries) { + res.add( + DiskGraphData( + volume: context + .read() + .state + .getVolume(entry.key.split('/').last), + color: colors[diskData.keys.toList().indexOf(entry.key)], + diskData: entry.value, + originalId: entry.key, + ), + ); + } + + return res; + } + + final disksGraphData = getDisksGraphData(context); + + return FilledCard( + clipped: false, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Flexible( - child: Text( - 'resource_chart.disk_title'.tr(), - style: - Theme.of(context).textTheme.titleMedium?.copyWith( + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: Text( + 'resource_chart.disk_title'.tr(), + style: Theme.of(context) + .textTheme + .titleMedium + ?.copyWith( color: Theme.of(context) .colorScheme .onSurfaceVariant, ), - ), - ), - Flexible( - fit: FlexFit.loose, - child: Wrap( - spacing: 8.0, - runSpacing: 8.0, - alignment: WrapAlignment.end, - runAlignment: WrapAlignment.end, - children: state.diskMetrics?.diskMetrics.keys + ), + ), + Flexible( + fit: FlexFit.loose, + child: Wrap( + spacing: 8.0, + runSpacing: 8.0, + alignment: WrapAlignment.end, + runAlignment: WrapAlignment.end, + children: disksGraphData .map( - (final diskId) => Legend( - color: - Theme.of(context).colorScheme.primary, - text: diskId, + (final disk) => Legend( + color: disk.color, + text: disk.volume.displayName, ), ) - .toList() ?? - [], - ), + .toList(), + ), + ), + ], + ), + const SizedBox(height: 20), + Stack( + alignment: Alignment.center, + children: [ + if (state is MetricsLoaded && + state.diskMetrics != null) + getDiskChart(state, disksGraphData), + AnimatedOpacity( + duration: const Duration(milliseconds: 200), + opacity: state is MetricsLoading ? 1 : 0, + child: const _GraphLoadingCardContent(), + ), + ], ), ], ), - const SizedBox(height: 20), - Stack( - alignment: Alignment.center, - children: [ - getDiskChart(state), - AnimatedOpacity( - duration: const Duration(milliseconds: 200), - opacity: state is MetricsLoading ? 1 : 0, - child: const _GraphLoadingCardContent(), - ), - ], - ), - ], - ), - ), + ), + ); + }, ), ]; } else if (state is MetricsUnsupported) { @@ -311,7 +366,10 @@ class _Chart extends StatelessWidget { } } -Widget getDiskChart(final MetricsLoaded state) { +Widget getDiskChart( + final MetricsLoaded state, + final List diskData, +) { final data = state.diskMetrics; if (data == null) { @@ -321,7 +379,7 @@ Widget getDiskChart(final MetricsLoaded state) { return SizedBox( height: 200, child: DiskChart( - diskData: data.diskMetrics, + diskData: diskData, period: state.period, start: state.metrics.start, ), @@ -383,3 +441,17 @@ class _ColoredBox extends StatelessWidget { ), ); } + +class DiskGraphData { + DiskGraphData({ + required this.volume, + required this.color, + required this.diskData, + required this.originalId, + }); + + final DiskVolume volume; + final Color color; + final List diskData; + final String originalId; +} diff --git a/lib/ui/pages/server_details/charts/disk_charts.dart b/lib/ui/pages/server_details/charts/disk_charts.dart index c0901d03..5d8ebc07 100644 --- a/lib/ui/pages/server_details/charts/disk_charts.dart +++ b/lib/ui/pages/server_details/charts/disk_charts.dart @@ -4,9 +4,9 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:selfprivacy/logic/common_enum/common_enum.dart'; -import 'package:selfprivacy/logic/models/disk_size.dart'; import 'package:selfprivacy/logic/models/metrics.dart'; import 'package:selfprivacy/ui/pages/server_details/charts/bottom_title.dart'; +import 'package:selfprivacy/ui/pages/server_details/server_details_screen.dart'; class DiskChart extends StatelessWidget { const DiskChart({ @@ -16,7 +16,7 @@ class DiskChart extends StatelessWidget { super.key, }); - final Map> diskData; + final List diskData; final Period period; final DateTime start; @@ -34,10 +34,9 @@ class DiskChart extends StatelessWidget { @override Widget build(final BuildContext context) { - final anyDiskId = diskData.keys.toList()[0]; final diskDataMax = [ - ...diskData.values.map>( - (final timeData) => timeData.map((final e) => e.value).toList(), + ...diskData.map>( + (final disk) => disk.diskData.map((final e) => e.value).toList(), ), ].expand((final x) => x).reduce(max); return LineChart( @@ -54,11 +53,11 @@ class DiskChart extends StatelessWidget { bool timeShown = false; for (final spot in touchedBarSpots) { final value = spot.y; - final date = diskData[anyDiskId]![spot.x.toInt()].time; + final date = diskData.first.diskData[spot.x.toInt()].time; res.add( LineTooltipItem( - '${timeShown ? '' : DateFormat('HH:mm dd.MM.yyyy').format(date)} ${diskData.keys.toList()[spot.barIndex]} ${value.toInt()}%', + '${timeShown ? '' : DateFormat('HH:mm dd.MM.yyyy').format(date)} ${diskData[spot.barIndex].volume.displayName} ${value.toInt()}%', TextStyle( color: Theme.of(context).colorScheme.onSurface, fontWeight: FontWeight.bold, @@ -73,14 +72,13 @@ class DiskChart extends StatelessWidget { }, ), ), - lineBarsData: diskData.values - .toList() + lineBarsData: diskData .map( - (final timeData) => LineChartBarData( - spots: getSpots(timeData), + (final disk) => LineChartBarData( + spots: getSpots(disk.diskData), isCurved: false, barWidth: 2, - color: Theme.of(context).colorScheme.primary, + color: disk.color, dotData: const FlDotData( show: false, ), @@ -88,8 +86,8 @@ class DiskChart extends StatelessWidget { show: true, gradient: LinearGradient( colors: [ - Theme.of(context).colorScheme.primary.withOpacity(0.5), - Theme.of(context).colorScheme.primary.withOpacity(0.0), + disk.color.withOpacity(0.5), + disk.color.withOpacity(0.0), ], begin: Alignment.bottomCenter, end: Alignment.topCenter, @@ -114,7 +112,7 @@ class DiskChart extends StatelessWidget { child: Text( bottomTitle( value.toInt(), - diskData[anyDiskId]!, + diskData.first.diskData, period, ), style: Theme.of(context).textTheme.labelSmall?.copyWith( diff --git a/lib/ui/pages/server_details/server_details_screen.dart b/lib/ui/pages/server_details/server_details_screen.dart index 2ef43887..c9ada4f9 100644 --- a/lib/ui/pages/server_details/server_details_screen.dart +++ b/lib/ui/pages/server_details/server_details_screen.dart @@ -7,6 +7,9 @@ import 'package:selfprivacy/logic/common_enum/common_enum.dart'; import 'package:selfprivacy/logic/cubit/metrics/metrics_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; +import 'package:selfprivacy/logic/models/disk_status.dart'; +import 'package:selfprivacy/logic/models/metrics.dart'; +import 'package:selfprivacy/theming/harmonized_basic_colors.dart'; import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart'; import 'package:selfprivacy/ui/components/buttons/segmented_buttons.dart'; import 'package:selfprivacy/ui/components/cards/filled_card.dart';