implement time period validation for bolus/basal profiles
This commit is contained in:
		
							parent
							
								
									68927522db
								
							
						
					
					
						commit
						db023a94cf
					
				
					 9 changed files with 407 additions and 242 deletions
				
			
		| 
						 | 
					@ -4,7 +4,6 @@ import 'package:diameter/config.dart';
 | 
				
			||||||
import 'package:diameter/navigation.dart';
 | 
					import 'package:diameter/navigation.dart';
 | 
				
			||||||
import 'package:diameter/utils/date_time_utils.dart';
 | 
					import 'package:diameter/utils/date_time_utils.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:flutter/services.dart';
 | 
					 | 
				
			||||||
import 'package:diameter/components/forms.dart';
 | 
					import 'package:diameter/components/forms.dart';
 | 
				
			||||||
import 'package:diameter/models/basal.dart';
 | 
					import 'package:diameter/models/basal.dart';
 | 
				
			||||||
import 'package:diameter/models/basal_profile.dart';
 | 
					import 'package:diameter/models/basal_profile.dart';
 | 
				
			||||||
| 
						 | 
					@ -116,6 +115,9 @@ class _BasalDetailScreenState extends State<BasalDetailScreen> {
 | 
				
			||||||
              Row(
 | 
					              Row(
 | 
				
			||||||
                children: [
 | 
					                children: [
 | 
				
			||||||
                  Expanded(
 | 
					                  Expanded(
 | 
				
			||||||
 | 
					                    // TODO fix handling of time zones!
 | 
				
			||||||
 | 
					                    child: Padding(
 | 
				
			||||||
 | 
					                      padding: const EdgeInsets.only(right: 5),
 | 
				
			||||||
                      child: StyledTimeOfDayFormField(
 | 
					                      child: StyledTimeOfDayFormField(
 | 
				
			||||||
                        label: 'Start Time',
 | 
					                        label: 'Start Time',
 | 
				
			||||||
                        controller: _startTimeController,
 | 
					                        controller: _startTimeController,
 | 
				
			||||||
| 
						 | 
					@ -130,7 +132,10 @@ class _BasalDetailScreenState extends State<BasalDetailScreen> {
 | 
				
			||||||
                        },
 | 
					                        },
 | 
				
			||||||
                      ),
 | 
					                      ),
 | 
				
			||||||
                    ),
 | 
					                    ),
 | 
				
			||||||
 | 
					                  ),
 | 
				
			||||||
                  Expanded(
 | 
					                  Expanded(
 | 
				
			||||||
 | 
					                    child: Padding(
 | 
				
			||||||
 | 
					                      padding: const EdgeInsets.only(left: 5),
 | 
				
			||||||
                      child: StyledTimeOfDayFormField(
 | 
					                      child: StyledTimeOfDayFormField(
 | 
				
			||||||
                        label: 'End Time',
 | 
					                        label: 'End Time',
 | 
				
			||||||
                        controller: _endTimeController,
 | 
					                        controller: _endTimeController,
 | 
				
			||||||
| 
						 | 
					@ -145,6 +150,7 @@ class _BasalDetailScreenState extends State<BasalDetailScreen> {
 | 
				
			||||||
                        },
 | 
					                        },
 | 
				
			||||||
                      ),
 | 
					                      ),
 | 
				
			||||||
                    ),
 | 
					                    ),
 | 
				
			||||||
 | 
					                  ),
 | 
				
			||||||
                ],
 | 
					                ],
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
              TextFormField(
 | 
					              TextFormField(
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,6 @@
 | 
				
			||||||
import 'package:diameter/components/dialogs.dart';
 | 
					import 'package:diameter/components/dialogs.dart';
 | 
				
			||||||
import 'package:diameter/config.dart';
 | 
					import 'package:diameter/config.dart';
 | 
				
			||||||
 | 
					import 'package:diameter/utils/date_time_utils.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:diameter/components/progress_indicator.dart';
 | 
					import 'package:diameter/components/progress_indicator.dart';
 | 
				
			||||||
import 'package:diameter/models/basal.dart';
 | 
					import 'package:diameter/models/basal.dart';
 | 
				
			||||||
| 
						 | 
					@ -64,6 +65,44 @@ class _BasalListScreenState extends State<BasalListScreen> {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  String? checkBasalValidity(List<Basal> basalRates, int index) {
 | 
				
			||||||
 | 
					    Basal basal = basalRates[index];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // check for gaps
 | 
				
			||||||
 | 
					    if (index == 0 &&
 | 
				
			||||||
 | 
					        (basal.startTime.toLocal().hour != 0 || basal.startTime.minute != 0)) {
 | 
				
			||||||
 | 
					      return 'First Basal of the day needs to start at 00:00';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (index > 0) {
 | 
				
			||||||
 | 
					      var lastEndTime = basalRates[index - 1].endTime;
 | 
				
			||||||
 | 
					      if (basal.startTime.isAfter(lastEndTime)) {
 | 
				
			||||||
 | 
					        return 'There\'s a time gap between this and the previous rate';
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (index == basalRates.length - 1 &&
 | 
				
			||||||
 | 
					        (basal.endTime.toLocal().hour != 0 || basal.endTime.minute != 0)) {
 | 
				
			||||||
 | 
					      return 'Last Basal of the day needs to end at 00:00';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // check for duplicates
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (basalRates
 | 
				
			||||||
 | 
					        .where((other) => basal != other && basal.startTime == other.startTime)
 | 
				
			||||||
 | 
					        .isNotEmpty) {
 | 
				
			||||||
 | 
					      return 'There are multiple rates with this start time';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (basalRates
 | 
				
			||||||
 | 
					        .where((other) =>
 | 
				
			||||||
 | 
					            basal.startTime.isBefore(other.startTime) &&
 | 
				
			||||||
 | 
					            basal.endTime.isAfter(other.startTime))
 | 
				
			||||||
 | 
					        .isNotEmpty) {
 | 
				
			||||||
 | 
					      return 'This rate\'s time period overlaps with another one';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  void initState() {
 | 
					  void initState() {
 | 
				
			||||||
    super.initState();
 | 
					    super.initState();
 | 
				
			||||||
| 
						 | 
					@ -73,51 +112,64 @@ class _BasalListScreenState extends State<BasalListScreen> {
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  Widget build(BuildContext context) {
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
    return SingleChildScrollView(
 | 
					    return SingleChildScrollView(
 | 
				
			||||||
      padding: const EdgeInsets.only(top: 10.0),
 | 
					 | 
				
			||||||
      child: Column(
 | 
					      child: Column(
 | 
				
			||||||
        children: [
 | 
					        children: [
 | 
				
			||||||
          FutureBuilder<List<Basal>>(
 | 
					          FutureBuilder<List<Basal>>(
 | 
				
			||||||
            future: widget.basalProfile!.basalRates,
 | 
					            future: widget.basalProfile!.basalRates,
 | 
				
			||||||
            builder: (context, snapshot) {
 | 
					            builder: (context, snapshot) {
 | 
				
			||||||
              return ViewWithProgressIndicator(
 | 
					              return ViewWithProgressIndicator(
 | 
				
			||||||
                // TODO: add warning if time period is missing or has multiple rates
 | 
					 | 
				
			||||||
                snapshot: snapshot,
 | 
					                snapshot: snapshot,
 | 
				
			||||||
                child: snapshot.data == null || snapshot.data!.isEmpty
 | 
					                child: snapshot.data == null || snapshot.data!.isEmpty
 | 
				
			||||||
                    ? const Padding(
 | 
					                    ? const Padding(
 | 
				
			||||||
                        padding: EdgeInsets.all(10.0),
 | 
					                        padding: EdgeInsets.all(10.0),
 | 
				
			||||||
                        child: Text('No Basal Rates for this Profile'),
 | 
					                        child: Text('No Basal Rates for this Profile'),
 | 
				
			||||||
                      )
 | 
					                      )
 | 
				
			||||||
                    : ListBody(
 | 
					                    : ListView.builder(
 | 
				
			||||||
 | 
					                        shrinkWrap: true,
 | 
				
			||||||
 | 
					                        itemCount:
 | 
				
			||||||
 | 
					                            snapshot.data != null ? snapshot.data!.length : 0,
 | 
				
			||||||
 | 
					                        itemBuilder: (context, index) {
 | 
				
			||||||
 | 
					                          final basal = snapshot.data![index];
 | 
				
			||||||
 | 
					                          final error =
 | 
				
			||||||
 | 
					                              checkBasalValidity(snapshot.data!, index);
 | 
				
			||||||
 | 
					                          return ListTile(
 | 
				
			||||||
 | 
					                            tileColor:
 | 
				
			||||||
 | 
					                                error != null ? Colors.red.shade100 : null,
 | 
				
			||||||
 | 
					                            onTap: () {
 | 
				
			||||||
 | 
					                              handleEditAction(basal);
 | 
				
			||||||
 | 
					                            },
 | 
				
			||||||
 | 
					                            title: Row(
 | 
				
			||||||
 | 
					                              mainAxisSize: MainAxisSize.max,
 | 
				
			||||||
                              children: [
 | 
					                              children: [
 | 
				
			||||||
                          DataTable(
 | 
					                                Expanded(
 | 
				
			||||||
                            columnSpacing: 10.0,
 | 
					                                    child: Text(
 | 
				
			||||||
                            showCheckboxColumn: false,
 | 
					                                        '${DateTimeUtils.displayTime(basal.startTime)} - ${DateTimeUtils.displayTime(basal.endTime)}')),
 | 
				
			||||||
                            rows: snapshot.data != null
 | 
					                                const Spacer(),
 | 
				
			||||||
                                ? snapshot.data!.map((basal) {
 | 
					                                Expanded(child: Text('${basal.units} U')),
 | 
				
			||||||
                                    return DataRow(
 | 
					                              ],
 | 
				
			||||||
                                      cells: basal.asDataTableCells([
 | 
					 | 
				
			||||||
                                        IconButton(
 | 
					 | 
				
			||||||
                                            icon: const Icon(Icons.edit),
 | 
					 | 
				
			||||||
                                            iconSize: 16.0,
 | 
					 | 
				
			||||||
                                            onPressed: () =>
 | 
					 | 
				
			||||||
                                                handleEditAction(basal)),
 | 
					 | 
				
			||||||
                                        IconButton(
 | 
					 | 
				
			||||||
                                          icon: const Icon(Icons.delete),
 | 
					 | 
				
			||||||
                                          iconSize: 16.0,
 | 
					 | 
				
			||||||
                                          onPressed: () =>
 | 
					 | 
				
			||||||
                                              handleDeleteAction(basal),
 | 
					 | 
				
			||||||
                            ),
 | 
					                            ),
 | 
				
			||||||
                                      ]),
 | 
					                            subtitle: error != null
 | 
				
			||||||
                                    );
 | 
					                                ? Text(error,
 | 
				
			||||||
                                  }).toList()
 | 
					                                    style: const TextStyle(color: Colors.red))
 | 
				
			||||||
                                : [],
 | 
					                                : Container(),
 | 
				
			||||||
                            columns: Basal.asDataTableColumns(),
 | 
					                            trailing: Row(
 | 
				
			||||||
 | 
					                              mainAxisSize: MainAxisSize.min,
 | 
				
			||||||
 | 
					                              children: [
 | 
				
			||||||
 | 
					                                IconButton(
 | 
				
			||||||
 | 
					                                  icon: const Icon(
 | 
				
			||||||
 | 
					                                    Icons.delete,
 | 
				
			||||||
 | 
					                                    color: Colors.blue,
 | 
				
			||||||
 | 
					                                  ),
 | 
				
			||||||
 | 
					                                  onPressed: () => handleDeleteAction(basal),
 | 
				
			||||||
                                ),
 | 
					                                ),
 | 
				
			||||||
                              ],
 | 
					                              ],
 | 
				
			||||||
                            ),
 | 
					                            ),
 | 
				
			||||||
                          );
 | 
					                          );
 | 
				
			||||||
                        },
 | 
					                        },
 | 
				
			||||||
                      ),
 | 
					                      ),
 | 
				
			||||||
 | 
					              );
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -227,7 +227,7 @@ class _BasalProfileDetailScreenState extends State<BasalProfileDetailScreen> {
 | 
				
			||||||
  Widget build(BuildContext context) {
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
    bool isNew = widget.basalProfile == null;
 | 
					    bool isNew = widget.basalProfile == null;
 | 
				
			||||||
    return DefaultTabController(
 | 
					    return DefaultTabController(
 | 
				
			||||||
      length: 2,
 | 
					      length: isNew ? 1 : 2,
 | 
				
			||||||
      child: Builder(builder: (BuildContext context) {
 | 
					      child: Builder(builder: (BuildContext context) {
 | 
				
			||||||
        final TabController tabController = DefaultTabController.of(context)!;
 | 
					        final TabController tabController = DefaultTabController.of(context)!;
 | 
				
			||||||
        tabController.addListener(() {
 | 
					        tabController.addListener(() {
 | 
				
			||||||
| 
						 | 
					@ -235,24 +235,7 @@ class _BasalProfileDetailScreenState extends State<BasalProfileDetailScreen> {
 | 
				
			||||||
            renderTabButtons(tabController.index);
 | 
					            renderTabButtons(tabController.index);
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        return Scaffold(
 | 
					        List<Widget> tabs = [
 | 
				
			||||||
          appBar: AppBar(
 | 
					 | 
				
			||||||
            title:
 | 
					 | 
				
			||||||
                Text(isNew ? 'New Basal Profile' : widget.basalProfile!.name),
 | 
					 | 
				
			||||||
            bottom: isNew
 | 
					 | 
				
			||||||
                ? PreferredSize(child: Container(), preferredSize: Size.zero)
 | 
					 | 
				
			||||||
                : const TabBar(
 | 
					 | 
				
			||||||
                    tabs: [
 | 
					 | 
				
			||||||
                      Tab(text: 'PROFILE'),
 | 
					 | 
				
			||||||
                      Tab(text: 'RATES'),
 | 
					 | 
				
			||||||
                    ],
 | 
					 | 
				
			||||||
                  ),
 | 
					 | 
				
			||||||
            actions: appBarActions,
 | 
					 | 
				
			||||||
          ),
 | 
					 | 
				
			||||||
          drawer: const Navigation(
 | 
					 | 
				
			||||||
              currentLocation: BasalProfileDetailScreen.routeName),
 | 
					 | 
				
			||||||
          body: TabBarView(
 | 
					 | 
				
			||||||
            children: [
 | 
					 | 
				
			||||||
          SingleChildScrollView(
 | 
					          SingleChildScrollView(
 | 
				
			||||||
            child: Column(
 | 
					            child: Column(
 | 
				
			||||||
              crossAxisAlignment: CrossAxisAlignment.stretch,
 | 
					              crossAxisAlignment: CrossAxisAlignment.stretch,
 | 
				
			||||||
| 
						 | 
					@ -295,9 +278,29 @@ class _BasalProfileDetailScreenState extends State<BasalProfileDetailScreen> {
 | 
				
			||||||
              ],
 | 
					              ],
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
              BasalListScreen(basalProfile: widget.basalProfile),
 | 
					        ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!isNew) {
 | 
				
			||||||
 | 
					          tabs.add(BasalListScreen(basalProfile: widget.basalProfile));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return Scaffold(
 | 
				
			||||||
 | 
					          appBar: AppBar(
 | 
				
			||||||
 | 
					            title:
 | 
				
			||||||
 | 
					                Text(isNew ? 'New Basal Profile' : widget.basalProfile!.name),
 | 
				
			||||||
 | 
					            bottom: isNew
 | 
				
			||||||
 | 
					                ? PreferredSize(child: Container(), preferredSize: Size.zero)
 | 
				
			||||||
 | 
					                : const TabBar(
 | 
				
			||||||
 | 
					                    tabs: [
 | 
				
			||||||
 | 
					                      Tab(text: 'PROFILE'),
 | 
				
			||||||
 | 
					                      Tab(text: 'RATES'),
 | 
				
			||||||
                    ],
 | 
					                    ],
 | 
				
			||||||
                  ),
 | 
					                  ),
 | 
				
			||||||
 | 
					            actions: appBarActions,
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					          drawer: const Navigation(
 | 
				
			||||||
 | 
					              currentLocation: BasalProfileDetailScreen.routeName),
 | 
				
			||||||
 | 
					          body: TabBarView(children: tabs),
 | 
				
			||||||
          bottomNavigationBar: DetailBottomRow(
 | 
					          bottomNavigationBar: DetailBottomRow(
 | 
				
			||||||
            onCancel: handleCancelAction,
 | 
					            onCancel: handleCancelAction,
 | 
				
			||||||
            onSave: handleSaveAction,
 | 
					            onSave: handleSaveAction,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,11 +6,9 @@ import 'package:diameter/settings.dart';
 | 
				
			||||||
import 'package:diameter/utils/date_time_utils.dart';
 | 
					import 'package:diameter/utils/date_time_utils.dart';
 | 
				
			||||||
import 'package:diameter/utils/utils.dart';
 | 
					import 'package:diameter/utils/utils.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:flutter/services.dart';
 | 
					 | 
				
			||||||
import 'package:diameter/components/forms.dart';
 | 
					import 'package:diameter/components/forms.dart';
 | 
				
			||||||
import 'package:diameter/models/bolus.dart';
 | 
					import 'package:diameter/models/bolus.dart';
 | 
				
			||||||
import 'package:diameter/models/bolus_profile.dart';
 | 
					import 'package:diameter/models/bolus_profile.dart';
 | 
				
			||||||
import 'package:flutter/widgets.dart';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BolusDetailScreen extends StatefulWidget {
 | 
					class BolusDetailScreen extends StatefulWidget {
 | 
				
			||||||
  static const String routeName = '/bolus';
 | 
					  static const String routeName = '/bolus';
 | 
				
			||||||
| 
						 | 
					@ -31,12 +29,12 @@ class _BolusDetailScreenState extends State<BolusDetailScreen> {
 | 
				
			||||||
  TimeOfDay _startTime = const TimeOfDay(hour: 0, minute: 0);
 | 
					  TimeOfDay _startTime = const TimeOfDay(hour: 0, minute: 0);
 | 
				
			||||||
  TimeOfDay _endTime = const TimeOfDay(hour: 0, minute: 0);
 | 
					  TimeOfDay _endTime = const TimeOfDay(hour: 0, minute: 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  final _startTimeController = TextEditingController();
 | 
					  final _startTimeController = TextEditingController(text: '');
 | 
				
			||||||
  final _endTimeController = TextEditingController();
 | 
					  final _endTimeController = TextEditingController(text: '');
 | 
				
			||||||
  final _unitsController = TextEditingController();
 | 
					  final _unitsController = TextEditingController(text: '');
 | 
				
			||||||
  final _carbsController = TextEditingController();
 | 
					  final _carbsController = TextEditingController(text: '');
 | 
				
			||||||
  final _mgPerDlController = TextEditingController();
 | 
					  final _mgPerDlController = TextEditingController(text: '');
 | 
				
			||||||
  final _mmolPerLController = TextEditingController();
 | 
					  final _mmolPerLController = TextEditingController(text: '');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  void initState() {
 | 
					  void initState() {
 | 
				
			||||||
| 
						 | 
					@ -136,7 +134,6 @@ class _BolusDetailScreenState extends State<BolusDetailScreen> {
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void convertBetweenMgPerDlAndMmolPerL({GlucoseMeasurement? calculateFrom}) {
 | 
					  void convertBetweenMgPerDlAndMmolPerL({GlucoseMeasurement? calculateFrom}) {
 | 
				
			||||||
    // TODO figure out why this isnt happening automatically
 | 
					 | 
				
			||||||
    int? mgPerDl;
 | 
					    int? mgPerDl;
 | 
				
			||||||
    double? mmolPerL;
 | 
					    double? mmolPerL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -182,6 +179,9 @@ class _BolusDetailScreenState extends State<BolusDetailScreen> {
 | 
				
			||||||
                Row(
 | 
					                Row(
 | 
				
			||||||
                  children: [
 | 
					                  children: [
 | 
				
			||||||
                    Expanded(
 | 
					                    Expanded(
 | 
				
			||||||
 | 
					                      child: Padding(
 | 
				
			||||||
 | 
					                        padding: const EdgeInsets.only(right: 5),
 | 
				
			||||||
 | 
					                        // TODO fix handling of time zones!
 | 
				
			||||||
                        child: StyledTimeOfDayFormField(
 | 
					                        child: StyledTimeOfDayFormField(
 | 
				
			||||||
                          label: 'Start Time',
 | 
					                          label: 'Start Time',
 | 
				
			||||||
                          controller: _startTimeController,
 | 
					                          controller: _startTimeController,
 | 
				
			||||||
| 
						 | 
					@ -196,7 +196,10 @@ class _BolusDetailScreenState extends State<BolusDetailScreen> {
 | 
				
			||||||
                          },
 | 
					                          },
 | 
				
			||||||
                        ),
 | 
					                        ),
 | 
				
			||||||
                      ),
 | 
					                      ),
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
                    Expanded(
 | 
					                    Expanded(
 | 
				
			||||||
 | 
					                      child: Padding(
 | 
				
			||||||
 | 
					                        padding: const EdgeInsets.only(left: 5),
 | 
				
			||||||
                        child: StyledTimeOfDayFormField(
 | 
					                        child: StyledTimeOfDayFormField(
 | 
				
			||||||
                          label: 'End Time',
 | 
					                          label: 'End Time',
 | 
				
			||||||
                          controller: _endTimeController,
 | 
					                          controller: _endTimeController,
 | 
				
			||||||
| 
						 | 
					@ -211,6 +214,7 @@ class _BolusDetailScreenState extends State<BolusDetailScreen> {
 | 
				
			||||||
                          },
 | 
					                          },
 | 
				
			||||||
                        ),
 | 
					                        ),
 | 
				
			||||||
                      ),
 | 
					                      ),
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
                  ],
 | 
					                  ],
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
                TextFormField(
 | 
					                TextFormField(
 | 
				
			||||||
| 
						 | 
					@ -262,7 +266,9 @@ class _BolusDetailScreenState extends State<BolusDetailScreen> {
 | 
				
			||||||
                              ),
 | 
					                              ),
 | 
				
			||||||
                              controller: _mgPerDlController,
 | 
					                              controller: _mgPerDlController,
 | 
				
			||||||
                              onChanged: (_) =>
 | 
					                              onChanged: (_) =>
 | 
				
			||||||
                                  convertBetweenMgPerDlAndMmolPerL,
 | 
					                                  convertBetweenMgPerDlAndMmolPerL(
 | 
				
			||||||
 | 
					                                      calculateFrom:
 | 
				
			||||||
 | 
					                                          GlucoseMeasurement.mgPerDl),
 | 
				
			||||||
                              keyboardType:
 | 
					                              keyboardType:
 | 
				
			||||||
                                  const TextInputType.numberWithOptions(),
 | 
					                                  const TextInputType.numberWithOptions(),
 | 
				
			||||||
                              validator: (value) {
 | 
					                              validator: (value) {
 | 
				
			||||||
| 
						 | 
					@ -296,7 +302,9 @@ class _BolusDetailScreenState extends State<BolusDetailScreen> {
 | 
				
			||||||
                              ),
 | 
					                              ),
 | 
				
			||||||
                              controller: _mmolPerLController,
 | 
					                              controller: _mmolPerLController,
 | 
				
			||||||
                              onChanged: (_) =>
 | 
					                              onChanged: (_) =>
 | 
				
			||||||
                                  convertBetweenMgPerDlAndMmolPerL,
 | 
					                                  convertBetweenMgPerDlAndMmolPerL(
 | 
				
			||||||
 | 
					                                      calculateFrom:
 | 
				
			||||||
 | 
					                                          GlucoseMeasurement.mmolPerL),
 | 
				
			||||||
                              keyboardType:
 | 
					                              keyboardType:
 | 
				
			||||||
                                  const TextInputType.numberWithOptions(
 | 
					                                  const TextInputType.numberWithOptions(
 | 
				
			||||||
                                      decimal: true),
 | 
					                                      decimal: true),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,7 @@
 | 
				
			||||||
import 'package:diameter/components/dialogs.dart';
 | 
					import 'package:diameter/components/dialogs.dart';
 | 
				
			||||||
import 'package:diameter/config.dart';
 | 
					import 'package:diameter/config.dart';
 | 
				
			||||||
 | 
					import 'package:diameter/settings.dart';
 | 
				
			||||||
 | 
					import 'package:diameter/utils/date_time_utils.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:diameter/components/progress_indicator.dart';
 | 
					import 'package:diameter/components/progress_indicator.dart';
 | 
				
			||||||
import 'package:diameter/models/bolus.dart';
 | 
					import 'package:diameter/models/bolus.dart';
 | 
				
			||||||
| 
						 | 
					@ -64,6 +66,44 @@ class _BolusListScreenState extends State<BolusListScreen> {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  String? checkBolusValidity(List<Bolus> bolusRates, int index) {
 | 
				
			||||||
 | 
					    Bolus bolus = bolusRates[index];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // check for gaps
 | 
				
			||||||
 | 
					    if (index == 0 &&
 | 
				
			||||||
 | 
					        (bolus.startTime.toLocal().hour != 0 || bolus.startTime.minute != 0)) {
 | 
				
			||||||
 | 
					      return 'First Bolus of the day needs to start at 00:00';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (index > 0) {
 | 
				
			||||||
 | 
					      var lastEndTime = bolusRates[index - 1].endTime;
 | 
				
			||||||
 | 
					      if (bolus.startTime.isAfter(lastEndTime)) {
 | 
				
			||||||
 | 
					        return 'There\'s a time gap between this and the previous rate';
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (index == bolusRates.length - 1 &&
 | 
				
			||||||
 | 
					        (bolus.endTime.toLocal().hour != 0 || bolus.endTime.minute != 0)) {
 | 
				
			||||||
 | 
					      return 'Last Bolus of the day needs to end at 00:00';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // check for duplicates
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (bolusRates
 | 
				
			||||||
 | 
					        .where((other) => bolus != other && bolus.startTime == other.startTime)
 | 
				
			||||||
 | 
					        .isNotEmpty) {
 | 
				
			||||||
 | 
					      return 'There are multiple rates with this start time';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (bolusRates
 | 
				
			||||||
 | 
					        .where((other) =>
 | 
				
			||||||
 | 
					            bolus.startTime.isBefore(other.startTime) &&
 | 
				
			||||||
 | 
					            bolus.endTime.isAfter(other.startTime))
 | 
				
			||||||
 | 
					        .isNotEmpty) {
 | 
				
			||||||
 | 
					      return 'This rate\'s time period overlaps with another one';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  void initState() {
 | 
					  void initState() {
 | 
				
			||||||
    super.initState();
 | 
					    super.initState();
 | 
				
			||||||
| 
						 | 
					@ -80,41 +120,58 @@ class _BolusListScreenState extends State<BolusListScreen> {
 | 
				
			||||||
            future: widget.bolusProfile!.bolusRates,
 | 
					            future: widget.bolusProfile!.bolusRates,
 | 
				
			||||||
            builder: (context, snapshot) {
 | 
					            builder: (context, snapshot) {
 | 
				
			||||||
              return ViewWithProgressIndicator(
 | 
					              return ViewWithProgressIndicator(
 | 
				
			||||||
                // TODO: add warning if time period is missing or has multiple rates
 | 
					 | 
				
			||||||
                snapshot: snapshot,
 | 
					                snapshot: snapshot,
 | 
				
			||||||
                child: snapshot.data == null || snapshot.data!.isEmpty
 | 
					                child: snapshot.data == null || snapshot.data!.isEmpty
 | 
				
			||||||
                    ? const Padding(
 | 
					                    ? const Padding(
 | 
				
			||||||
                        padding: EdgeInsets.all(10.0),
 | 
					                        padding: EdgeInsets.all(10.0),
 | 
				
			||||||
                        child: Text('No Bolus Rates for this Profile'),
 | 
					                        child: Text('No Basal Rates for this Profile'),
 | 
				
			||||||
                      )
 | 
					                      )
 | 
				
			||||||
                    : ListBody(
 | 
					                    : ListView.builder(
 | 
				
			||||||
 | 
					                        shrinkWrap: true,
 | 
				
			||||||
 | 
					                        itemCount:
 | 
				
			||||||
 | 
					                            snapshot.data != null ? snapshot.data!.length : 0,
 | 
				
			||||||
 | 
					                        itemBuilder: (context, index) {
 | 
				
			||||||
 | 
					                          final bolus = snapshot.data![index];
 | 
				
			||||||
 | 
					                          final error =
 | 
				
			||||||
 | 
					                              checkBolusValidity(snapshot.data!, index);
 | 
				
			||||||
 | 
					                          return ListTile(
 | 
				
			||||||
 | 
					                            tileColor:
 | 
				
			||||||
 | 
					                                error != null ? Colors.red.shade100 : null,
 | 
				
			||||||
 | 
					                            onTap: () {
 | 
				
			||||||
 | 
					                              handleEditAction(bolus);
 | 
				
			||||||
 | 
					                            },
 | 
				
			||||||
 | 
					                            title: Row(
 | 
				
			||||||
 | 
					                              mainAxisSize: MainAxisSize.max,
 | 
				
			||||||
 | 
					                              children: [
 | 
				
			||||||
 | 
					                                Expanded(
 | 
				
			||||||
 | 
					                                    child: Text(
 | 
				
			||||||
 | 
					                                        '${DateTimeUtils.displayTime(bolus.startTime)} - ${DateTimeUtils.displayTime(bolus.endTime)}')),
 | 
				
			||||||
 | 
					                                // TODO: style this
 | 
				
			||||||
 | 
					                                Expanded(
 | 
				
			||||||
 | 
					                                  child: Text(
 | 
				
			||||||
 | 
					                                      '${bolus.units} U per ${bolus.carbs}${nutritionMeasurement == NutritionMeasurement.grams ? ' g' : ' oz'} carbs/${glucoseMeasurement == GlucoseMeasurement.mgPerDl ? bolus.mgPerDl : bolus.mmolPerL} ${glucoseMeasurement == GlucoseMeasurement.mgPerDl ? 'mg/dl' : 'mmol/l'}',
 | 
				
			||||||
 | 
					                                      style: const TextStyle(fontSize: 12.0)),
 | 
				
			||||||
 | 
					                                ),
 | 
				
			||||||
 | 
					                              ],
 | 
				
			||||||
 | 
					                            ),
 | 
				
			||||||
 | 
					                            subtitle: error != null
 | 
				
			||||||
 | 
					                                ? Text(error,
 | 
				
			||||||
 | 
					                                    style: const TextStyle(color: Colors.red))
 | 
				
			||||||
 | 
					                                : Container(),
 | 
				
			||||||
 | 
					                            trailing: Row(
 | 
				
			||||||
 | 
					                              mainAxisSize: MainAxisSize.min,
 | 
				
			||||||
                              children: [
 | 
					                              children: [
 | 
				
			||||||
                          DataTable(
 | 
					 | 
				
			||||||
                            columnSpacing: 10.0,
 | 
					 | 
				
			||||||
                            showCheckboxColumn: false,
 | 
					 | 
				
			||||||
                            rows: snapshot.data != null
 | 
					 | 
				
			||||||
                                ? snapshot.data!.map((bolus) {
 | 
					 | 
				
			||||||
                                    return DataRow(
 | 
					 | 
				
			||||||
                                      cells: bolus.asDataTableCells(
 | 
					 | 
				
			||||||
                                        [
 | 
					 | 
				
			||||||
                                IconButton(
 | 
					                                IconButton(
 | 
				
			||||||
                                              icon: const Icon(Icons.edit),
 | 
					                                  icon: const Icon(
 | 
				
			||||||
                                              iconSize: 16.0,
 | 
					                                    Icons.delete,
 | 
				
			||||||
                                              onPressed: () =>
 | 
					                                    color: Colors.blue,
 | 
				
			||||||
                                                  handleEditAction(bolus)),
 | 
					                                  ),
 | 
				
			||||||
                                          IconButton(
 | 
					                                  onPressed: () => handleDeleteAction(bolus),
 | 
				
			||||||
                                              icon: const Icon(Icons.delete),
 | 
					                                ),
 | 
				
			||||||
                                              iconSize: 16.0,
 | 
					 | 
				
			||||||
                                              onPressed: () =>
 | 
					 | 
				
			||||||
                                                  handleDeleteAction(bolus)),
 | 
					 | 
				
			||||||
                              ],
 | 
					                              ],
 | 
				
			||||||
                            ),
 | 
					                            ),
 | 
				
			||||||
                          );
 | 
					                          );
 | 
				
			||||||
                                  }).toList()
 | 
					                        },
 | 
				
			||||||
                                : [],
 | 
					 | 
				
			||||||
                            columns: Bolus.asDataTableColumns(),
 | 
					 | 
				
			||||||
                          ),
 | 
					 | 
				
			||||||
                        ],
 | 
					 | 
				
			||||||
                      ),
 | 
					                      ),
 | 
				
			||||||
              );
 | 
					              );
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -227,7 +227,7 @@ class _BolusProfileDetailScreenState extends State<BolusProfileDetailScreen> {
 | 
				
			||||||
  Widget build(BuildContext context) {
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
    bool isNew = widget.bolusProfile == null;
 | 
					    bool isNew = widget.bolusProfile == null;
 | 
				
			||||||
    return DefaultTabController(
 | 
					    return DefaultTabController(
 | 
				
			||||||
      length: 2,
 | 
					      length: isNew ? 1 : 2,
 | 
				
			||||||
      child: Builder(builder: (BuildContext context) {
 | 
					      child: Builder(builder: (BuildContext context) {
 | 
				
			||||||
        final TabController tabController = DefaultTabController.of(context)!;
 | 
					        final TabController tabController = DefaultTabController.of(context)!;
 | 
				
			||||||
        tabController.addListener(() {
 | 
					        tabController.addListener(() {
 | 
				
			||||||
| 
						 | 
					@ -235,24 +235,8 @@ class _BolusProfileDetailScreenState extends State<BolusProfileDetailScreen> {
 | 
				
			||||||
            renderTabButtons(tabController.index);
 | 
					            renderTabButtons(tabController.index);
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        return Scaffold(
 | 
					
 | 
				
			||||||
          appBar: AppBar(
 | 
					        List<Widget> tabs = [
 | 
				
			||||||
            title:
 | 
					 | 
				
			||||||
                Text(isNew ? 'New Bolus Profile' : widget.bolusProfile!.name),
 | 
					 | 
				
			||||||
            bottom: isNew
 | 
					 | 
				
			||||||
                ? PreferredSize(child: Container(), preferredSize: Size.zero)
 | 
					 | 
				
			||||||
                : const TabBar(
 | 
					 | 
				
			||||||
                    tabs: [
 | 
					 | 
				
			||||||
                      Tab(text: 'PROFILE'),
 | 
					 | 
				
			||||||
                      Tab(text: 'RATES'),
 | 
					 | 
				
			||||||
                    ],
 | 
					 | 
				
			||||||
                  ),
 | 
					 | 
				
			||||||
            actions: appBarActions,
 | 
					 | 
				
			||||||
          ),
 | 
					 | 
				
			||||||
          drawer: const Navigation(
 | 
					 | 
				
			||||||
              currentLocation: BolusProfileDetailScreen.routeName),
 | 
					 | 
				
			||||||
          body: TabBarView(
 | 
					 | 
				
			||||||
            children: [
 | 
					 | 
				
			||||||
          SingleChildScrollView(
 | 
					          SingleChildScrollView(
 | 
				
			||||||
            child: Column(
 | 
					            child: Column(
 | 
				
			||||||
              crossAxisAlignment: CrossAxisAlignment.stretch,
 | 
					              crossAxisAlignment: CrossAxisAlignment.stretch,
 | 
				
			||||||
| 
						 | 
					@ -294,9 +278,31 @@ class _BolusProfileDetailScreenState extends State<BolusProfileDetailScreen> {
 | 
				
			||||||
              ],
 | 
					              ],
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
              BolusListScreen(bolusProfile: widget.bolusProfile),
 | 
					        ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!isNew) {
 | 
				
			||||||
 | 
					          tabs.add(BolusListScreen(bolusProfile: widget.bolusProfile));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return Scaffold(
 | 
				
			||||||
 | 
					          appBar: AppBar(
 | 
				
			||||||
 | 
					            title:
 | 
				
			||||||
 | 
					                Text(isNew ? 'New Bolus Profile' : widget.bolusProfile!.name),
 | 
				
			||||||
 | 
					            bottom: isNew
 | 
				
			||||||
 | 
					                ? PreferredSize(child: Container(), preferredSize: Size.zero)
 | 
				
			||||||
 | 
					                : const TabBar(
 | 
				
			||||||
 | 
					                    tabs: [
 | 
				
			||||||
 | 
					                      Tab(text: 'PROFILE'),
 | 
				
			||||||
 | 
					                      Tab(text: 'RATES'),
 | 
				
			||||||
                    ],
 | 
					                    ],
 | 
				
			||||||
                  ),
 | 
					                  ),
 | 
				
			||||||
 | 
					            actions: appBarActions,
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					          drawer: const Navigation(
 | 
				
			||||||
 | 
					              currentLocation: BolusProfileDetailScreen.routeName),
 | 
				
			||||||
 | 
					          body: TabBarView(
 | 
				
			||||||
 | 
					            children: tabs,
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
          bottomNavigationBar: DetailBottomRow(
 | 
					          bottomNavigationBar: DetailBottomRow(
 | 
				
			||||||
            onCancel: handleCancelAction,
 | 
					            onCancel: handleCancelAction,
 | 
				
			||||||
            onSave: handleSaveAction,
 | 
					            onSave: handleSaveAction,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -217,7 +217,7 @@ class _LogEntryScreenState extends State<LogEntryScreen> {
 | 
				
			||||||
    bool isNew = widget.entry == null;
 | 
					    bool isNew = widget.entry == null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return DefaultTabController(
 | 
					    return DefaultTabController(
 | 
				
			||||||
      length: 3,
 | 
					      length: isNew ? 1 : 3,
 | 
				
			||||||
      child: Builder(builder: (BuildContext context) {
 | 
					      child: Builder(builder: (BuildContext context) {
 | 
				
			||||||
        final TabController tabController = DefaultTabController.of(context)!;
 | 
					        final TabController tabController = DefaultTabController.of(context)!;
 | 
				
			||||||
        tabController.addListener(() {
 | 
					        tabController.addListener(() {
 | 
				
			||||||
| 
						 | 
					@ -225,6 +225,16 @@ class _LogEntryScreenState extends State<LogEntryScreen> {
 | 
				
			||||||
            renderTabButtons(tabController.index);
 | 
					            renderTabButtons(tabController.index);
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					        List<Widget> tabs = [
 | 
				
			||||||
 | 
					          LogEntryForm(
 | 
				
			||||||
 | 
					              formState: logEntryForm, controllers: formDataControllers),
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!isNew) {
 | 
				
			||||||
 | 
					          tabs.add(LogMealListScreen(logEntry: widget.entry));
 | 
				
			||||||
 | 
					          tabs.add(LogEventListScreen(logEntry: widget.entry));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return Scaffold(
 | 
					        return Scaffold(
 | 
				
			||||||
          appBar: AppBar(
 | 
					          appBar: AppBar(
 | 
				
			||||||
            title: Text(isNew ? 'New Log Entry' : 'Edit Log Entry'),
 | 
					            title: Text(isNew ? 'New Log Entry' : 'Edit Log Entry'),
 | 
				
			||||||
| 
						 | 
					@ -241,12 +251,7 @@ class _LogEntryScreenState extends State<LogEntryScreen> {
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
          drawer: const Navigation(currentLocation: LogEntryScreen.routeName),
 | 
					          drawer: const Navigation(currentLocation: LogEntryScreen.routeName),
 | 
				
			||||||
          body: TabBarView(
 | 
					          body: TabBarView(
 | 
				
			||||||
            children: [
 | 
					            children: tabs,
 | 
				
			||||||
              LogEntryForm(
 | 
					 | 
				
			||||||
                  formState: logEntryForm, controllers: formDataControllers),
 | 
					 | 
				
			||||||
              LogMealListScreen(logEntry: widget.entry),
 | 
					 | 
				
			||||||
              LogEventListScreen(logEntry: widget.entry),
 | 
					 | 
				
			||||||
            ],
 | 
					 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
          bottomNavigationBar: DetailBottomRow(
 | 
					          bottomNavigationBar: DetailBottomRow(
 | 
				
			||||||
            onCancel: handleCancelAction,
 | 
					            onCancel: handleCancelAction,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -17,6 +17,35 @@ class LogEntryForm extends StatefulWidget {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class _LogEntryFormState extends State<LogEntryForm> {
 | 
					class _LogEntryFormState extends State<LogEntryForm> {
 | 
				
			||||||
 | 
					  void convertBetweenMgPerDlAndMmolPerL({GlucoseMeasurement? calculateFrom}) {
 | 
				
			||||||
 | 
					    int? mgPerDl;
 | 
				
			||||||
 | 
					    double? mmolPerL;
 | 
				
			||||||
 | 
					    final _mgPerDlController = widget.controllers['mgPerDl'];
 | 
				
			||||||
 | 
					    final _mmolPerLController = widget.controllers['mmolPerL'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (calculateFrom != GlucoseMeasurement.mmolPerL &&
 | 
				
			||||||
 | 
					        _mgPerDlController!.text != '') {
 | 
				
			||||||
 | 
					      mgPerDl = int.tryParse(_mgPerDlController.text);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (calculateFrom != GlucoseMeasurement.mgPerDl &&
 | 
				
			||||||
 | 
					        _mmolPerLController!.text != '') {
 | 
				
			||||||
 | 
					      mmolPerL = double.tryParse(_mmolPerLController.text);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (mgPerDl != null && mmolPerL == null) {
 | 
				
			||||||
 | 
					      setState(() {
 | 
				
			||||||
 | 
					        _mmolPerLController!.text =
 | 
				
			||||||
 | 
					            Utils.convertMgPerDlToMmolPerL(mgPerDl!).toString();
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (mmolPerL != null && mgPerDl == null) {
 | 
				
			||||||
 | 
					      setState(() {
 | 
				
			||||||
 | 
					        _mgPerDlController!.text =
 | 
				
			||||||
 | 
					            Utils.convertMmolPerLToMgPerDl(mmolPerL!).toString();
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  Widget build(BuildContext context) {
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
    final _timeController = widget.controllers['time'];
 | 
					    final _timeController = widget.controllers['time'];
 | 
				
			||||||
| 
						 | 
					@ -48,8 +77,6 @@ class _LogEntryFormState extends State<LogEntryForm> {
 | 
				
			||||||
            //       }
 | 
					            //       }
 | 
				
			||||||
            //),
 | 
					            //),
 | 
				
			||||||
            Row(
 | 
					            Row(
 | 
				
			||||||
              // TODO: improve conversion of mg/dl and mmol/l
 | 
					 | 
				
			||||||
              // TODO: display according to settings
 | 
					 | 
				
			||||||
              children: [
 | 
					              children: [
 | 
				
			||||||
                glucoseMeasurement == GlucoseMeasurement.mgPerDl ||
 | 
					                glucoseMeasurement == GlucoseMeasurement.mgPerDl ||
 | 
				
			||||||
                        glucoseDisplayMode == GlucoseDisplayMode.both ||
 | 
					                        glucoseDisplayMode == GlucoseDisplayMode.both ||
 | 
				
			||||||
| 
						 | 
					@ -61,6 +88,8 @@ class _LogEntryFormState extends State<LogEntryForm> {
 | 
				
			||||||
                            suffixText: 'mg/dl',
 | 
					                            suffixText: 'mg/dl',
 | 
				
			||||||
                          ),
 | 
					                          ),
 | 
				
			||||||
                          controller: _mgPerDlController,
 | 
					                          controller: _mgPerDlController,
 | 
				
			||||||
 | 
					                          onChanged: (_) => convertBetweenMgPerDlAndMmolPerL(
 | 
				
			||||||
 | 
					                              calculateFrom: GlucoseMeasurement.mgPerDl),
 | 
				
			||||||
                          keyboardType: const TextInputType.numberWithOptions(),
 | 
					                          keyboardType: const TextInputType.numberWithOptions(),
 | 
				
			||||||
                          validator: (value) {
 | 
					                          validator: (value) {
 | 
				
			||||||
                            if (value!.trim().isEmpty &&
 | 
					                            if (value!.trim().isEmpty &&
 | 
				
			||||||
| 
						 | 
					@ -72,6 +101,14 @@ class _LogEntryFormState extends State<LogEntryForm> {
 | 
				
			||||||
                        ),
 | 
					                        ),
 | 
				
			||||||
                      )
 | 
					                      )
 | 
				
			||||||
                    : Container(),
 | 
					                    : Container(),
 | 
				
			||||||
 | 
					                glucoseDisplayMode == GlucoseDisplayMode.both ||
 | 
				
			||||||
 | 
					                        glucoseDisplayMode == GlucoseDisplayMode.bothForDetail
 | 
				
			||||||
 | 
					                    ? IconButton(
 | 
				
			||||||
 | 
					                        onPressed: () => convertBetweenMgPerDlAndMmolPerL(
 | 
				
			||||||
 | 
					                            calculateFrom: GlucoseMeasurement.mmolPerL),
 | 
				
			||||||
 | 
					                        icon: const Icon(Icons.calculate),
 | 
				
			||||||
 | 
					                      )
 | 
				
			||||||
 | 
					                    : Container(),
 | 
				
			||||||
                glucoseMeasurement == GlucoseMeasurement.mmolPerL ||
 | 
					                glucoseMeasurement == GlucoseMeasurement.mmolPerL ||
 | 
				
			||||||
                        glucoseDisplayMode == GlucoseDisplayMode.both ||
 | 
					                        glucoseDisplayMode == GlucoseDisplayMode.both ||
 | 
				
			||||||
                        glucoseDisplayMode == GlucoseDisplayMode.bothForDetail
 | 
					                        glucoseDisplayMode == GlucoseDisplayMode.bothForDetail
 | 
				
			||||||
| 
						 | 
					@ -80,19 +117,10 @@ class _LogEntryFormState extends State<LogEntryForm> {
 | 
				
			||||||
                          decoration: const InputDecoration(
 | 
					                          decoration: const InputDecoration(
 | 
				
			||||||
                            labelText: 'mmol/l',
 | 
					                            labelText: 'mmol/l',
 | 
				
			||||||
                            suffixText: 'mmol/l',
 | 
					                            suffixText: 'mmol/l',
 | 
				
			||||||
                            alignLabelWithHint: true,
 | 
					 | 
				
			||||||
                          ),
 | 
					                          ),
 | 
				
			||||||
                          controller: _mmolPerLController,
 | 
					                          controller: _mmolPerLController,
 | 
				
			||||||
                          onChanged: (_) {
 | 
					                          onChanged: (_) => convertBetweenMgPerDlAndMmolPerL(
 | 
				
			||||||
                            setState(() {
 | 
					                              calculateFrom: GlucoseMeasurement.mmolPerL),
 | 
				
			||||||
                              _mgPerDlController!.text =
 | 
					 | 
				
			||||||
                                  Utils.convertMmolPerLToMgPerDl(
 | 
					 | 
				
			||||||
                                          double.tryParse(
 | 
					 | 
				
			||||||
                                                  _mgPerDlController.text) ??
 | 
					 | 
				
			||||||
                                              0)
 | 
					 | 
				
			||||||
                                      .toString();
 | 
					 | 
				
			||||||
                            });
 | 
					 | 
				
			||||||
                          },
 | 
					 | 
				
			||||||
                          keyboardType: const TextInputType.numberWithOptions(
 | 
					                          keyboardType: const TextInputType.numberWithOptions(
 | 
				
			||||||
                              decimal: true),
 | 
					                              decimal: true),
 | 
				
			||||||
                          validator: (value) {
 | 
					                          validator: (value) {
 | 
				
			||||||
| 
						 | 
					@ -105,6 +133,14 @@ class _LogEntryFormState extends State<LogEntryForm> {
 | 
				
			||||||
                        ),
 | 
					                        ),
 | 
				
			||||||
                      )
 | 
					                      )
 | 
				
			||||||
                    : Container(),
 | 
					                    : Container(),
 | 
				
			||||||
 | 
					                glucoseDisplayMode == GlucoseDisplayMode.both ||
 | 
				
			||||||
 | 
					                        glucoseDisplayMode == GlucoseDisplayMode.bothForDetail
 | 
				
			||||||
 | 
					                    ? IconButton(
 | 
				
			||||||
 | 
					                        onPressed: () => convertBetweenMgPerDlAndMmolPerL(
 | 
				
			||||||
 | 
					                            calculateFrom: GlucoseMeasurement.mgPerDl),
 | 
				
			||||||
 | 
					                        icon: const Icon(Icons.calculate),
 | 
				
			||||||
 | 
					                      )
 | 
				
			||||||
 | 
					                    : Container(),
 | 
				
			||||||
              ],
 | 
					              ],
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
            TextFormField(
 | 
					            TextFormField(
 | 
				
			||||||
| 
						 | 
					@ -142,16 +178,6 @@ class _LogEntryFormState extends State<LogEntryForm> {
 | 
				
			||||||
              keyboardType: TextInputType.multiline,
 | 
					              keyboardType: TextInputType.multiline,
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
          ],
 | 
					          ],
 | 
				
			||||||
          // buttons: [
 | 
					 | 
				
			||||||
          //   ElevatedButton(
 | 
					 | 
				
			||||||
          //     onPressed: handleCancelAction,
 | 
					 | 
				
			||||||
          //     child: const Text('CANCEL'),
 | 
					 | 
				
			||||||
          //   ),
 | 
					 | 
				
			||||||
          //   ElevatedButton(
 | 
					 | 
				
			||||||
          //     onPressed: handleSaveAction,
 | 
					 | 
				
			||||||
          //     child: const Text('SAVE'),
 | 
					 | 
				
			||||||
          //   ),
 | 
					 | 
				
			||||||
          // ],
 | 
					 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
      ]),
 | 
					      ]),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,6 +3,8 @@ import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:intl/intl.dart';
 | 
					import 'package:intl/intl.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DateTimeUtils {
 | 
					class DateTimeUtils {
 | 
				
			||||||
 | 
					  // TODO fix handling of time zones!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static String displayDateTime(DateTime? date, {String fallback = ''}) {
 | 
					  static String displayDateTime(DateTime? date, {String fallback = ''}) {
 | 
				
			||||||
    if (date == null) {
 | 
					    if (date == null) {
 | 
				
			||||||
      return fallback;
 | 
					      return fallback;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		
		Reference in a new issue