From 3e7e6699e939d54141df5265bfdb879df42da7f8 Mon Sep 17 00:00:00 2001 From: sruusk <57395313+sruusk@users.noreply.github.com> Date: Sat, 14 Dec 2024 05:58:59 +0200 Subject: [PATCH] Make chart interval controllable --- lib/utils/api.dart | 9 ++ lib/widgets/chart.dart | 252 ++++++++++++++++++++++++++--------------- pubspec.yaml | 2 +- 3 files changed, 169 insertions(+), 94 deletions(-) diff --git a/lib/utils/api.dart b/lib/utils/api.dart index 60aa7a4..acd68ba 100644 --- a/lib/utils/api.dart +++ b/lib/utils/api.dart @@ -108,11 +108,20 @@ class ElectricityApi { ).toList(); } + /// Get prices for the current day Future> getPricesForDay(DateTime day) async { final DateTime start = DateTime(day.year, day.month, day.day, 0, 0, 0); final DateTime end = DateTime(day.year, day.month, day.day, 23, 0, 0); return getPricesForInterval(start, end); } + + /// Get prices for current + n days. + Future> getPricesForDays(int days) async { + final DateTime now = DateTime.now(); + final DateTime start = DateTime(now.year, now.month, now.day, 0, 0, 0); + final DateTime end = DateTime(now.year, now.month, now.day + days, 23, 0, 0); + return getPricesForInterval(start, end); + } } class ElectricityPrice { diff --git a/lib/widgets/chart.dart b/lib/widgets/chart.dart index 2446beb..b2e5753 100644 --- a/lib/widgets/chart.dart +++ b/lib/widgets/chart.dart @@ -3,109 +3,175 @@ import 'package:mrx_charts/mrx_charts.dart'; import 'package:flutter/material.dart'; import 'package:sahkohinta/utils/api.dart'; -class ChartWidget extends StatelessWidget { - ChartWidget({super.key, required PriceModifiers modifiers}); - final pricesFuture = ElectricityApi().getPricesForDay(DateTime.now()); - final modifiersFuture = getModifiers(); +class ChartWidget extends StatefulWidget { + final PriceModifiers modifiers; + const ChartWidget({ + required this.modifiers, + super.key, + }); + + @override + State createState() => _ChartWidget(); +} + +class _ChartWidget extends State { + late List interval; + List? prices; + late PriceModifiers modifiers; + bool isLoading = true; + String? errorMessage; + + @override + void initState() { + super.initState(); + modifiers = widget.modifiers; + interval = getInterval(); + ElectricityApi().getPricesForDay(DateTime.now()).then((value) { + setState(() { + prices = value; + }); + }).catchError((error) { + setState(() { + errorMessage = error.toString(); + }); + }); + } + + _nextInterval() { + setState(() { + if(prices == null || prices!.length < interval[1].toInt() + 12) return; + interval = [interval[0] + 12, interval[1] + 12]; + }); + } + + _previousInterval() { + if(interval[0] < 12) return; + setState(() { + interval = [interval[0] - 12, interval[1] - 12]; + }); + } @override Widget build(BuildContext context) { - return FutureBuilder( - future: Future.wait([pricesFuture, modifiersFuture]), - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return const Center( - child: CircularProgressIndicator(), - ); - } else if (snapshot.hasError) { - return Center( - child: Text('Error: ${snapshot.error}'), - ); - } else if(snapshot.data!.isEmpty) { - return const Center( - child: Text('Virhe ladatessa hintatietoja'), - ); - } else { - var prices = snapshot.data![0] as List; - var modifiers = snapshot.data![1] as PriceModifiers; - if(prices.length != 24) { - return const Center( - child: Text('Virhe ladatessa hintatietoja'), - ); - } + if(errorMessage != null) return Center(child: Text('Error: $errorMessage')); + if(prices == null) return const Center(child: CircularProgressIndicator()); - double maxPrice = prices.map((e) => e.priceWithModifiers(modifiers)).reduce(max).ceilToDouble(); - double minPrice = min(0, prices.map((e) => e.priceWithModifiers(modifiers)).reduce(min)).floorToDouble(); - return Center( - child: Container( - constraints: const BoxConstraints( - maxHeight: 300.0, - maxWidth: 600 - ), - padding: const EdgeInsets.symmetric(horizontal: 32.0, vertical: 16.0), - child: Chart( - padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 2), - layers: [ - ChartAxisLayer( - settings: ChartAxisSettings( - x: ChartAxisSettingsAxis( - frequency: 1.0, - max: getInterval()[1], - min: getInterval()[0], - textStyle: TextStyle( - color: Theme.of(context).colorScheme.primary.withOpacity(0.6), - fontSize: 14.0, - ), - ), - y: ChartAxisSettingsAxis( - frequency: (maxPrice - minPrice) / 5, - max: maxPrice, - min: minPrice, - textStyle: TextStyle( - color: Theme.of(context).colorScheme.primary.withOpacity(0.6), - fontSize: 14.0, - ), - ), + double maxPrice = prices!.map((e) => e.priceWithModifiers(modifiers)).reduce(max).ceilToDouble(); + double minPrice = min(0, prices!.map((e) => e.priceWithModifiers(modifiers)).reduce(min)).floorToDouble(); + + return Center( + child: Column( + children: [ + Row( + children: [ + const SizedBox(width: 16.0), + Text( + '${prices![interval[0].toInt()].time.toLocal().month}. ${prices![interval[0].toInt()].time.toLocal().day}. ' + 'klo ${prices![interval[0].toInt()].time.toLocal().hour} ' + '- ${prices![interval[1].toInt()].time.toLocal().hour + 1} välillä', + style: Theme.of(context).textTheme.headlineSmall + ), + ], + ), + Container( + constraints: const BoxConstraints( + maxHeight: 300.0, + maxWidth: 600 + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16.0), + color: Theme.of(context).colorScheme.surfaceContainerLow, + ), + margin: const EdgeInsets.all(16.0), + padding: const EdgeInsets.all(16.0), + child: Chart( + padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 2), + layers: [ + ChartAxisLayer( + settings: ChartAxisSettings( + x: ChartAxisSettingsAxis( + frequency: 1.0, + max: interval[1], + min: interval[0], + textStyle: TextStyle( + color: Theme.of(context).colorScheme.primary.withOpacity(0.6), + fontSize: 14.0, ), - labelX: (value) => value.toInt().toString(), - labelY: (value) => value.toInt().toString(), ), - ChartBarLayer( - items: List.generate( - 12, - (index) => ChartBarDataItem( - color: Theme.of(context).colorScheme.primary, - value: prices[index + getInterval()[0].toInt()].priceWithModifiers(modifiers), - x: index.toDouble() + getInterval()[0], - ), - ), - settings: const ChartBarSettings( - thickness: 14.0, + y: ChartAxisSettingsAxis( + frequency: (maxPrice - minPrice) / 5, + max: maxPrice, + min: minPrice, + textStyle: TextStyle( + color: Theme.of(context).colorScheme.primary.withOpacity(0.6), + fontSize: 14.0, ), ), - ChartTooltipLayer( - shape: () => ChartTooltipBarShape( - backgroundColor: Theme.of(context).colorScheme.primaryContainer, - padding: const EdgeInsets.symmetric( - horizontal: 12.0, - vertical: 8.0, - ), - radius: 8.0, - currentPos: (item) => item.currentValuePos, - currentSize: (item) => item.currentValueSize, - onTextValue: (item) => '${item.value.toStringAsFixed(2)} sent/kWh', - textStyle: Theme.of(context).textTheme.bodyMedium!.copyWith( - color: Theme.of(context).colorScheme.onPrimaryContainer, - fontWeight: FontWeight.bold, - ), - ) + ), + labelX: (value) => value.toInt().toString(), + labelY: (value) => value.toInt().toString(), + ), + ChartBarLayer( + items: List.generate( + 12, + (index) => ChartBarDataItem( + color: Theme.of(context).colorScheme.primary, + value: prices![index + interval[0].toInt()].priceWithModifiers(modifiers), + x: index.toDouble() + interval[0], + ), + ), + settings: const ChartBarSettings( + thickness: 14.0, + ), + ), + ChartTooltipLayer( + shape: () => ChartTooltipBarShape( + backgroundColor: Theme.of(context).colorScheme.primaryContainer, + padding: const EdgeInsets.symmetric( + horizontal: 12.0, + vertical: 8.0, ), + radius: 8.0, + currentPos: (item) => item.currentValuePos, + currentSize: (item) => item.currentValueSize, + onTextValue: (item) => '${item.value.toStringAsFixed(2)} sent/kWh', + textStyle: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onPrimaryContainer, + fontWeight: FontWeight.bold, + ), + ) + ), + ], + ) + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + onPressed: _previousInterval, + child: Row( + children: [ + const Icon(Icons.arrow_back), + const SizedBox(width: 8.0), + Text('Edelliset 12 tuntia', style: Theme.of(context).textTheme.bodyMedium), ], - ) - ) - ); - } - } + ), + ), + const SizedBox(width: 16.0), + ElevatedButton( + onPressed: _nextInterval, + child: Row( + children: [ + Text('Seuraavat 12 tuntia', style: Theme.of(context).textTheme.bodyMedium), + const SizedBox(width: 8.0), + const Icon(Icons.arrow_forward), + ], + ), + ), + ], + ) + ], + ) ); } diff --git a/pubspec.yaml b/pubspec.yaml index 3cde539..077fc6c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.0.4+1 +version: 1.0.5+1 environment: sdk: ^3.5.3