refactor: Add colors to disks graph

This commit is contained in:
Inex Code 2024-08-06 18:25:05 +03:00
parent bc121aa7ed
commit eb3aaa4c62
6 changed files with 164 additions and 67 deletions

View file

@ -5,7 +5,7 @@ import 'package:selfprivacy/config/app_controller/app_controller.dart';
import 'package:selfprivacy/config/brand_colors.dart'; import 'package:selfprivacy/config/brand_colors.dart';
import 'package:selfprivacy/config/preferences_repository/inherited_preferences_repository.dart'; import 'package:selfprivacy/config/preferences_repository/inherited_preferences_repository.dart';
import 'package:selfprivacy/config/preferences_repository/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<AppController> { class _AppControllerInjector extends InheritedNotifier<AppController> {
const _AppControllerInjector({ const _AppControllerInjector({

View file

@ -0,0 +1,24 @@
import 'package:dynamic_color/dynamic_color.dart';
import 'package:flutter/material.dart';
List<Color> 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),
];

View file

@ -7,6 +7,24 @@ class _Chart extends StatelessWidget {
final Period period = cubit.state.period; final Period period = cubit.state.period;
final MetricsState state = cubit.state; final MetricsState state = cubit.state;
List<Widget> charts; List<Widget> charts;
List<Color> 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) { if (state is MetricsLoaded || state is MetricsLoading) {
charts = [ charts = [
FilledCard( FilledCard(
@ -146,66 +164,103 @@ class _Chart extends StatelessWidget {
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
if (state is MetricsLoaded && state.diskMetrics != null) if (!(state is MetricsLoaded && state.diskMetrics == null))
FilledCard( Builder(
clipped: false, builder: (final context) {
child: Padding( List<DiskGraphData> getDisksGraphData(
padding: const EdgeInsets.all(16.0), final BuildContext context,
child: Column( ) {
crossAxisAlignment: CrossAxisAlignment.start, if (state is! MetricsLoaded) {
children: [ return [];
Row( }
crossAxisAlignment: CrossAxisAlignment.center, final diskData = state.diskMetrics?.diskMetrics;
mainAxisSize: MainAxisSize.max, if (diskData == null) {
mainAxisAlignment: MainAxisAlignment.spaceBetween, return [];
}
final List<DiskGraphData> res = [];
final colors = getGraphColors(context, diskData.keys.length);
for (final entry in diskData.entries) {
res.add(
DiskGraphData(
volume: context
.read<VolumesBloc>()
.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: [ children: [
Flexible( Row(
child: Text( crossAxisAlignment: CrossAxisAlignment.center,
'resource_chart.disk_title'.tr(), mainAxisSize: MainAxisSize.max,
style: mainAxisAlignment: MainAxisAlignment.spaceBetween,
Theme.of(context).textTheme.titleMedium?.copyWith( children: [
Flexible(
child: Text(
'resource_chart.disk_title'.tr(),
style: Theme.of(context)
.textTheme
.titleMedium
?.copyWith(
color: Theme.of(context) color: Theme.of(context)
.colorScheme .colorScheme
.onSurfaceVariant, .onSurfaceVariant,
), ),
), ),
), ),
Flexible( Flexible(
fit: FlexFit.loose, fit: FlexFit.loose,
child: Wrap( child: Wrap(
spacing: 8.0, spacing: 8.0,
runSpacing: 8.0, runSpacing: 8.0,
alignment: WrapAlignment.end, alignment: WrapAlignment.end,
runAlignment: WrapAlignment.end, runAlignment: WrapAlignment.end,
children: state.diskMetrics?.diskMetrics.keys children: disksGraphData
.map<Widget>( .map<Widget>(
(final diskId) => Legend( (final disk) => Legend(
color: color: disk.color,
Theme.of(context).colorScheme.primary, text: disk.volume.displayName,
text: diskId,
), ),
) )
.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) { } 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<DiskGraphData> diskData,
) {
final data = state.diskMetrics; final data = state.diskMetrics;
if (data == null) { if (data == null) {
@ -321,7 +379,7 @@ Widget getDiskChart(final MetricsLoaded state) {
return SizedBox( return SizedBox(
height: 200, height: 200,
child: DiskChart( child: DiskChart(
diskData: data.diskMetrics, diskData: diskData,
period: state.period, period: state.period,
start: state.metrics.start, 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<TimeSeriesData> diskData;
final String originalId;
}

View file

@ -4,9 +4,9 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:selfprivacy/logic/common_enum/common_enum.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/logic/models/metrics.dart';
import 'package:selfprivacy/ui/pages/server_details/charts/bottom_title.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 { class DiskChart extends StatelessWidget {
const DiskChart({ const DiskChart({
@ -16,7 +16,7 @@ class DiskChart extends StatelessWidget {
super.key, super.key,
}); });
final Map<String, List<TimeSeriesData>> diskData; final List<DiskGraphData> diskData;
final Period period; final Period period;
final DateTime start; final DateTime start;
@ -34,10 +34,9 @@ class DiskChart extends StatelessWidget {
@override @override
Widget build(final BuildContext context) { Widget build(final BuildContext context) {
final anyDiskId = diskData.keys.toList()[0];
final diskDataMax = [ final diskDataMax = [
...diskData.values.map<List<double>>( ...diskData.map<List<double>>(
(final timeData) => timeData.map((final e) => e.value).toList(), (final disk) => disk.diskData.map((final e) => e.value).toList(),
), ),
].expand((final x) => x).reduce(max); ].expand((final x) => x).reduce(max);
return LineChart( return LineChart(
@ -54,11 +53,11 @@ class DiskChart extends StatelessWidget {
bool timeShown = false; bool timeShown = false;
for (final spot in touchedBarSpots) { for (final spot in touchedBarSpots) {
final value = spot.y; final value = spot.y;
final date = diskData[anyDiskId]![spot.x.toInt()].time; final date = diskData.first.diskData[spot.x.toInt()].time;
res.add( res.add(
LineTooltipItem( 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( TextStyle(
color: Theme.of(context).colorScheme.onSurface, color: Theme.of(context).colorScheme.onSurface,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@ -73,14 +72,13 @@ class DiskChart extends StatelessWidget {
}, },
), ),
), ),
lineBarsData: diskData.values lineBarsData: diskData
.toList()
.map<LineChartBarData>( .map<LineChartBarData>(
(final timeData) => LineChartBarData( (final disk) => LineChartBarData(
spots: getSpots(timeData), spots: getSpots(disk.diskData),
isCurved: false, isCurved: false,
barWidth: 2, barWidth: 2,
color: Theme.of(context).colorScheme.primary, color: disk.color,
dotData: const FlDotData( dotData: const FlDotData(
show: false, show: false,
), ),
@ -88,8 +86,8 @@ class DiskChart extends StatelessWidget {
show: true, show: true,
gradient: LinearGradient( gradient: LinearGradient(
colors: [ colors: [
Theme.of(context).colorScheme.primary.withOpacity(0.5), disk.color.withOpacity(0.5),
Theme.of(context).colorScheme.primary.withOpacity(0.0), disk.color.withOpacity(0.0),
], ],
begin: Alignment.bottomCenter, begin: Alignment.bottomCenter,
end: Alignment.topCenter, end: Alignment.topCenter,
@ -114,7 +112,7 @@ class DiskChart extends StatelessWidget {
child: Text( child: Text(
bottomTitle( bottomTitle(
value.toInt(), value.toInt(),
diskData[anyDiskId]!, diskData.first.diskData,
period, period,
), ),
style: Theme.of(context).textTheme.labelSmall?.copyWith( style: Theme.of(context).textTheme.labelSmall?.copyWith(

View file

@ -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/metrics/metrics_cubit.dart';
import 'package:selfprivacy/logic/cubit/server_detailed_info/server_detailed_info_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/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/brand_icons/brand_icons.dart';
import 'package:selfprivacy/ui/components/buttons/segmented_buttons.dart'; import 'package:selfprivacy/ui/components/buttons/segmented_buttons.dart';
import 'package:selfprivacy/ui/components/cards/filled_card.dart'; import 'package:selfprivacy/ui/components/cards/filled_card.dart';