335 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			335 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
import 'package:diameter/components/detail.dart';
 | 
						|
import 'package:diameter/components/forms/number_form_field.dart';
 | 
						|
import 'package:diameter/components/forms/time_of_day_form_field.dart';
 | 
						|
import 'package:diameter/localization_keys.dart';
 | 
						|
import 'package:diameter/utils/dialog_utils.dart';
 | 
						|
import 'package:diameter/models/settings.dart';
 | 
						|
import 'package:diameter/navigation.dart';
 | 
						|
import 'package:diameter/utils/date_time_utils.dart';
 | 
						|
import 'package:diameter/utils/utils.dart';
 | 
						|
import 'package:flutter/material.dart';
 | 
						|
import 'package:diameter/components/forms/form_wrapper.dart';
 | 
						|
import 'package:diameter/models/basal.dart';
 | 
						|
import 'package:diameter/models/basal_profile.dart';
 | 
						|
import 'package:flutter_translate/flutter_translate.dart';
 | 
						|
 | 
						|
class BasalDetailScreen extends StatefulWidget {
 | 
						|
  static const String routeName = '/basal';
 | 
						|
 | 
						|
  final int basalProfileId;
 | 
						|
  final int id;
 | 
						|
  final TimeOfDay? suggestedStartTime;
 | 
						|
  final TimeOfDay? suggestedEndTime;
 | 
						|
 | 
						|
  const BasalDetailScreen(
 | 
						|
      {Key? key,
 | 
						|
      this.basalProfileId = 0,
 | 
						|
      this.id = 0,
 | 
						|
      this.suggestedStartTime,
 | 
						|
      this.suggestedEndTime})
 | 
						|
      : super(key: key);
 | 
						|
 | 
						|
  @override
 | 
						|
  _BasalDetailScreenState createState() => _BasalDetailScreenState();
 | 
						|
}
 | 
						|
 | 
						|
class _BasalDetailScreenState extends State<BasalDetailScreen> {
 | 
						|
  Basal? _basal;
 | 
						|
  bool _isNew = true;
 | 
						|
  bool _isSaving = false;
 | 
						|
  bool _isFinalRate = true;
 | 
						|
 | 
						|
  final GlobalKey<FormState> _basalForm = GlobalKey<FormState>();
 | 
						|
  final ScrollController _scrollController = ScrollController();
 | 
						|
 | 
						|
  TimeOfDay _startTime = const TimeOfDay(hour: 0, minute: 0);
 | 
						|
  TimeOfDay _endTime = const TimeOfDay(hour: 0, minute: 0);
 | 
						|
 | 
						|
  final _startTimeController = TextEditingController(text: '');
 | 
						|
  final _endTimeController = TextEditingController(text: '');
 | 
						|
  final _unitsController =
 | 
						|
      TextEditingController(text: 0.toStringAsPrecision(3));
 | 
						|
 | 
						|
  @override
 | 
						|
  void initState() {
 | 
						|
    super.initState();
 | 
						|
    reload();
 | 
						|
 | 
						|
    if (widget.suggestedStartTime != null) {
 | 
						|
      _startTime = widget.suggestedStartTime!;
 | 
						|
    }
 | 
						|
    if (widget.suggestedEndTime != null) {
 | 
						|
      _endTime = widget.suggestedEndTime!;
 | 
						|
    }
 | 
						|
 | 
						|
    if (_basal != null) {
 | 
						|
      _startTime = TimeOfDay.fromDateTime(_basal!.startTime);
 | 
						|
      _endTime = TimeOfDay.fromDateTime(_basal!.endTime);
 | 
						|
      _unitsController.text = _basal!.units.toStringAsPrecision(3);
 | 
						|
    }
 | 
						|
 | 
						|
    _startTimeController.text = DateTimeUtils.displayTimeOfDay(_startTime);
 | 
						|
    _endTimeController.text = DateTimeUtils.displayTimeOfDay(_endTime);
 | 
						|
  }
 | 
						|
 | 
						|
  @override
 | 
						|
  void dispose() {
 | 
						|
    _scrollController.dispose();
 | 
						|
    _startTimeController.dispose();
 | 
						|
    _endTimeController.dispose();
 | 
						|
    _unitsController.dispose();
 | 
						|
    super.dispose();
 | 
						|
  }
 | 
						|
 | 
						|
  void reload({String? message}) {
 | 
						|
    if (widget.id != 0) {
 | 
						|
      setState(() {
 | 
						|
        _basal = Basal.get(widget.id);
 | 
						|
      });
 | 
						|
    }
 | 
						|
    _isNew = _basal == null;
 | 
						|
 | 
						|
    setState(() {
 | 
						|
      if (message != null) {
 | 
						|
        var snackBar = SnackBar(
 | 
						|
          content: Text(message),
 | 
						|
          duration: const Duration(seconds: 2),
 | 
						|
        );
 | 
						|
        ScaffoldMessenger.of(context)
 | 
						|
          ..removeCurrentSnackBar()
 | 
						|
          ..showSnackBar(snackBar);
 | 
						|
      }
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  void updateStartTime(TimeOfDay? value) {
 | 
						|
    if (value != null) {
 | 
						|
      setState(() {
 | 
						|
        _startTime = value;
 | 
						|
        _startTimeController.text = DateTimeUtils.displayTimeOfDay(_startTime);
 | 
						|
      });
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  void updateEndTime(TimeOfDay? value) {
 | 
						|
    if (value != null) {
 | 
						|
      setState(() {
 | 
						|
        _endTime = value;
 | 
						|
        _endTimeController.text = DateTimeUtils.displayTimeOfDay(_endTime);
 | 
						|
        _isFinalRate = widget.suggestedEndTime == null ||
 | 
						|
            _endTime == widget.suggestedEndTime!;
 | 
						|
      });
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  Future<String?> validateTimePeriod() async {
 | 
						|
    String? error;
 | 
						|
    List<Basal> basalRates = Basal.getAllForProfile(widget.basalProfileId);
 | 
						|
 | 
						|
    if (basalRates
 | 
						|
        .where((other) =>
 | 
						|
            (widget.id != other.id) &&
 | 
						|
            _startTime.hour == other.startTime.hour &&
 | 
						|
            _startTime.minute == other.startTime.minute)
 | 
						|
        .isNotEmpty) {
 | 
						|
      error = translate(LocalizationKeys.basal_warnings_duplicate);
 | 
						|
    }
 | 
						|
 | 
						|
    if (basalRates
 | 
						|
        .where((other) =>
 | 
						|
            (widget.id != other.id) &&
 | 
						|
            DateTimeUtils.convertTimeOfDayToDateTime(_startTime)
 | 
						|
                .isBefore(other.startTime) &&
 | 
						|
            DateTimeUtils.convertTimeOfDayToDateTime(_endTime)
 | 
						|
                .isAfter(other.startTime))
 | 
						|
        .isNotEmpty) {
 | 
						|
      error = translate(LocalizationKeys.basal_warnings_overlap);
 | 
						|
    }
 | 
						|
 | 
						|
    return error == null
 | 
						|
        ? null
 | 
						|
        : showDialog(
 | 
						|
            context: context,
 | 
						|
            builder: (BuildContext context) {
 | 
						|
              return AlertDialog(
 | 
						|
                content: Text(error!),
 | 
						|
                actions: <Widget>[
 | 
						|
                  TextButton(
 | 
						|
                    onPressed: () => Navigator.pop(context, 'CANCEL'),
 | 
						|
                    child: Text(translate(LocalizationKeys.general_keepEditing).toUpperCase()),
 | 
						|
                  ),
 | 
						|
                  ElevatedButton(
 | 
						|
                    onPressed: () => Navigator.pop(context, 'CONFIRM'),
 | 
						|
                    child: Text(translate(LocalizationKeys.general_saveAsIs).toUpperCase()),
 | 
						|
                  ),
 | 
						|
                ],
 | 
						|
              );
 | 
						|
            });
 | 
						|
  }
 | 
						|
 | 
						|
  void handleSaveAction({bool next = true}) async {
 | 
						|
    setState(() {
 | 
						|
      _isSaving = true;
 | 
						|
    });
 | 
						|
    if (_basalForm.currentState!.validate()) {
 | 
						|
      await validateTimePeriod().then((value) async {
 | 
						|
        if (value != 'CANCEL') {
 | 
						|
          Basal basal = Basal(
 | 
						|
            id: widget.id,
 | 
						|
            startTime: DateTimeUtils.convertTimeOfDayToDateTime(_startTime),
 | 
						|
            endTime: DateTimeUtils.convertTimeOfDayToDateTime(_endTime),
 | 
						|
            units: double.parse(_unitsController.text),
 | 
						|
          );
 | 
						|
          basal.basalProfile.targetId = widget.basalProfileId;
 | 
						|
          Basal.put(basal);
 | 
						|
 | 
						|
          if (next) {
 | 
						|
            Navigator.push(
 | 
						|
              context,
 | 
						|
              MaterialPageRoute(
 | 
						|
                builder: (context) {
 | 
						|
                  return BasalDetailScreen(
 | 
						|
                    basalProfileId: widget.basalProfileId,
 | 
						|
                    suggestedStartTime: _endTime,
 | 
						|
                    suggestedEndTime: widget.suggestedEndTime,
 | 
						|
                  );
 | 
						|
                },
 | 
						|
              ),
 | 
						|
            ).then((result) {
 | 
						|
              Navigator.pop(
 | 
						|
                context,
 | 
						|
                [
 | 
						|
                  translatePlural(
 | 
						|
                    LocalizationKeys.basal_saved,
 | 
						|
                    result.length,
 | 
						|
                    args: {
 | 
						|
                      "status": _isNew ? '${LocalizationKeys.basal_new} ' : ''
 | 
						|
                    },
 | 
						|
                  ),
 | 
						|
                basal] + [result[1]],
 | 
						|
              );
 | 
						|
            });
 | 
						|
          } else {
 | 
						|
            Navigator.pop(
 | 
						|
                context, [translatePlural(
 | 
						|
                    LocalizationKeys.basal_saved,
 | 
						|
                    1,
 | 
						|
                    args: {
 | 
						|
                      "status": _isNew ? '${LocalizationKeys.basal_new} ' : ''
 | 
						|
                    },
 | 
						|
                  ), basal]);
 | 
						|
          }
 | 
						|
        }
 | 
						|
      });
 | 
						|
    }
 | 
						|
    setState(() {
 | 
						|
      _isSaving = false;
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  void handleCancelAction() {
 | 
						|
    if (Settings.get().showConfirmationDialogOnCancel &&
 | 
						|
        ((_isNew &&
 | 
						|
                (_startTime.hour != (widget.suggestedStartTime?.hour ?? 0) ||
 | 
						|
                    _endTime.hour != (widget.suggestedEndTime?.hour ?? 0) ||
 | 
						|
                    _startTime.minute !=
 | 
						|
                        (widget.suggestedStartTime?.minute ?? 0) ||
 | 
						|
                    _endTime.minute != (widget.suggestedEndTime?.minute ?? 0) ||
 | 
						|
                    double.tryParse(_unitsController.text) != 0)) ||
 | 
						|
            (!_isNew &&
 | 
						|
                (TimeOfDay.fromDateTime(_basal!.startTime) != _startTime ||
 | 
						|
                    TimeOfDay.fromDateTime(_basal!.endTime) != _endTime ||
 | 
						|
                    double.tryParse(_unitsController.text) !=
 | 
						|
                        _basal!.units)))) {
 | 
						|
      DialogUtils.showCancelConfirmationDialog(
 | 
						|
        context: context,
 | 
						|
        isNew: _isNew,
 | 
						|
        onSave: handleSaveAction,
 | 
						|
      );
 | 
						|
    } else {
 | 
						|
      Navigator.pop(context);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  @override
 | 
						|
  Widget build(BuildContext context) {
 | 
						|
    return Scaffold(
 | 
						|
      appBar: AppBar(
 | 
						|
        title: Text(
 | 
						|
          translate(
 | 
						|
            LocalizationKeys.basal_title,
 | 
						|
            args: {
 | 
						|
              "status": _isNew ? LocalizationKeys.basal_new : LocalizationKeys.general_edit,
 | 
						|
              "profileName": BasalProfile.get(widget.basalProfileId)?.name,
 | 
						|
            }
 | 
						|
          ),
 | 
						|
        ),
 | 
						|
      ),
 | 
						|
      drawer: const Navigation(currentLocation: BasalDetailScreen.routeName),
 | 
						|
      body: Scrollbar(
 | 
						|
        controller: _scrollController,
 | 
						|
        child: SingleChildScrollView(
 | 
						|
          controller: _scrollController,
 | 
						|
          child: Column(
 | 
						|
            children: [
 | 
						|
              FormWrapper(
 | 
						|
                formState: _basalForm,
 | 
						|
                fields: [
 | 
						|
                  Row(
 | 
						|
                    children: [
 | 
						|
                      Expanded(
 | 
						|
                        child: Padding(
 | 
						|
                          padding: const EdgeInsets.only(right: 5),
 | 
						|
                          child: TimeOfDayFormField(
 | 
						|
                            label: translate(LocalizationKeys.basal_fields_startTime),
 | 
						|
                            controller: _startTimeController,
 | 
						|
                            time: _startTime,
 | 
						|
                            onChanged: updateStartTime,
 | 
						|
                          ),
 | 
						|
                        ),
 | 
						|
                      ),
 | 
						|
                      Expanded(
 | 
						|
                        child: Padding(
 | 
						|
                          padding: const EdgeInsets.only(left: 5),
 | 
						|
                          child: TimeOfDayFormField(
 | 
						|
                            label: translate(LocalizationKeys.basal_fields_endTime),
 | 
						|
                            controller: _endTimeController,
 | 
						|
                            time: _endTime,
 | 
						|
                            onChanged: updateEndTime,
 | 
						|
                          ),
 | 
						|
                        ),
 | 
						|
                      ),
 | 
						|
                    ],
 | 
						|
                  ),
 | 
						|
                  NumberFormField(
 | 
						|
                      controller: _unitsController,
 | 
						|
                      label: translate(LocalizationKeys.basal_fields_units),
 | 
						|
                      suffix: translate(LocalizationKeys.general_suffixes_units),
 | 
						|
                      autoRoundToMultipleOfStep: true,
 | 
						|
                      step: Settings.insulinSteps,
 | 
						|
                      onChanged: (value) {
 | 
						|
                        if (value != null) {
 | 
						|
                          _unitsController.text =
 | 
						|
                              Utils.toStringMatchingTemplateFractionPrecision(
 | 
						|
                                  value, Settings.insulinSteps);
 | 
						|
                        }
 | 
						|
                      })
 | 
						|
                ],
 | 
						|
              ),
 | 
						|
            ],
 | 
						|
          ),
 | 
						|
        ),
 | 
						|
      ),
 | 
						|
      bottomNavigationBar: DetailBottomRow(
 | 
						|
        onCancel: handleCancelAction,
 | 
						|
        onAction:
 | 
						|
            _isSaving ? null : () => handleSaveAction(next: !_isFinalRate),
 | 
						|
        onMiddleAction: _isSaving || _isFinalRate
 | 
						|
            ? null
 | 
						|
            : () => handleSaveAction(next: false),
 | 
						|
        actionTextKey: translate(_isFinalRate ? LocalizationKeys.general_saveAndClose : LocalizationKeys.general_next).toUpperCase(),
 | 
						|
        middleActionTextKey: translate(LocalizationKeys.general_saveAndClose).toUpperCase(),
 | 
						|
      ),
 | 
						|
    );
 | 
						|
  }
 | 
						|
}
 |