improve screens using tabs, disable save buttons while saving, redo log meal list screen, log meal model fixes

This commit is contained in:
spinel 2021-10-27 21:00:28 +02:00
parent 53fb4ac7fc
commit b4db712719
18 changed files with 213 additions and 113 deletions

19
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,19 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "diameter",
"request": "launch",
"type": "dart"
},
{
"name": "diameter (profile mode)",
"request": "launch",
"type": "dart",
"flutterMode": "profile"
}
]
}

3
TODO Normal file
View File

@ -0,0 +1,3 @@
Todo:
☐ Item

View File

@ -1,8 +1,8 @@
import 'package:flutter/material.dart';
class DetailBottomRow extends StatefulWidget {
final void Function() onCancel;
final void Function() onSave;
final void Function()? onCancel;
final void Function()? onSave;
const DetailBottomRow(
{Key? key, required this.onCancel, required this.onSave})

View File

@ -33,6 +33,7 @@ class _ViewWithProgressIndicatorState extends State<ViewWithProgressIndicator> {
child: SizedBox(
width: widget.progressIndicatorSize,
height: widget.progressIndicatorSize,
// TODO: only show if loading takes longer than 30ms
child: const CircularProgressIndicator(),
),
),

View File

@ -23,7 +23,6 @@ import 'package:diameter/config.dart';
import 'package:diameter/screens/basal/basal_profiles_list.dart';
import 'package:diameter/screens/bolus/bolus_profile_list.dart';
import 'package:diameter/navigation.dart';
import 'package:flex_color_scheme/flex_color_scheme.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();

View File

@ -24,39 +24,19 @@ class LogMeal extends DataTableContent {
LogMeal(ParseObject object) {
objectId = object.get<String>('objectId');
logEntry = object.get<ParseObject>('logEntry')!.get<String>('objectId')!;
meal = object.get<ParseObject>('meal') != null
? object.get<ParseObject>('meal')!.get<String>('objectId')
: null;
meal = object.get<ParseObject>('meal')?.get<String>('objectId');
value = object.get<String>('value')!;
source = object.get<ParseObject>('source') != null
? object.get<ParseObject>('source')!.get<String>('objectId')
: null;
category = object.get<ParseObject>('category') != null
? object.get<ParseObject>('category')!.get<String>('objectId')
: null;
portionType = object.get<ParseObject>('portionType') != null
? object.get<ParseObject>('portionType')!.get<String>('objectId')
: null;
carbsRatio = object.get<num>('carbsRatio')!.toDouble();
portionSize = object.get<num>('portionSize')!.toDouble();
carbsPerPortion = object.get<num>('carbsPerPortion')!.toDouble();
portionSizeAccuracy = object.get<ParseObject>('portionSizeAccuracy') != null
? object
.get<ParseObject>('portionSizeAccuracy')!
.get<String>('objectId')
: null;
carbsRatioAccuracy = object.get<ParseObject>('carbsRatioAccuracy') != null
? object.get<ParseObject>('carbsRatioAccuracy')!.get<String>('objectId')
: null;
bolus = object.get<num>('bolus') != null
? object.get<num>('bolus')!.toDouble()
: null;
delayedBolusDuration = object.get<num>('delayedBolusDuration') != null
? object.get<num>('delayedBolusDuration')!.toInt()
: null;
delayedBolusRate = object.get<num>('delayedBolusRate') != null
? object.get<num>('delayedBolusRate')!.toDouble()
: null;
source = object.get<ParseObject>('source')?.get<String>('objectId');
category = object.get<ParseObject>('category')?.get<String>('objectId');
portionType = object.get<ParseObject>('portionType')?.get<String>('objectId');
carbsRatio = object.get<num>('carbsRatio')?.toDouble();
portionSize = object.get<num>('portionSize')?.toDouble();
carbsPerPortion = object.get<num>('carbsPerPortion')?.toDouble();
portionSizeAccuracy = object.get<ParseObject>('portionSizeAccuracy')?.get<String>('objectId');
carbsRatioAccuracy = object.get<ParseObject>('carbsRatioAccuracy')?.get<String>('objectId');
bolus = object.get<num>('bolus')?.toDouble();
delayedBolusDuration = object.get<num>('delayedBolusDuration')?.toInt();
delayedBolusRate = object.get<num>('delayedBolusRate')?.toDouble();
notes = object.get<String>('notes');
}
@ -104,7 +84,7 @@ class LogMeal extends DataTableContent {
double? delayedBolusRate,
String? notes,
}) async {
final logMeal = ParseObject('Meal')
final logMeal = ParseObject('LogMeal')
..set('value', value)
..set('logEntry',
(ParseObject('LogEntry')..objectId = logEntry).toPointer())
@ -161,7 +141,7 @@ class LogMeal extends DataTableContent {
double? delayedBolusRate,
String? notes,
}) async {
var logMeal = ParseObject('Meal')..objectId = objectId;
var logMeal = ParseObject('LogMeal')..objectId = objectId;
if (value != null) {
logMeal.set('value', value);
}

View File

@ -24,6 +24,8 @@ class _AccuracyDetailScreenState extends State<AccuracyDetailScreen> {
bool _forCarbsRatio = false;
bool _forPortionSize = false;
bool _isSaving = false;
@override
void initState() {
super.initState();
@ -38,6 +40,9 @@ class _AccuracyDetailScreenState extends State<AccuracyDetailScreen> {
}
void handleSaveAction() async {
setState(() {
_isSaving = true;
});
if (_accuracyForm.currentState!.validate()) {
bool isNew = widget.accuracy == null;
isNew
@ -58,6 +63,9 @@ class _AccuracyDetailScreenState extends State<AccuracyDetailScreen> {
);
Navigator.pop(context, '${isNew ? 'New' : ''} Accuracy saved');
}
setState(() {
_isSaving = false;
});
}
void handleCancelAction() {
@ -154,7 +162,7 @@ class _AccuracyDetailScreenState extends State<AccuracyDetailScreen> {
),
bottomNavigationBar: DetailBottomRow(
onCancel: handleCancelAction,
onSave: handleSaveAction,
onSave: _isSaving ? null : handleSaveAction,
),
);
}

View File

@ -37,6 +37,8 @@ class _BasalDetailScreenState extends State<BasalDetailScreen> {
final _endTimeController = TextEditingController(text: '');
final _unitsController = TextEditingController(text: '');
bool _isSaving = false;
@override
void initState() {
super.initState();
@ -114,6 +116,9 @@ class _BasalDetailScreenState extends State<BasalDetailScreen> {
}
void handleSaveAction() async {
setState(() {
_isSaving = true;
});
if (_basalForm.currentState!.validate()) {
await validateTimePeriod().then((value) async {
if (value != 'CANCEL') {
@ -137,6 +142,9 @@ class _BasalDetailScreenState extends State<BasalDetailScreen> {
}
});
}
setState(() {
_isSaving = false;
});
}
void handleCancelAction() {
@ -241,7 +249,7 @@ class _BasalDetailScreenState extends State<BasalDetailScreen> {
),
bottomNavigationBar: DetailBottomRow(
onCancel: handleCancelAction,
onSave: handleSaveAction,
onSave: _isSaving ? null : handleSaveAction,
),
);
}

View File

@ -30,11 +30,18 @@ class _BasalProfileDetailScreenState extends State<BasalProfileDetailScreen> {
late FloatingActionButton addBasalButton;
late IconButton refreshButton;
late IconButton closeButton;
late DetailBottomRow detailBottomRow;
FloatingActionButton? actionButton;
List<Widget> appBarActions = [];
DetailBottomRow? bottomNav;
final _nameController = TextEditingController(text: '');
final _notesController = TextEditingController(text: '');
bool _active = false;
bool _isSaving = false;
@override
void initState() {
super.initState();
@ -62,8 +69,14 @@ class _BasalProfileDetailScreenState extends State<BasalProfileDetailScreen> {
icon: const Icon(Icons.close),
);
detailBottomRow = DetailBottomRow(
onCancel: handleCancelAction,
onSave: _isSaving ? null : handleSaveAction,
);
actionButton = null;
appBarActions = [closeButton];
bottomNav = detailBottomRow;
}
void refresh({String? message}) {
@ -195,6 +208,9 @@ class _BasalProfileDetailScreenState extends State<BasalProfileDetailScreen> {
}
void handleSaveAction() async {
setState(() {
_isSaving = true;
});
if (_basalProfileForm.currentState!.validate()) {
await checkActiveProfiles();
bool isNew = widget.basalProfile == null;
@ -211,6 +227,9 @@ class _BasalProfileDetailScreenState extends State<BasalProfileDetailScreen> {
);
Navigator.pop(context, '${isNew ? 'New' : ''} Basal Profile saved');
}
setState(() {
_isSaving = false;
});
}
void handleCancelAction() {
@ -235,9 +254,6 @@ class _BasalProfileDetailScreenState extends State<BasalProfileDetailScreen> {
}
}
FloatingActionButton? actionButton;
List<Widget> appBarActions = [];
void renderTabButtons(index) {
if (widget.basalProfile != null) {
setState(() {
@ -245,10 +261,12 @@ class _BasalProfileDetailScreenState extends State<BasalProfileDetailScreen> {
case 1:
actionButton = addBasalButton;
appBarActions = [refreshButton, closeButton];
bottomNav = null;
break;
default:
actionButton = null;
appBarActions = [closeButton];
bottomNav = detailBottomRow;
}
});
}
@ -262,9 +280,7 @@ class _BasalProfileDetailScreenState extends State<BasalProfileDetailScreen> {
child: Builder(builder: (BuildContext context) {
final TabController tabController = DefaultTabController.of(context)!;
tabController.addListener(() {
if (tabController.indexIsChanging) {
renderTabButtons(tabController.index);
}
});
List<Widget> tabs = [
SingleChildScrollView(
@ -332,13 +348,10 @@ class _BasalProfileDetailScreenState extends State<BasalProfileDetailScreen> {
drawer: const Navigation(
currentLocation: BasalProfileDetailScreen.routeName),
body: TabBarView(children: tabs),
bottomNavigationBar: DetailBottomRow(
onCancel: handleCancelAction,
onSave: handleSaveAction,
),
bottomNavigationBar: bottomNav,
floatingActionButton: actionButton,
floatingActionButtonLocation:
FloatingActionButtonLocation.centerDocked,
FloatingActionButtonLocation.endFloat,
);
}),
);

View File

@ -43,6 +43,8 @@ class _BolusDetailScreenState extends State<BolusDetailScreen> {
final _mgPerDlController = TextEditingController(text: '');
final _mmolPerLController = TextEditingController(text: '');
bool _isSaving = false;
@override
void initState() {
super.initState();
@ -123,6 +125,9 @@ class _BolusDetailScreenState extends State<BolusDetailScreen> {
}
void handleSaveAction() async {
setState(() {
_isSaving = true;
});
if (_bolusForm.currentState!.validate()) {
await validateTimePeriod().then((value) async {
if (value != 'CANCEL') {
@ -152,6 +157,9 @@ class _BolusDetailScreenState extends State<BolusDetailScreen> {
}
});
}
setState(() {
_isSaving = false;
});
}
void handleCancelAction() {
@ -391,7 +399,7 @@ class _BolusDetailScreenState extends State<BolusDetailScreen> {
),
bottomNavigationBar: DetailBottomRow(
onCancel: handleCancelAction,
onSave: handleSaveAction,
onSave: _isSaving ? null : handleSaveAction,
),
);
}

View File

@ -30,11 +30,18 @@ class _BolusProfileDetailScreenState extends State<BolusProfileDetailScreen> {
late FloatingActionButton addBolusButton;
late IconButton refreshButton;
late IconButton closeButton;
late DetailBottomRow detailBottomRow;
FloatingActionButton? actionButton;
List<Widget> appBarActions = [];
DetailBottomRow? bottomNav;
final _nameController = TextEditingController(text: '');
final _notesController = TextEditingController(text: '');
bool _active = false;
bool _isSaving = false;
@override
void initState() {
super.initState();
@ -62,8 +69,15 @@ class _BolusProfileDetailScreenState extends State<BolusProfileDetailScreen> {
icon: const Icon(Icons.close),
);
// TODO: fix (saving button doesnt get disabled)
detailBottomRow = DetailBottomRow(
onCancel: handleCancelAction,
onSave: _isSaving ? null : handleSaveAction,
);
actionButton = null;
appBarActions = [closeButton];
bottomNav = detailBottomRow;
}
void refresh({String? message}) {
@ -195,6 +209,9 @@ class _BolusProfileDetailScreenState extends State<BolusProfileDetailScreen> {
}
void handleSaveAction() async {
setState(() {
_isSaving = true;
});
if (_bolusProfileForm.currentState!.validate()) {
await checkActiveProfiles();
bool isNew = widget.bolusProfile == null;
@ -211,6 +228,9 @@ class _BolusProfileDetailScreenState extends State<BolusProfileDetailScreen> {
);
Navigator.pop(context, '${isNew ? 'New' : ''} Bolus Profile saved');
}
setState(() {
_isSaving = false;
});
}
void handleCancelAction() {
@ -235,9 +255,6 @@ class _BolusProfileDetailScreenState extends State<BolusProfileDetailScreen> {
}
}
FloatingActionButton? actionButton;
List<Widget> appBarActions = [];
void renderTabButtons(index) {
if (widget.bolusProfile != null) {
setState(() {
@ -245,10 +262,12 @@ class _BolusProfileDetailScreenState extends State<BolusProfileDetailScreen> {
case 1:
actionButton = addBolusButton;
appBarActions = [refreshButton, closeButton];
bottomNav = null;
break;
default:
actionButton = null;
appBarActions = [closeButton];
bottomNav = detailBottomRow;
}
});
}
@ -262,9 +281,7 @@ class _BolusProfileDetailScreenState extends State<BolusProfileDetailScreen> {
child: Builder(builder: (BuildContext context) {
final TabController tabController = DefaultTabController.of(context)!;
tabController.addListener(() {
if (tabController.indexIsChanging) {
renderTabButtons(tabController.index);
}
});
List<Widget> tabs = [
@ -334,13 +351,10 @@ class _BolusProfileDetailScreenState extends State<BolusProfileDetailScreen> {
body: TabBarView(
children: tabs,
),
bottomNavigationBar: DetailBottomRow(
onCancel: handleCancelAction,
onSave: handleSaveAction,
),
bottomNavigationBar: bottomNav,
floatingActionButton: actionButton,
floatingActionButtonLocation:
FloatingActionButtonLocation.centerDocked,
FloatingActionButtonLocation.endFloat,
);
}),
);

View File

@ -28,9 +28,13 @@ class _LogEntryScreenState extends State<LogEntryScreen> {
late FloatingActionButton addEventButton;
late IconButton refreshButton;
late IconButton closeButton;
late DetailBottomRow detailBottomRow;
static FloatingActionButton? actionButton;
static List<Widget> appBarActions = [];
FloatingActionButton? actionButton;
List<Widget> appBarActions = [];
DetailBottomRow? bottomNav;
bool _isSaving = false;
final formDataControllers = <String, TextEditingController>{
'time': TextEditingController(text: ''),
@ -54,6 +58,9 @@ class _LogEntryScreenState extends State<LogEntryScreen> {
}
void handleSaveAction() async {
setState(() {
_isSaving = true;
});
if (logEntryForm.currentState!.validate()) {
bool isNew = widget.entry == null;
isNew
@ -85,6 +92,9 @@ class _LogEntryScreenState extends State<LogEntryScreen> {
Navigator.pushReplacementNamed(context, '/log',
arguments: '${isNew ? 'New' : ''} Log Entry Saved');
}
setState(() {
_isSaving = false;
});
}
void handleCancelAction() {
@ -188,8 +198,14 @@ class _LogEntryScreenState extends State<LogEntryScreen> {
icon: const Icon(Icons.close),
);
detailBottomRow = DetailBottomRow(
onCancel: handleCancelAction,
onSave: _isSaving ? null : handleSaveAction,
);
actionButton = null;
appBarActions = [closeButton];
bottomNav = detailBottomRow;
}
void renderTabButtons(index) {
@ -199,14 +215,17 @@ class _LogEntryScreenState extends State<LogEntryScreen> {
case 1:
actionButton = addMealButton;
appBarActions = [refreshButton, closeButton];
bottomNav = null;
break;
case 2:
actionButton = addEventButton;
appBarActions = [refreshButton, closeButton];
bottomNav = null;
break;
default:
actionButton = null;
appBarActions = [closeButton];
bottomNav = detailBottomRow;
}
});
}
@ -221,9 +240,7 @@ class _LogEntryScreenState extends State<LogEntryScreen> {
child: Builder(builder: (BuildContext context) {
final TabController tabController = DefaultTabController.of(context)!;
tabController.addListener(() {
if (tabController.indexIsChanging) {
renderTabButtons(tabController.index);
}
});
List<Widget> tabs = [
LogEntryForm(
@ -253,13 +270,10 @@ class _LogEntryScreenState extends State<LogEntryScreen> {
body: TabBarView(
children: tabs,
),
bottomNavigationBar: DetailBottomRow(
onCancel: handleCancelAction,
onSave: handleSaveAction,
),
bottomNavigationBar: bottomNav,
floatingActionButton: actionButton,
floatingActionButtonLocation:
FloatingActionButtonLocation.centerDocked,
FloatingActionButtonLocation.centerDocked,
);
}),
);

View File

@ -30,6 +30,8 @@ class _LogEventDetailScreenState extends State<LogEventDetailScreen> {
late Future<List<LogEventType>> _logEventTypes;
bool _isSaving = false;
@override
void initState() {
super.initState();
@ -44,6 +46,9 @@ class _LogEventDetailScreenState extends State<LogEventDetailScreen> {
}
void handleSaveAction() async {
setState(() {
_isSaving = true;
});
if (_logEventForm.currentState!.validate()) {
bool isNew = widget.logEvent == null;
isNew
@ -63,6 +68,9 @@ class _LogEventDetailScreenState extends State<LogEventDetailScreen> {
);
Navigator.pop(context, '${isNew ? 'New' : ''} Event Saved');
}
setState(() {
_isSaving = false;
});
}
void handleCancelAction() {
@ -138,7 +146,7 @@ class _LogEventDetailScreenState extends State<LogEventDetailScreen> {
),
bottomNavigationBar: DetailBottomRow(
onCancel: handleCancelAction,
onSave: handleSaveAction,
onSave: _isSaving ? null : handleSaveAction,
),
);
}

View File

@ -24,6 +24,8 @@ class _LogEventTypeDetailScreenState extends State<LogEventTypeDetailScreen> {
final _notesController = TextEditingController(text: '');
bool _hasEndTime = false;
bool _isSaving = false;
@override
void initState() {
super.initState();
@ -38,6 +40,9 @@ class _LogEventTypeDetailScreenState extends State<LogEventTypeDetailScreen> {
}
void handleSaveAction() async {
setState(() {
_isSaving = true;
});
if (_logEventTypeForm.currentState!.validate()) {
bool isNew = widget.logEventType == null;
isNew
@ -58,6 +63,9 @@ class _LogEventTypeDetailScreenState extends State<LogEventTypeDetailScreen> {
);
Navigator.pop(context, '${isNew ? 'New' : ''} Log Event Type Saved');
}
setState(() {
_isSaving = false;
});
}
void handleCancelAction() {
@ -148,7 +156,7 @@ class _LogEventTypeDetailScreenState extends State<LogEventTypeDetailScreen> {
),
bottomNavigationBar: DetailBottomRow(
onCancel: handleCancelAction,
onSave: handleSaveAction,
onSave: _isSaving ? null : handleSaveAction,
),
);
}

View File

@ -49,6 +49,8 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
late Future<List<Accuracy>> _portionSizeAccuracies;
late Future<List<Accuracy>> _carbsRatioAccuracies;
bool _isSaving = false;
@override
void initState() {
super.initState();
@ -128,6 +130,9 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
}
void handleSaveAction() async {
setState(() {
_isSaving = true;
});
if (_logMealForm.currentState!.validate()) {
bool isNew = widget.logMeal == null;
isNew
@ -143,6 +148,7 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
carbsPerPortion: double.tryParse(_carbsPerPortionController.text),
portionSizeAccuracy: _portionSizeAccuracy,
carbsRatioAccuracy: _carbsRatioAccuracy,
bolus: double.tryParse(_bolusController.text),
delayedBolusDuration:
int.tryParse(_delayedBolusDurationController.text),
delayedBolusRate:
@ -161,6 +167,7 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
carbsPerPortion: double.tryParse(_carbsPerPortionController.text),
portionSizeAccuracy: _portionSizeAccuracy,
carbsRatioAccuracy: _carbsRatioAccuracy,
bolus: double.tryParse(_bolusController.text),
delayedBolusDuration:
int.tryParse(_delayedBolusDurationController.text),
delayedBolusRate:
@ -169,6 +176,9 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
);
Navigator.pop(context, '${isNew ? 'New' : ''} Meal Saved');
}
setState(() {
_isSaving = false;
});
}
void handleCancelAction() {
@ -185,6 +195,7 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
double.tryParse(_carbsPerPortionController.text) != null ||
_carbsRatioAccuracy != null ||
_portionSizeAccuracy != null ||
double.tryParse(_bolusController.text) != null ||
int.tryParse(_delayedBolusDurationController.text) !=
null ||
double.tryParse(_delayedBolusRateController.text) != null ||
@ -204,6 +215,7 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
_carbsRatioAccuracy != widget.logMeal!.carbsRatioAccuracy ||
_portionSizeAccuracy !=
widget.logMeal!.portionSizeAccuracy ||
double.tryParse(_bolusController.text) != widget.logMeal!.bolus ||
int.tryParse(_delayedBolusDurationController.text) !=
widget.logMeal!.delayedBolusDuration ||
double.tryParse(_delayedBolusRateController.text) !=
@ -486,7 +498,7 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
),
bottomNavigationBar: DetailBottomRow(
onCancel: handleCancelAction,
onSave: handleSaveAction,
onSave: _isSaving ? null : handleSaveAction,
),
);
}

View File

@ -86,35 +86,32 @@ class _LogMealListScreenState extends State<LogMealListScreen> {
padding: EdgeInsets.all(10.0),
child: Text('No Meals for this Log Entry'),
)
: ListBody(
children: [
DataTable(
columnSpacing: 10.0,
showCheckboxColumn: false,
rows: snapshot.data != null
? snapshot.data!.map((meal) {
return DataRow(
cells: meal.asDataTableCells(
[
IconButton(
icon: const Icon(Icons.edit),
iconSize: 16.0,
onPressed: () =>
handleEditAction(meal)),
IconButton(
icon: const Icon(Icons.delete),
iconSize: 16.0,
onPressed: () =>
handleDeleteAction(meal)),
],
),
);
}).toList()
: [],
columns: LogMeal.asDataTableColumns(),
: ListView.builder(
shrinkWrap: true,
itemCount: snapshot.data != null ? snapshot.data!.length : 0,
itemBuilder: (context, index) {
final meal = snapshot.data![index];
return ListTile(
onTap: () => handleEditAction(meal),
title: Row(
children: [
Expanded(
child: Text(meal.value)),
Expanded(
child: Text(meal.carbsPerPortion != null ? '${meal.carbsPerPortion} g carbs' : '')),
Expanded(child: Text(meal.bolus != null ? '${meal.bolus} U' : ''))
],
),
],
),
trailing: IconButton(
icon: const Icon(
Icons.delete,
color: Colors.blue,
),
onPressed: () => handleDeleteAction(meal),
),
);
},
),
);
},
),

View File

@ -43,6 +43,8 @@ class _MealDetailScreenState extends State<MealDetailScreen> {
late Future<List<Accuracy>> _portionSizeAccuracies;
late Future<List<Accuracy>> _carbsRatioAccuracies;
bool isSaving = false;
@override
void initState() {
super.initState();
@ -74,6 +76,9 @@ class _MealDetailScreenState extends State<MealDetailScreen> {
}
void handleSaveAction() async {
setState(() {
isSaving = true;
});
if (_mealForm.currentState!.validate()) {
bool isNew = widget.meal == null;
isNew
@ -112,6 +117,9 @@ class _MealDetailScreenState extends State<MealDetailScreen> {
);
Navigator.pop(context, '${isNew ? 'New' : ''} Meal Saved');
}
setState(() {
isSaving = false;
});
}
void handleCancelAction() {
@ -430,7 +438,7 @@ class _MealDetailScreenState extends State<MealDetailScreen> {
),
bottomNavigationBar: DetailBottomRow(
onCancel: handleCancelAction,
onSave: handleSaveAction,
onSave: isSaving ? null : handleSaveAction,
),
);
}

View File

@ -14,7 +14,7 @@ packages:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.8.2"
version: "2.8.1"
boolean_selector:
dependency: transitive
description:
@ -28,7 +28,7 @@ packages:
name: characters
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
version: "1.1.0"
charcode:
dependency: transitive
description:
@ -70,7 +70,7 @@ packages:
name: connectivity_plus_macos
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
version: "1.2.1"
connectivity_plus_platform_interface:
dependency: transitive
description:
@ -119,7 +119,7 @@ packages:
name: dio
url: "https://pub.dartlang.org"
source: hosted
version: "4.0.0"
version: "4.0.1"
fake_async:
dependency: transitive
description:
@ -218,7 +218,7 @@ packages:
name: matcher
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.11"
version: "0.12.10"
meta:
dependency: transitive
description:
@ -267,7 +267,7 @@ packages:
name: package_info_plus_macos
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
version: "1.3.0"
package_info_plus_platform_interface:
dependency: transitive
description:
@ -358,7 +358,7 @@ packages:
name: petitparser
url: "https://pub.dartlang.org"
source: hosted
version: "4.3.0"
version: "4.4.0"
platform:
dependency: transitive
description:
@ -379,7 +379,7 @@ packages:
name: process
url: "https://pub.dartlang.org"
source: hosted
version: "4.2.3"
version: "4.2.4"
provider:
dependency: "direct dev"
description:
@ -510,7 +510,7 @@ packages:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.3"
version: "0.4.2"
typed_data:
dependency: transitive
description:
@ -531,7 +531,7 @@ packages:
name: vector_math
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.1"
version: "2.1.0"
web_socket_channel:
dependency: transitive
description:
@ -545,7 +545,7 @@ packages:
name: win32
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.9"
version: "2.2.10"
xdg_directories:
dependency: transitive
description:
@ -559,7 +559,7 @@ packages:
name: xml
url: "https://pub.dartlang.org"
source: hosted
version: "5.3.0"
version: "5.3.1"
xxtea:
dependency: transitive
description: