mirror of
https://github.com/wger-project/flutter.git
synced 2026-02-18 00:17:48 +01:00
Allow multiple weight entries per day
This commit is contained in:
@@ -29,7 +29,7 @@ class WeightEntry {
|
||||
@JsonKey(required: true, fromJson: stringToNum, toJson: numToString)
|
||||
late num weight = 0;
|
||||
|
||||
@JsonKey(required: true, toJson: dateToYYYYMMDD)
|
||||
@JsonKey(required: true)
|
||||
late DateTime date;
|
||||
|
||||
WeightEntry({this.id, weight, DateTime? date}) {
|
||||
|
||||
@@ -21,5 +21,5 @@ WeightEntry _$WeightEntryFromJson(Map<String, dynamic> json) {
|
||||
Map<String, dynamic> _$WeightEntryToJson(WeightEntry instance) => <String, dynamic>{
|
||||
'id': instance.id,
|
||||
'weight': numToString(instance.weight),
|
||||
'date': dateToYYYYMMDD(instance.date),
|
||||
'date': instance.date.toIso8601String(),
|
||||
};
|
||||
|
||||
@@ -32,8 +32,6 @@ class WeightScreen extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final lastWeightEntry = context.read<BodyWeightProvider>().getNewestEntry();
|
||||
|
||||
return Scaffold(
|
||||
appBar: EmptyAppBar(AppLocalizations.of(context).weight),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
@@ -44,7 +42,7 @@ class WeightScreen extends StatelessWidget {
|
||||
FormScreen.routeName,
|
||||
arguments: FormScreenArguments(
|
||||
AppLocalizations.of(context).newEntry,
|
||||
WeightForm(lastWeightEntry?.copyWith(id: null, date: DateTime.now())),
|
||||
WeightForm(),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -21,38 +21,41 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wger/helpers/consts.dart';
|
||||
import 'package:wger/helpers/date.dart';
|
||||
import 'package:wger/helpers/json.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/models/body_weight/weight_entry.dart';
|
||||
import 'package:wger/providers/body_weight.dart';
|
||||
|
||||
class WeightForm extends StatelessWidget {
|
||||
final _form = GlobalKey<FormState>();
|
||||
final dateController = TextEditingController();
|
||||
final weightController = TextEditingController();
|
||||
final dateController = TextEditingController(text: '');
|
||||
final timeController = TextEditingController(text: '');
|
||||
final weightController = TextEditingController(text: '');
|
||||
|
||||
late final WeightEntry _weightEntry;
|
||||
final WeightEntry _weightEntry;
|
||||
|
||||
WeightForm([WeightEntry? weightEntry]) {
|
||||
_weightEntry = weightEntry ?? WeightEntry(date: DateTime.now());
|
||||
weightController.text = '';
|
||||
dateController.text = dateToYYYYMMDD(_weightEntry.date)!;
|
||||
}
|
||||
WeightForm([WeightEntry? weightEntry])
|
||||
: _weightEntry = weightEntry ?? WeightEntry(date: DateTime.now());
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final numberFormat = NumberFormat.decimalPattern(Localizations.localeOf(context).toString());
|
||||
final dateFormat = DateFormat.yMd(Localizations.localeOf(context).languageCode);
|
||||
final timeFormat = DateFormat.Hm(Localizations.localeOf(context).languageCode);
|
||||
|
||||
if (weightController.text.isEmpty && _weightEntry.weight != 0) {
|
||||
weightController.text = numberFormat.format(_weightEntry.weight);
|
||||
}
|
||||
if (dateController.text.isEmpty) {
|
||||
dateController.text = dateFormat.format(_weightEntry.date);
|
||||
}
|
||||
if (timeController.text.isEmpty) {
|
||||
timeController.text = TimeOfDay.fromDateTime(_weightEntry.date).format(context);
|
||||
}
|
||||
|
||||
return Form(
|
||||
key: _form,
|
||||
child: Column(
|
||||
children: [
|
||||
// Weight date
|
||||
TextFormField(
|
||||
key: const Key('dateInput'),
|
||||
// Stop keyboard from appearing
|
||||
@@ -72,24 +75,51 @@ class WeightForm extends StatelessWidget {
|
||||
initialDate: _weightEntry.date,
|
||||
firstDate: DateTime(DateTime.now().year - 10),
|
||||
lastDate: DateTime.now(),
|
||||
selectableDayPredicate: (day) {
|
||||
// Always allow the current initial date
|
||||
if (day.isSameDayAs(_weightEntry.date)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// if the date is known, don't allow it
|
||||
return Provider.of<BodyWeightProvider>(context, listen: false).findByDate(day) ==
|
||||
null;
|
||||
},
|
||||
);
|
||||
|
||||
if (pickedDate != null) {
|
||||
dateController.text = dateToYYYYMMDD(pickedDate)!;
|
||||
dateController.text = dateFormat.format(pickedDate);
|
||||
}
|
||||
},
|
||||
onSaved: (newValue) {
|
||||
_weightEntry.date = DateTime.parse(newValue!);
|
||||
final date = dateFormat.parse(newValue!);
|
||||
_weightEntry.date = _weightEntry.date.copyWith(
|
||||
year: date.year,
|
||||
month: date.month,
|
||||
day: date.day,
|
||||
);
|
||||
},
|
||||
),
|
||||
TextFormField(
|
||||
key: const Key('timeInput'),
|
||||
// Stop keyboard from appearing
|
||||
readOnly: true,
|
||||
decoration: InputDecoration(
|
||||
labelText: AppLocalizations.of(context).time,
|
||||
suffixIcon: const Icon(
|
||||
Icons.access_time_outlined,
|
||||
key: Key('clockIcon'),
|
||||
),
|
||||
),
|
||||
enableInteractiveSelection: false,
|
||||
controller: timeController,
|
||||
onTap: () async {
|
||||
final pickedTime = await showTimePicker(
|
||||
context: context,
|
||||
initialTime: TimeOfDay.fromDateTime(_weightEntry.date),
|
||||
);
|
||||
|
||||
if (pickedTime != null) {
|
||||
timeController.text = pickedTime.format(context);
|
||||
}
|
||||
},
|
||||
onSaved: (newValue) {
|
||||
final time = timeFormat.parse(newValue!);
|
||||
_weightEntry.date = _weightEntry.date.copyWith(
|
||||
hour: time.hour,
|
||||
minute: time.minute,
|
||||
second: time.second,
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ class WeightOverview extends StatelessWidget {
|
||||
subtitle: Text(
|
||||
DateFormat.yMd(
|
||||
Localizations.localeOf(context).languageCode,
|
||||
).format(currentEntry.date),
|
||||
).add_Hm().format(currentEntry.date),
|
||||
),
|
||||
trailing: PopupMenuButton(
|
||||
itemBuilder: (BuildContext context) {
|
||||
|
||||
@@ -1009,7 +1009,7 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i20.NutritionPlans
|
||||
@override
|
||||
_i18.Future<_i13.Ingredient?> searchIngredientWithBarcode(String? barcode) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#searchIngredientWithCode,
|
||||
#searchIngredientWithBarcode,
|
||||
[barcode],
|
||||
),
|
||||
returnValue: _i18.Future<_i13.Ingredient?>.value(),
|
||||
|
||||
@@ -414,7 +414,7 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i8.NutritionPlansP
|
||||
@override
|
||||
_i9.Future<_i7.Ingredient?> searchIngredientWithBarcode(String? barcode) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#searchIngredientWithCode,
|
||||
#searchIngredientWithBarcode,
|
||||
[barcode],
|
||||
),
|
||||
returnValue: _i9.Future<_i7.Ingredient?>.value(),
|
||||
|
||||
@@ -414,7 +414,7 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i8.NutritionPlansP
|
||||
@override
|
||||
_i9.Future<_i7.Ingredient?> searchIngredientWithBarcode(String? barcode) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#searchIngredientWithCode,
|
||||
#searchIngredientWithBarcode,
|
||||
[barcode],
|
||||
),
|
||||
returnValue: _i9.Future<_i7.Ingredient?>.value(),
|
||||
|
||||
@@ -34,11 +34,21 @@ void main() {
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('The form is prefilled with the data from an entry', (WidgetTester tester) async {
|
||||
testWidgets('Correctly prefills and localizes the data - en', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(createWeightForm(weightEntry: testWeightEntry1));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('2021-01-01'), findsOneWidget);
|
||||
expect(find.text('1/1/2021'), findsOneWidget);
|
||||
expect(find.text('3:30 PM'), findsOneWidget);
|
||||
expect(find.text('80'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('Correctly prefills and localizes the data - de', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(createWeightForm(weightEntry: testWeightEntry1, locale: 'de'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('1.1.2021'), findsOneWidget);
|
||||
expect(find.text('15:30'), findsOneWidget);
|
||||
expect(find.text('80'), findsOneWidget);
|
||||
});
|
||||
|
||||
|
||||
@@ -22,11 +22,15 @@ import 'package:wger/models/body_weight/weight_entry.dart';
|
||||
void main() {
|
||||
group('fetchPost', () {
|
||||
test('Test that the weight entries are correctly converted to json', () {
|
||||
WeightEntry weightEntry = WeightEntry(id: 1, weight: 80, date: DateTime(2020, 12, 31));
|
||||
expect(weightEntry.toJson(), {'id': 1, 'weight': '80', 'date': '2020-12-31'});
|
||||
expect(
|
||||
WeightEntry(id: 1, weight: 80, date: DateTime(2020, 12, 31, 12, 34)).toJson(),
|
||||
{'id': 1, 'weight': '80', 'date': '2020-12-31T12:34:00.000'},
|
||||
);
|
||||
|
||||
weightEntry = WeightEntry(id: 2, weight: 70.2, date: DateTime(2020, 12, 01));
|
||||
expect(weightEntry.toJson(), {'id': 2, 'weight': '70.2', 'date': '2020-12-01'});
|
||||
expect(
|
||||
WeightEntry(id: 2, weight: 70.2, date: DateTime(2020, 12, 01)).toJson(),
|
||||
{'id': 2, 'weight': '70.2', 'date': '2020-12-01T00:00:00.000'},
|
||||
);
|
||||
});
|
||||
|
||||
test('Test that the weight entries are correctly converted from json', () {
|
||||
|
||||
@@ -70,8 +70,10 @@ void main() {
|
||||
path: 'api/v2/weightentry/',
|
||||
);
|
||||
when(mockBaseProvider.makeUrl(any, query: anyNamed('query'))).thenReturn(uri);
|
||||
when(mockBaseProvider.post({'id': null, 'weight': '80', 'date': '2021-01-01'}, uri))
|
||||
.thenAnswer((_) => Future.value({'id': 25, 'date': '2021-01-01', 'weight': '80'}));
|
||||
when(mockBaseProvider.post(
|
||||
{'id': null, 'weight': '80', 'date': '2021-01-01T00:00:00.000'},
|
||||
uri,
|
||||
)).thenAnswer((_) => Future.value({'id': 25, 'date': '2021-01-01', 'weight': '80'}));
|
||||
|
||||
// Act
|
||||
final BodyWeightProvider provider = BodyWeightProvider(mockBaseProvider);
|
||||
|
||||
@@ -43,7 +43,6 @@ void main() {
|
||||
setUp(() {
|
||||
mockWeightProvider = MockBodyWeightProvider();
|
||||
when(mockWeightProvider.items).thenReturn(getWeightEntries());
|
||||
when(mockWeightProvider.getNewestEntry()).thenReturn(null);
|
||||
|
||||
mockUserProvider = MockUserProvider();
|
||||
when(mockUserProvider.profile).thenReturn(tProfile1);
|
||||
|
||||
@@ -739,7 +739,7 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i16.NutritionPlans
|
||||
@override
|
||||
_i11.Future<_i9.Ingredient?> searchIngredientWithBarcode(String? barcode) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#searchIngredientWithCode,
|
||||
#searchIngredientWithBarcode,
|
||||
[barcode],
|
||||
),
|
||||
returnValue: _i11.Future<_i9.Ingredient?>.value(),
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
|
||||
import 'package:wger/models/body_weight/weight_entry.dart';
|
||||
|
||||
final testWeightEntry1 = WeightEntry(id: 1, weight: 80, date: DateTime(2021, 01, 01));
|
||||
final testWeightEntry2 = WeightEntry(id: 2, weight: 81, date: DateTime(2021, 01, 10));
|
||||
final testWeightEntry1 = WeightEntry(id: 1, weight: 80, date: DateTime(2021, 01, 01, 15, 30));
|
||||
final testWeightEntry2 = WeightEntry(id: 2, weight: 81, date: DateTime(2021, 01, 10, 10, 0));
|
||||
|
||||
List<WeightEntry> getWeightEntries() {
|
||||
return [testWeightEntry1, testWeightEntry2];
|
||||
|
||||
Reference in New Issue
Block a user