Commit ae9ccfa

derdilla <derdilla06@gmail.com>
2023-07-10 20:30:58
use ConsistentFutureBuilder instead of individual implementations
1 parent e8162ce
lib/components/measurement_graph.dart
@@ -1,6 +1,7 @@
 import 'dart:collection';
 import 'dart:math';
 
+import 'package:blood_pressure_app/components/consistent_future_builder.dart';
 import 'package:blood_pressure_app/components/display_interval_picker.dart';
 import 'package:blood_pressure_app/model/blood_pressure.dart';
 import 'package:blood_pressure_app/model/settings_store.dart';
@@ -34,139 +35,127 @@ class _LineChartState extends State<_LineChart> {
                 builder: (context, settings, child) {
                   return Consumer<BloodPressureModel>(builder: (context, model, child) {
                     var end = settings.displayDataEnd;
+                    return ConsistentFutureBuilder<UnmodifiableListView<BloodPressureRecord>>(
+                      future: (settings.graphStepSize == TimeStep.lifetime)
+                          ? model.all
+                          : model.getInTimeRange(settings.displayDataStart, end),
+                      onData: (context, fetchedData) {
+                        if (fetchedData.length < 2) {
+                          return Text(AppLocalizations.of(context)!.errNotEnoughDataToGraph);
+                        }
+                        List<BloodPressureRecord> data = fetchedData.toList();
+                        data.sort((a, b) => a.creationTime.compareTo(b.creationTime));
 
-                    return FutureBuilder<UnmodifiableListView<BloodPressureRecord>>(
-                        future: (settings.graphStepSize == TimeStep.lifetime)
-                            ? model.all
-                            : model.getInTimeRange(settings.displayDataStart, end),
-                        builder:
-                            (BuildContext context, AsyncSnapshot<UnmodifiableListView<BloodPressureRecord>> snapshot) {
-                          switch (snapshot.connectionState) {
-                            case ConnectionState.none:
-                              return Text(AppLocalizations.of(context)!.errNotStarted);
-                            case ConnectionState.waiting:
-                              return Text(AppLocalizations.of(context)!.loading);
-                            default:
-                              if (snapshot.hasError) {
-                                return Text(AppLocalizations.of(context)!.error(snapshot.error.toString()));
-                              } else if (snapshot.hasData && snapshot.data!.length < 2) {
-                                return Text(AppLocalizations.of(context)!.errNotEnoughDataToGraph);
-                              } else {
-                                assert(snapshot.hasData);
-                                List<BloodPressureRecord> data = snapshot.data?.toList() ?? [];
-                                data.sort((a, b) => a.creationTime.compareTo(b.creationTime));
+                        List<FlSpot> pulseSpots = [];
+                        List<FlSpot> diastolicSpots = [];
+                        List<FlSpot> systolicSpots = [];
+                        int pulMax = 0;
+                        int diaMax = 0;
+                        int sysMax = 0;
+                        for (var element in data) {
+                          final x = element.creationTime.millisecondsSinceEpoch.toDouble();
+                          diastolicSpots.add(FlSpot(x, element.diastolic.toDouble()));
+                          systolicSpots.add(FlSpot(x, element.systolic.toDouble()));
+                          pulseSpots.add(FlSpot(x, element.pulse.toDouble()));
+                          pulMax = max(pulMax, element.pulse);
+                          diaMax = max(diaMax, element.diastolic);
+                          sysMax = max(sysMax, element.systolic);
+                        }
 
-                                List<FlSpot> pulseSpots = [];
-                                List<FlSpot> diastolicSpots = [];
-                                List<FlSpot> systolicSpots = [];
-                                int pulMax = 0;
-                                int diaMax = 0;
-                                int sysMax = 0;
-                                for (var element in data) {
-                                  final x = element.creationTime.millisecondsSinceEpoch.toDouble();
-                                  diastolicSpots.add(FlSpot(x, element.diastolic.toDouble()));
-                                  systolicSpots.add(FlSpot(x, element.systolic.toDouble()));
-                                  pulseSpots.add(FlSpot(x, element.pulse.toDouble()));
-                                  pulMax = max(pulMax, element.pulse);
-                                  diaMax = max(diaMax, element.diastolic);
-                                  sysMax = max(sysMax, element.systolic);
-                                }
+                        const noTitels = AxisTitles(sideTitles: SideTitles(reservedSize: 40, showTitles: false));
+                        return LineChart(
+                          LineChartData(
+                            minY: settings.validateInputs ? 30 : 0,
+                            maxY: max(pulMax.toDouble(), max(diaMax.toDouble(), sysMax.toDouble())) + 5,
+                            titlesData: FlTitlesData(
+                              topTitles: noTitels,
+                              rightTitles: noTitels,
+                              bottomTitles: AxisTitles(
+                                sideTitles: SideTitles(
+                                  showTitles: true,
+                                  interval: _lineChartTitleIntervall,
+                                  getTitlesWidget: (double pos, TitleMeta meta) {
+                                    // calculate new intervall
+                                    double graphWidth = meta.max - meta.min;
+                                    assert(graphWidth > 0);
+                                    if (((graphWidth - 2) / settings.graphTitlesCount) !=
+                                        _lineChartTitleIntervall) {
+                                      // simple hack needed to change the state during build
+                                      // https://stackoverflow.com/a/63607696/21489239
+                                      Future.delayed(Duration.zero, () async {
+                                        setState(() {
+                                          _lineChartTitleIntervall =
+                                              (graphWidth - 2) / settings.graphTitlesCount;
+                                        });
+                                      });
+                                    }
 
-                                const noTitels = AxisTitles(sideTitles: SideTitles(reservedSize: 40, showTitles: false));
-                                return LineChart(
-                                    LineChartData(
-                                        minY: settings.validateInputs ? 30 : 0,
-                                        maxY: max(pulMax.toDouble(), max(diaMax.toDouble(), sysMax.toDouble())) + 5,
-                                        titlesData: FlTitlesData(
-                                          topTitles: noTitels,
-                                          rightTitles: noTitels,
-                                          bottomTitles: AxisTitles(
-                                            sideTitles: SideTitles(
-                                                showTitles: true,
-                                                interval: _lineChartTitleIntervall,
-                                                getTitlesWidget: (double pos, TitleMeta meta) {
-                                                  // calculate new intervall
-                                                  double graphWidth = meta.max - meta.min;
-                                                  assert(graphWidth > 0);
-                                                  if (((graphWidth - 2) / settings.graphTitlesCount) !=
-                                                      _lineChartTitleIntervall) {
-                                                    // simple hack needed to change the state during build
-                                                    // https://stackoverflow.com/a/63607696/21489239
-                                                    Future.delayed(Duration.zero, () async {
-                                                      setState(() {
-                                                        _lineChartTitleIntervall =
-                                                            (graphWidth - 2) / settings.graphTitlesCount;
-                                                      });
-                                                    });
-                                                  }
+                                    // don't show fixed titles, as they are replaced by long dates below
+                                    if (meta.axisPosition <= 1 || pos >= meta.max) {
+                                      return const SizedBox.shrink();
+                                    }
 
-                                                  // don't show fixed titles, as they are replaced by long dates below
-                                                  if (meta.axisPosition <= 1 || pos >= meta.max) {
-                                                    return const SizedBox.shrink();
-                                                  }
-
-                                                  late final DateFormat formatter;
-                                                  switch (settings.graphStepSize) {
-                                                    case TimeStep.day:
-                                                      formatter = DateFormat('H:m');
-                                                      break;
-                                                    case TimeStep.month:
-                                                      formatter = DateFormat('d');
-                                                      break;
-                                                    case TimeStep.week:
-                                                      formatter = DateFormat('E');
-                                                      break;
-                                                    case TimeStep.year:
-                                                      formatter = DateFormat('MMM');
-                                                      break;
-                                                    case TimeStep.lifetime:
-                                                      formatter = DateFormat('yyyy');
-                                                  }
-                                                  return Text(formatter
-                                                      .format(DateTime.fromMillisecondsSinceEpoch(pos.toInt())));
-                                                }),
-                                          ),
-                                        ),
-                                        lineTouchData: LineTouchData(
-                                            touchTooltipData:
-                                                LineTouchTooltipData(tooltipMargin: -200, tooltipRoundedRadius: 20)),
-                                        lineBarsData: [
-                                          LineChartBarData(
-                                            spots: pulseSpots,
-                                            dotData: FlDotData(
-                                              show: false,
-                                            ),
-                                            color: settings.pulColor,
-                                            barWidth: settings.graphLineThickness,
-                                          ),
-                                          LineChartBarData(
-                                              spots: diastolicSpots,
-                                              color: settings.diaColor,
-                                              barWidth: settings.graphLineThickness,
-                                              dotData: FlDotData(
-                                                show: false,
-                                              ),
-                                              belowBarData: BarAreaData(
-                                                  show: true,
-                                                  color: Colors.red.shade400.withAlpha(100),
-                                                  cutOffY: settings.diaWarn,
-                                                  applyCutOffY: true)),
-                                          LineChartBarData(
-                                              spots: systolicSpots,
-                                              color: settings.sysColor,
-                                              barWidth: settings.graphLineThickness,
-                                              dotData: FlDotData(
-                                                show: false,
-                                              ),
-                                              belowBarData: BarAreaData(
-                                                  show: true,
-                                                  color: Colors.red.shade400.withAlpha(100),
-                                                  cutOffY: settings.sysWarn,
-                                                  applyCutOffY: true))
-                                        ]));
-                              }
-                          }
-                        });
+                                    late final DateFormat formatter;
+                                    switch (settings.graphStepSize) {
+                                      case TimeStep.day:
+                                        formatter = DateFormat('H:m');
+                                        break;
+                                      case TimeStep.month:
+                                        formatter = DateFormat('d');
+                                        break;
+                                      case TimeStep.week:
+                                        formatter = DateFormat('E');
+                                        break;
+                                      case TimeStep.year:
+                                        formatter = DateFormat('MMM');
+                                        break;
+                                      case TimeStep.lifetime:
+                                        formatter = DateFormat('yyyy');
+                                    }
+                                    return Text(formatter
+                                        .format(DateTime.fromMillisecondsSinceEpoch(pos.toInt())));
+                                  }),
+                                ),
+                              ),
+                              lineTouchData: const LineTouchData(
+                                  touchTooltipData:
+                                  LineTouchTooltipData(tooltipMargin: -200, tooltipRoundedRadius: 20)),
+                              lineBarsData: [
+                                LineChartBarData(
+                                  spots: pulseSpots,
+                                  dotData: const FlDotData(
+                                    show: false,
+                                  ),
+                                  color: settings.pulColor,
+                                  barWidth: settings.graphLineThickness,
+                                ),
+                                LineChartBarData(
+                                    spots: diastolicSpots,
+                                    color: settings.diaColor,
+                                    barWidth: settings.graphLineThickness,
+                                    dotData: const FlDotData(
+                                      show: false,
+                                    ),
+                                    belowBarData: BarAreaData(
+                                        show: true,
+                                        color: Colors.red.shade400.withAlpha(100),
+                                        cutOffY: settings.diaWarn,
+                                        applyCutOffY: true)),
+                                LineChartBarData(
+                                    spots: systolicSpots,
+                                    color: settings.sysColor,
+                                    barWidth: settings.graphLineThickness,
+                                    dotData: const FlDotData(
+                                      show: false,
+                                    ),
+                                    belowBarData: BarAreaData(
+                                        show: true,
+                                        color: Colors.red.shade400.withAlpha(100),
+                                        cutOffY: settings.sysWarn,
+                                        applyCutOffY: true))
+                              ]));
+                      }
+                    );
                   });
                 },
               )),
lib/components/measurement_list.dart
@@ -1,5 +1,6 @@
 import 'dart:collection';
 
+import 'package:blood_pressure_app/components/consistent_future_builder.dart';
 import 'package:blood_pressure_app/model/blood_pressure.dart';
 import 'package:blood_pressure_app/model/settings_store.dart';
 import 'package:blood_pressure_app/screens/add_measurement.dart';
@@ -69,147 +70,138 @@ class MeasurementList extends StatelessWidget {
           child: Consumer<BloodPressureModel>(builder: (context, model, child) {
             return Consumer<Settings>(builder: (context, settings, child) {
               final items = model.getInTimeRange(settings.displayDataStart, settings.displayDataEnd);
-              return FutureBuilder<UnmodifiableListView<BloodPressureRecord>>(
-                  future: items,
-                  builder:
-                      (BuildContext context, AsyncSnapshot<UnmodifiableListView<BloodPressureRecord>> recordsSnapshot) {
-                    assert(recordsSnapshot.connectionState != ConnectionState.none);
+              return ConsistentFutureBuilder<UnmodifiableListView<BloodPressureRecord>>(
+                future: items,
+                onData: (context, data) {
+                  if (data.isNotEmpty && data.first.diastolic > 0) {
+                    return ListView.builder(
+                        itemCount: data.length,
+                        shrinkWrap: true,
+                        padding: const EdgeInsets.all(2),
+                        itemBuilder: (context, index) {
+                          final formatter = DateFormat(settings.dateFormatString);
+                          return Column(
+                            children: [
+                              Dismissible(
+                                key: Key(data[index].creationTime.toIso8601String()),
+                                confirmDismiss: (direction) async {
+                                  final model = Provider.of<BloodPressureModel>(context, listen: false);
+                                  if (direction == DismissDirection.startToEnd) { // edit
+                                    model.delete(data[index].creationTime);
+                                    Navigator.push(
+                                      context,
+                                      MaterialPageRoute(
+                                          builder: (context) => AddMeasurementPage(
+                                            initTime: data[index].creationTime,
+                                            initSys: data[index].systolic,
+                                            initDia: data[index].diastolic,
+                                            initPul: data[index].pulse,
+                                            initNote: data[index].notes,
+                                            isEdit: true,
+                                          )),
+                                    );
+                                    return false;
+                                  } else { // delete
+                                    bool dialogeDeletionConfirmed = false;
+                                    if (settings.confirmDeletion) {
+                                      await showDialog(
+                                          context: context,
+                                          builder: (context) {
+                                            return AlertDialog(
+                                              title: Text(AppLocalizations.of(context)!.confirmDelete),
+                                              content: Text(AppLocalizations.of(context)!.confirmDeleteDesc),
+                                              actions: [
+                                                ElevatedButton(
+                                                    onPressed: () => Navigator.of(context).pop(),
+                                                    child: Text(AppLocalizations.of(context)!.btnCancel)),
+                                                ElevatedButton(
+                                                    onPressed: () {
+                                                      model.delete(data[index].creationTime);
 
-                    if (recordsSnapshot.connectionState == ConnectionState.waiting) {
-                      return Text(AppLocalizations.of(context)!.loading);
-                    } else if (recordsSnapshot.hasError) {
-                      return Text(AppLocalizations.of(context)!.error(recordsSnapshot.error.toString()));
-                    } else {
-                      final data = recordsSnapshot.data ?? [];
-                      if (data.isNotEmpty && data.first.diastolic > 0) {
-                        return ListView.builder(
-                            itemCount: data.length,
-                            shrinkWrap: true,
-                            padding: const EdgeInsets.all(2),
-                            itemBuilder: (context, index) {
-                              final formatter = DateFormat(settings.dateFormatString);
-                              return Column(
-                                children: [
-                                  Dismissible(
-                                    key: Key(data[index].creationTime.toIso8601String()),
-                                    confirmDismiss: (direction) async {
-                                      final model = Provider.of<BloodPressureModel>(context, listen: false);
-                                      if (direction == DismissDirection.startToEnd) { // edit
-                                        model.delete(data[index].creationTime);
-                                        Navigator.push(
-                                          context,
-                                          MaterialPageRoute(
-                                              builder: (context) => AddMeasurementPage(
-                                                    initTime: data[index].creationTime,
-                                                    initSys: data[index].systolic,
-                                                    initDia: data[index].diastolic,
-                                                    initPul: data[index].pulse,
-                                                    initNote: data[index].notes,
-                                                    isEdit: true,
-                                                  )),
-                                        );
-                                        return false;
-                                      } else { // delete
-                                        bool dialogeDeletionConfirmed = false;
-                                        if (settings.confirmDeletion) {
-                                          await showDialog(
-                                              context: context,
-                                              builder: (context) {
-                                                return AlertDialog(
-                                                  title: Text(AppLocalizations.of(context)!.confirmDelete),
-                                                  content: Text(AppLocalizations.of(context)!.confirmDeleteDesc),
-                                                  actions: [
-                                                    ElevatedButton(
-                                                        onPressed: () => Navigator.of(context).pop(),
-                                                        child: Text(AppLocalizations.of(context)!.btnCancel)),
-                                                    ElevatedButton(
-                                                        onPressed: () {
-                                                          model.delete(data[index].creationTime);
+                                                      dialogeDeletionConfirmed = true;
+                                                      Navigator.of(context).pop();
+                                                    },
+                                                    child: Text(AppLocalizations.of(context)!.btnConfirm)),
+                                              ],
+                                            );
+                                          });
+                                    } else {
+                                      model.delete(data[index].creationTime);
+                                      dialogeDeletionConfirmed = true;
+                                    }
 
-                                                          dialogeDeletionConfirmed = true;
-                                                          Navigator.of(context).pop();
-                                                        },
-                                                        child: Text(AppLocalizations.of(context)!.btnConfirm)),
-                                                  ],
-                                                );
-                                              });
-                                        } else {
-                                          model.delete(data[index].creationTime);
-                                          dialogeDeletionConfirmed = true;
-                                        }
-
-                                        if (dialogeDeletionConfirmed) {
-                                          if (!context.mounted) return true;
-                                          ScaffoldMessenger.of(context).removeCurrentSnackBar();
-                                          ScaffoldMessenger.of(context).showSnackBar(SnackBar(
-                                            duration: const Duration(seconds: 5),
-                                            content: Text(AppLocalizations.of(context)!.deletionConfirmed),
-                                            action: SnackBarAction(
-                                              label: AppLocalizations.of(context)!.btnUndo,
-                                              onPressed: () async {
-                                                model.add(BloodPressureRecord(
-                                                    data[index].creationTime,
-                                                    data[index].systolic,
-                                                    data[index].diastolic,
-                                                    data[index].pulse,
-                                                    data[index].notes));
-                                              },
-                                            ),
-                                          ));
-                                        }
-                                        return dialogeDeletionConfirmed;
-                                      }
-                                    },
-                                    onDismissed: (direction) {},
-                                    background: Container(
-                                      width: 10,
-                                      decoration:
-                                          BoxDecoration(color: Colors.blue, borderRadius: BorderRadius.circular(5)),
-                                      child: const Align(alignment: Alignment(-0.95, 0), child: Icon(Icons.edit)),
-                                    ),
-                                    secondaryBackground: Container(
-                                      width: 10,
-                                      decoration:
-                                          BoxDecoration(color: Colors.red, borderRadius: BorderRadius.circular(5)),
-                                      child: const Align(alignment: Alignment(0.95, 0), child: Icon(Icons.delete)),
-                                    ),
-                                    child: Container(
-                                      constraints: const BoxConstraints(minHeight: 40),
-                                      child: Row(children: [
-                                        Expanded(
-                                          flex: _sideFlex,
-                                          child: const SizedBox(),
+                                    if (dialogeDeletionConfirmed) {
+                                      if (!context.mounted) return true;
+                                      ScaffoldMessenger.of(context).removeCurrentSnackBar();
+                                      ScaffoldMessenger.of(context).showSnackBar(SnackBar(
+                                        duration: const Duration(seconds: 5),
+                                        content: Text(AppLocalizations.of(context)!.deletionConfirmed),
+                                        action: SnackBarAction(
+                                          label: AppLocalizations.of(context)!.btnUndo,
+                                          onPressed: () async {
+                                            model.add(BloodPressureRecord(
+                                                data[index].creationTime,
+                                                data[index].systolic,
+                                                data[index].diastolic,
+                                                data[index].pulse,
+                                                data[index].notes));
+                                          },
                                         ),
-                                        Expanded(
-                                            flex: _tableElementsSizes[0],
-                                            child: Text(formatter.format(data[index].creationTime))),
-                                        Expanded(
-                                            flex: _tableElementsSizes[1], child: Text(data[index].systolic.toString())),
-                                        Expanded(
-                                            flex: _tableElementsSizes[2],
-                                            child: Text(data[index].diastolic.toString())),
-                                        Expanded(
-                                            flex: _tableElementsSizes[3], child: Text(data[index].pulse.toString())),
-                                        Expanded(flex: _tableElementsSizes[4], child: Text(data[index].notes)),
-                                        Expanded(
-                                          flex: _sideFlex,
-                                          child: const SizedBox(),
-                                        ),
-                                      ]),
+                                      ));
+                                    }
+                                    return dialogeDeletionConfirmed;
+                                  }
+                                },
+                                onDismissed: (direction) {},
+                                background: Container(
+                                  width: 10,
+                                  decoration:
+                                  BoxDecoration(color: Colors.blue, borderRadius: BorderRadius.circular(5)),
+                                  child: const Align(alignment: Alignment(-0.95, 0), child: Icon(Icons.edit)),
+                                ),
+                                secondaryBackground: Container(
+                                  width: 10,
+                                  decoration:
+                                  BoxDecoration(color: Colors.red, borderRadius: BorderRadius.circular(5)),
+                                  child: const Align(alignment: Alignment(0.95, 0), child: Icon(Icons.delete)),
+                                ),
+                                child: Container(
+                                  constraints: const BoxConstraints(minHeight: 40),
+                                  child: Row(children: [
+                                    Expanded(
+                                      flex: _sideFlex,
+                                      child: const SizedBox(),
+                                    ),
+                                    Expanded(
+                                        flex: _tableElementsSizes[0],
+                                        child: Text(formatter.format(data[index].creationTime))),
+                                    Expanded(
+                                        flex: _tableElementsSizes[1], child: Text(data[index].systolic.toString())),
+                                    Expanded(
+                                        flex: _tableElementsSizes[2],
+                                        child: Text(data[index].diastolic.toString())),
+                                    Expanded(
+                                        flex: _tableElementsSizes[3], child: Text(data[index].pulse.toString())),
+                                    Expanded(flex: _tableElementsSizes[4], child: Text(data[index].notes)),
+                                    Expanded(
+                                      flex: _sideFlex,
+                                      child: const SizedBox(),
                                     ),
-                                  ),
-                                  const Divider(
-                                    thickness: 1,
-                                    height: 1,
-                                  )
-                                ],
-                              );
-                            });
-                      } else {
-                        return Text(AppLocalizations.of(context)!.errNoData);
-                      }
-                    }
-                  });
+                                  ]),
+                                ),
+                              ),
+                              const Divider(
+                                thickness: 1,
+                                height: 1,
+                              )
+                            ],
+                          );
+                        });
+                  } else {
+                    return Text(AppLocalizations.of(context)!.errNoData);
+                  }
+                },
+              );
             });
           }),
         )
lib/screens/settings.dart
@@ -1,3 +1,4 @@
+import 'package:blood_pressure_app/components/consistent_future_builder.dart';
 import 'package:blood_pressure_app/components/settings_widgets.dart';
 import 'package:blood_pressure_app/model/settings_store.dart';
 import 'package:blood_pressure_app/screens/subsettings/enter_timeformat.dart';
@@ -259,34 +260,20 @@ class SettingsPage extends StatelessWidget {
               ],
             ),
             SettingsSection(title: const Text('about'), children: [
-              FutureBuilder<PackageInfo>(
-                future: PackageInfo.fromPlatform(),
-                builder: (context, snapshot) {
-                  String description = AppLocalizations.of(context)!.errNotStarted;
-                  switch (snapshot.connectionState) {
-                    case ConnectionState.waiting:
-                      description = AppLocalizations.of(context)!.loading;
-                      break;
-                    default:
-                      if (snapshot.hasError) {
-                        description = (AppLocalizations.of(context)!.error(snapshot.error.toString()));
-                      } else if (snapshot.hasData && snapshot.data != null) {
-                        description = snapshot.data!.version;
-                      }
+              SettingsTile(
+                  key: const Key('version'),
+                  title: Text(AppLocalizations.of(context)!.version),
+                  leading: const Icon(Icons.info_outline),
+                  description: ConsistentFutureBuilder<PackageInfo>(
+                    future: PackageInfo.fromPlatform(),
+                    onData: (context, info) => Text(info.version)
+                  ),
+                  onPressed: (context) {
+                    Navigator.push(
+                      context,
+                      MaterialPageRoute(builder: (context) => const VersionScreen()),
+                    );
                   }
-                  return SettingsTile(
-                      key: const Key('version'),
-                      title: Text(AppLocalizations.of(context)!.version),
-                      leading: const Icon(Icons.info_outline),
-                      description: Text(description),
-                      onPressed: (context) {
-                        Navigator.push(
-                          context,
-                          MaterialPageRoute(builder: (context) => const VersionScreen()),
-                        );
-                      }
-                  );
-                },
               ),
               SettingsTile(
                 key: const Key('sourceCode'),
lib/screens/statistics.dart
@@ -1,3 +1,4 @@
+import 'package:blood_pressure_app/components/consistent_future_builder.dart';
 import 'package:blood_pressure_app/model/blood_pressure.dart';
 import 'package:blood_pressure_app/model/blood_pressure_analyzer.dart';
 import 'package:blood_pressure_app/model/settings_store.dart';
@@ -82,64 +83,51 @@ class StatisticsPage extends StatelessWidget {
                 // Time-Resolved Metrics
                 Statistic(
                   caption: Text(AppLocalizations.of(context)!.timeResolvedMetrics),
-                  child: FutureBuilder<List<List<int>>>(
+                  child: ConsistentFutureBuilder<List<List<int>>>(
                       future: BloodPressureAnalyser(model).allAvgsRelativeToDaytime,
-                      builder: (BuildContext context, AsyncSnapshot<List<List<int>>> snapshot) {
-                        switch (snapshot.connectionState) {
-                          case ConnectionState.none:
-                            return Text(AppLocalizations.of(context)!.errNotStarted);
-                          case ConnectionState.waiting:
-                            return Text(AppLocalizations.of(context)!.loading);
-                          default:
-                            if (snapshot.hasError) {
-                              return Text(AppLocalizations.of(context)!.error(snapshot.error.toString()));
-                            }
-                            assert(snapshot.hasData);
-                            assert(snapshot.data != null);
-                            final daytimeAvgs = snapshot.data ?? [];
-                            const opacity = 0.5;
-                            return SizedBox(
-                              width: 500,
-                              height: 270,
-                              child: RadarChart(
-                                RadarChartData(
-                                  radarShape: RadarShape.circle,
-                                  radarBorderData: const BorderSide(color: Colors.transparent),
-                                  gridBorderData: BorderSide(color: Theme.of(context).dividerColor, width: 2),
-                                  tickBorderData: BorderSide(color: Theme.of(context).dividerColor, width: 2),
-                                  ticksTextStyle: const TextStyle(color: Colors.transparent),
-                                  tickCount: 5,
-                                  titleTextStyle: const TextStyle(fontSize: 25),
-                                  getTitle: (pos, value) {
-                                    if (pos % 2 == 0) {
-                                      return RadarChartTitle(text: '$pos', positionPercentageOffset: 0.05);
-                                    }
-                                    return const RadarChartTitle(text: '');
-                                  },
-                                  dataSets: [
-                                    RadarDataSet(
-                                        dataEntries: intListToRadarEntry(daytimeAvgs[0]),
-                                        borderColor: settings.diaColor,
-                                        fillColor: settings.diaColor.withOpacity(opacity),
-                                        entryRadius: 0,
-                                        borderWidth: settings.graphLineThickness),
-                                    RadarDataSet(
-                                        dataEntries: intListToRadarEntry(daytimeAvgs[1]),
-                                        borderColor: settings.sysColor,
-                                        fillColor: settings.sysColor.withOpacity(opacity),
-                                        entryRadius: 0,
-                                        borderWidth: settings.graphLineThickness),
-                                    RadarDataSet(
-                                        dataEntries: intListToRadarEntry(daytimeAvgs[2]),
-                                        borderColor: settings.pulColor,
-                                        fillColor: settings.pulColor.withOpacity(opacity),
-                                        entryRadius: 0,
-                                        borderWidth: settings.graphLineThickness),
-                                  ],
-                                ),
-                              ),
-                            );
-                        }
+                      onData: (context, data) {
+                        const opacity = 0.5;
+                        return SizedBox(
+                          width: 500,
+                          height: 270,
+                          child: RadarChart(
+                            RadarChartData(
+                              radarShape: RadarShape.circle,
+                              radarBorderData: const BorderSide(color: Colors.transparent),
+                              gridBorderData: BorderSide(color: Theme.of(context).dividerColor, width: 2),
+                              tickBorderData: BorderSide(color: Theme.of(context).dividerColor, width: 2),
+                              ticksTextStyle: const TextStyle(color: Colors.transparent),
+                              tickCount: 5,
+                              titleTextStyle: const TextStyle(fontSize: 25),
+                              getTitle: (pos, value) {
+                                if (pos % 2 == 0) {
+                                  return RadarChartTitle(text: '$pos', positionPercentageOffset: 0.05);
+                                }
+                                return const RadarChartTitle(text: '');
+                              },
+                              dataSets: [
+                                RadarDataSet(
+                                    dataEntries: intListToRadarEntry(data[0]),
+                                    borderColor: settings.diaColor,
+                                    fillColor: settings.diaColor.withOpacity(opacity),
+                                    entryRadius: 0,
+                                    borderWidth: settings.graphLineThickness),
+                                RadarDataSet(
+                                    dataEntries: intListToRadarEntry(data[1]),
+                                    borderColor: settings.sysColor,
+                                    fillColor: settings.sysColor.withOpacity(opacity),
+                                    entryRadius: 0,
+                                    borderWidth: settings.graphLineThickness),
+                                RadarDataSet(
+                                    dataEntries: intListToRadarEntry(data[2]),
+                                    borderColor: settings.pulColor,
+                                    fillColor: settings.pulColor.withOpacity(opacity),
+                                    entryRadius: 0,
+                                    borderWidth: settings.graphLineThickness),
+                              ],
+                            ),
+                          ),
+                        );
                       }),
                 ),
               ],
@@ -267,23 +255,13 @@ class StatisticsRow extends StatelessWidget {
 }
 
 Widget futureInt(Future<int> value) {
-  return FutureBuilder<int>(
+  return ConsistentFutureBuilder<int>(
       future: value,
-      builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
-        switch (snapshot.connectionState) {
-          case ConnectionState.none:
-            return Text(AppLocalizations.of(context)!.errNotStarted);
-          case ConnectionState.waiting:
-            return Text(AppLocalizations.of(context)!.loading);
-          default:
-            if (snapshot.hasError) {
-              return Text(AppLocalizations.of(context)!.error(snapshot.error.toString()));
-            }
-            assert(snapshot.hasData);
-            if ((snapshot.data ?? -1) < 0) {
-              return const Text('-');
-            }
-            return Text(snapshot.data.toString());
+      onData: (context, data) {
+        if (data < 0) {
+          return const Text('-');
         }
-      });
+        return Text(data.toString());
+      }
+  );
 }