Compare commits
No commits in common. "master" and "v1.0.3" have entirely different histories.
2
.flutter
2
.flutter
@ -1 +1 @@
|
|||||||
Subproject commit 17025dd88227cd9532c33fa78f5250d548d87e9a
|
Subproject commit dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -42,5 +42,3 @@ app.*.map.json
|
|||||||
/android/app/profile
|
/android/app/profile
|
||||||
/android/app/release
|
/android/app/release
|
||||||
assets/icon_base.xcf
|
assets/icon_base.xcf
|
||||||
|
|
||||||
/metadata/**/*.xcf
|
|
||||||
|
@ -15,7 +15,6 @@ android {
|
|||||||
namespace = "de.swgross.calorimeter"
|
namespace = "de.swgross.calorimeter"
|
||||||
compileSdk = flutter.compileSdkVersion
|
compileSdk = flutter.compileSdkVersion
|
||||||
ndkVersion = flutter.ndkVersion
|
ndkVersion = flutter.ndkVersion
|
||||||
|
|
||||||
dependenciesInfo {
|
dependenciesInfo {
|
||||||
includeInApk = false
|
includeInApk = false
|
||||||
includeInBundle = false
|
includeInBundle = false
|
||||||
@ -34,8 +33,8 @@ android {
|
|||||||
applicationId = "de.swgross.calorimeter"
|
applicationId = "de.swgross.calorimeter"
|
||||||
minSdk = flutter.minSdkVersion
|
minSdk = flutter.minSdkVersion
|
||||||
targetSdk = flutter.targetSdkVersion
|
targetSdk = flutter.targetSdkVersion
|
||||||
versionCode = 5
|
versionCode = 3
|
||||||
versionName = "1.0.5"
|
versionName = "1.0.3"
|
||||||
}
|
}
|
||||||
|
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
@ -48,10 +47,6 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
debug {
|
|
||||||
applicationIdSuffix '.debug'
|
|
||||||
versionNameSuffix '-DEBUG'
|
|
||||||
}
|
|
||||||
release {
|
release {
|
||||||
signingConfig = signingConfigs.release
|
signingConfig = signingConfigs.release
|
||||||
}
|
}
|
||||||
|
@ -1,59 +0,0 @@
|
|||||||
/* SPDX-License-Identifier: GPL-3.0-or-later */
|
|
||||||
/* Copyright (C) 2024 Marco Groß <mgross@sw-gross.de> */
|
|
||||||
|
|
||||||
import 'package:calorimeter/main.dart';
|
|
||||||
import 'package:calorimeter/storage/storage.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:integration_test/integration_test.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
|
||||||
setUp(() {});
|
|
||||||
|
|
||||||
group('end-to-end test', () {
|
|
||||||
testWidgets('add food manually', (tester) async {
|
|
||||||
var foodStorage = await FoodStorage.create();
|
|
||||||
|
|
||||||
await tester.pumpWidget(MainApp(storage: foodStorage));
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
final addButtonFinder = find.byIcon(Icons.add);
|
|
||||||
expect(addButtonFinder, findsOneWidget);
|
|
||||||
|
|
||||||
await tester.tap(addButtonFinder);
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
final nameAutocompleteFinder =
|
|
||||||
find.widgetWithText(Autocomplete<String>, "Name");
|
|
||||||
final amountFinder = find.widgetWithText(TextField, "Amount");
|
|
||||||
final kcalFinder = find.widgetWithText(TextField, "kcal");
|
|
||||||
final addButton = find.widgetWithIcon(ElevatedButton, Icons.check);
|
|
||||||
|
|
||||||
expect(nameAutocompleteFinder, findsOneWidget);
|
|
||||||
expect(amountFinder, findsOneWidget);
|
|
||||||
expect(kcalFinder, findsOneWidget);
|
|
||||||
expect(addButton, findsOneWidget);
|
|
||||||
|
|
||||||
await tester.enterText(nameAutocompleteFinder, "Bread");
|
|
||||||
await tester.enterText(amountFinder, "150");
|
|
||||||
await tester.enterText(kcalFinder, "250");
|
|
||||||
|
|
||||||
await tester.tap(addButton);
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
// EnterFoodWidget collapses
|
|
||||||
expect(nameAutocompleteFinder, findsNothing);
|
|
||||||
|
|
||||||
var enteredFood = find.text("Bread");
|
|
||||||
var enteredAmount = find.text("150");
|
|
||||||
var enteredKcal = find.text("250");
|
|
||||||
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
expect(enteredFood, findsOneWidget);
|
|
||||||
expect(enteredAmount, findsOneWidget);
|
|
||||||
expect(enteredKcal, findsOneWidget);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
@ -1,6 +1,6 @@
|
|||||||
/* SPDX-License-Identifier: GPL-3.0-or-later */
|
/* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||||
/* Copyright (C) 2024 Marco Groß <mgross@sw-gross.de> */
|
/* Copyright (C) 2024 Marco Groß <mgross@sw-gross.de> */
|
||||||
|
import 'package:calorimeter/storage/storage.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:calorimeter/food_entry/food_entry_bloc.dart';
|
import 'package:calorimeter/food_entry/food_entry_bloc.dart';
|
||||||
import 'package:calorimeter/utils/row_with_spacers_widget.dart';
|
import 'package:calorimeter/utils/row_with_spacers_widget.dart';
|
||||||
@ -8,10 +8,8 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
|||||||
|
|
||||||
class EnterFoodWidget extends StatefulWidget {
|
class EnterFoodWidget extends StatefulWidget {
|
||||||
final Function(BuildContext context, FoodEntryState entry) onAdd;
|
final Function(BuildContext context, FoodEntryState entry) onAdd;
|
||||||
final Map<String, int> foodEntryLookupDatabase;
|
|
||||||
|
|
||||||
const EnterFoodWidget(
|
const EnterFoodWidget({super.key, required this.onAdd});
|
||||||
{super.key, required this.onAdd, required this.foodEntryLookupDatabase});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<EnterFoodWidget> createState() => _EnterFoodWidgetState();
|
State<EnterFoodWidget> createState() => _EnterFoodWidgetState();
|
||||||
@ -21,123 +19,89 @@ class _EnterFoodWidgetState extends State<EnterFoodWidget> {
|
|||||||
late TextEditingController nameController;
|
late TextEditingController nameController;
|
||||||
late TextEditingController massController;
|
late TextEditingController massController;
|
||||||
late TextEditingController kcalPerMassController;
|
late TextEditingController kcalPerMassController;
|
||||||
late bool open;
|
late Map<String, int> suggestions;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
nameController = TextEditingController();
|
nameController = TextEditingController();
|
||||||
massController = TextEditingController();
|
massController = TextEditingController();
|
||||||
kcalPerMassController = TextEditingController();
|
kcalPerMassController = TextEditingController();
|
||||||
|
suggestions = FoodStorage.getInstance().getFoodEntryLookupDatabase;
|
||||||
open = false;
|
|
||||||
|
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Column(
|
return Padding(
|
||||||
children: [
|
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
Stack(
|
child: RowWidget(
|
||||||
children: [
|
Autocomplete<String>(
|
||||||
if (!open)
|
optionsViewOpenDirection: OptionsViewOpenDirection.down,
|
||||||
RowWidget(
|
fieldViewBuilder: (context, controller, focusNode, onSubmitted) {
|
||||||
showDividers: false,
|
nameController = controller;
|
||||||
null,
|
return TextFormField(
|
||||||
null,
|
controller: controller,
|
||||||
null,
|
focusNode: focusNode,
|
||||||
ElevatedButton(
|
decoration: InputDecoration(
|
||||||
style: ElevatedButton.styleFrom(
|
label: Text(AppLocalizations.of(context)!.name),
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
),
|
|
||||||
onPressed: () => setState(() => open = true),
|
|
||||||
child: Icon(Icons.add)),
|
|
||||||
),
|
|
||||||
Offstage(
|
|
||||||
offstage: !open,
|
|
||||||
child: AnimatedOpacity(
|
|
||||||
duration: Duration(milliseconds: 250),
|
|
||||||
opacity: open ? 1.0 : 0.0,
|
|
||||||
child: RowWidget(
|
|
||||||
showDividers: true,
|
|
||||||
Autocomplete<String>(
|
|
||||||
optionsViewOpenDirection: OptionsViewOpenDirection.down,
|
|
||||||
fieldViewBuilder:
|
|
||||||
(context, controller, focusNode, onSubmitted) {
|
|
||||||
nameController = controller;
|
|
||||||
return TextFormField(
|
|
||||||
scrollPadding: EdgeInsets.only(bottom: 100),
|
|
||||||
controller: controller,
|
|
||||||
focusNode: focusNode,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
label: Text(AppLocalizations.of(context)!.name),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
optionsBuilder: (TextEditingValue textEditingValue) {
|
|
||||||
if (textEditingValue.text == '') {
|
|
||||||
return const Iterable<String>.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
return widget.foodEntryLookupDatabase.keys.where(
|
|
||||||
(name) {
|
|
||||||
return name
|
|
||||||
.toLowerCase()
|
|
||||||
.contains(textEditingValue.text.toLowerCase());
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
onSelected: (selectedFood) {
|
|
||||||
int kcalPerMassForSelectedFood =
|
|
||||||
widget.foodEntryLookupDatabase[selectedFood]!;
|
|
||||||
setState(() {
|
|
||||||
nameController.text = selectedFood;
|
|
||||||
kcalPerMassController.text =
|
|
||||||
kcalPerMassForSelectedFood.toString();
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
TextField(
|
|
||||||
scrollPadding: EdgeInsets.only(bottom: 100),
|
|
||||||
textAlign: TextAlign.end,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
label: Directionality(
|
|
||||||
textDirection: TextDirection.rtl,
|
|
||||||
child: Text(AppLocalizations.of(context)!.amount),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
controller: massController,
|
|
||||||
onSubmitted: (value) => onSubmitAction(),
|
|
||||||
),
|
|
||||||
TextField(
|
|
||||||
scrollPadding: EdgeInsets.only(bottom: 100),
|
|
||||||
textAlign: TextAlign.end,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
label: Directionality(
|
|
||||||
textDirection: TextDirection.rtl,
|
|
||||||
child: Text(AppLocalizations.of(context)!.kcal))),
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
controller: kcalPerMassController,
|
|
||||||
onSubmitted: (value) => onSubmitAction(),
|
|
||||||
),
|
|
||||||
ElevatedButton(
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
),
|
|
||||||
onPressed: () => onSubmitAction(),
|
|
||||||
child: const Icon(Icons.check)),
|
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
|
},
|
||||||
|
optionsBuilder: (TextEditingValue textEditingValue) {
|
||||||
|
if (textEditingValue.text == '') {
|
||||||
|
return const Iterable<String>.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
return suggestions.keys.where(
|
||||||
|
(name) {
|
||||||
|
return name
|
||||||
|
.toLowerCase()
|
||||||
|
.contains(textEditingValue.text.toLowerCase());
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onSelected: (selectedFood) {
|
||||||
|
int kcalPerMassForSelectedFood = suggestions[selectedFood]!;
|
||||||
|
setState(() {
|
||||||
|
nameController.text = selectedFood;
|
||||||
|
kcalPerMassController.text =
|
||||||
|
kcalPerMassForSelectedFood.toString();
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
TextField(
|
||||||
|
textAlign: TextAlign.end,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
label: Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: Text(AppLocalizations.of(context)!.amountPer),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
controller: massController,
|
||||||
|
onSubmitted: (value) => onSubmitAction(),
|
||||||
),
|
),
|
||||||
SizedBox(
|
TextField(
|
||||||
height: 200,
|
textAlign: TextAlign.end,
|
||||||
child: GestureDetector(
|
decoration: InputDecoration(
|
||||||
onTap: () => setState(() {
|
label: Align(
|
||||||
open = false;
|
alignment: Alignment.centerRight,
|
||||||
}))),
|
child: Text(AppLocalizations.of(context)!.kcalper),
|
||||||
],
|
)),
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
controller: kcalPerMassController,
|
||||||
|
onSubmitted: (value) => onSubmitAction(),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 16.0),
|
||||||
|
child: ElevatedButton(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
),
|
||||||
|
onPressed: () => onSubmitAction(),
|
||||||
|
child: const Icon(Icons.add)),
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,7 +143,6 @@ class _EnterFoodWidgetState extends State<EnterFoodWidget> {
|
|||||||
nameController.text = "";
|
nameController.text = "";
|
||||||
massController.text = "";
|
massController.text = "";
|
||||||
kcalPerMassController.text = "";
|
kcalPerMassController.text = "";
|
||||||
open = false;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,8 @@
|
|||||||
/* Copyright (C) 2024 Marco Groß <mgross@sw-gross.de> */
|
/* Copyright (C) 2024 Marco Groß <mgross@sw-gross.de> */
|
||||||
import 'package:barcode_scan2/barcode_scan2.dart';
|
import 'package:barcode_scan2/barcode_scan2.dart';
|
||||||
import 'package:calorimeter/food_scan/food_fact_lookup.dart';
|
import 'package:calorimeter/food_scan/food_fact_lookup.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:calorimeter/storage/storage.dart';
|
import 'package:calorimeter/storage/storage.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
class FoodEntryBloc extends Bloc<FoodEvent, GlobalEntryState> {
|
class FoodEntryBloc extends Bloc<FoodEvent, GlobalEntryState> {
|
||||||
@ -19,7 +16,7 @@ class FoodEntryBloc extends Bloc<FoodEvent, GlobalEntryState> {
|
|||||||
on<FoodEntryEvent>(handleFoodEntryEvent);
|
on<FoodEntryEvent>(handleFoodEntryEvent);
|
||||||
on<FoodChangedEvent>(handleFoodChangedEvent);
|
on<FoodChangedEvent>(handleFoodChangedEvent);
|
||||||
on<FoodDeletionEvent>(handleDeleteFoodEvent);
|
on<FoodDeletionEvent>(handleDeleteFoodEvent);
|
||||||
on<BarcodeAboutToBeScanned>(handleBarcodeScannedEvent);
|
on<BarcodeScanned>(handleBarcodeScannedEvent);
|
||||||
on<FoodEntryTapped>(handleFoodEntryTapped);
|
on<FoodEntryTapped>(handleFoodEntryTapped);
|
||||||
}
|
}
|
||||||
void handlePageBeingInitialized(
|
void handlePageBeingInitialized(
|
||||||
@ -40,8 +37,12 @@ class FoodEntryBloc extends Bloc<FoodEvent, GlobalEntryState> {
|
|||||||
await storage.writeEntriesForDate(event.forDate, entriesForDate);
|
await storage.writeEntriesForDate(event.forDate, entriesForDate);
|
||||||
storage.addFoodEntryToLookupDatabase(event.entry);
|
storage.addFoodEntryToLookupDatabase(event.entry);
|
||||||
|
|
||||||
|
// this is just checking if writing to the database worked
|
||||||
|
// can be optimized out by just emitting newState
|
||||||
|
var newList = await storage.getEntriesForDate(event.forDate);
|
||||||
|
|
||||||
var newFoodEntries = state.foodEntries;
|
var newFoodEntries = state.foodEntries;
|
||||||
newFoodEntries.addAll({event.forDate: entriesForDate});
|
newFoodEntries.addAll({event.forDate: newList});
|
||||||
|
|
||||||
emit(GlobalEntryState(foodEntries: newFoodEntries));
|
emit(GlobalEntryState(foodEntries: newFoodEntries));
|
||||||
}
|
}
|
||||||
@ -61,8 +62,12 @@ class FoodEntryBloc extends Bloc<FoodEvent, GlobalEntryState> {
|
|||||||
await storage.writeEntriesForDate(event.forDate, entriesForDate);
|
await storage.writeEntriesForDate(event.forDate, entriesForDate);
|
||||||
storage.addFoodEntryToLookupDatabase(event.newEntry);
|
storage.addFoodEntryToLookupDatabase(event.newEntry);
|
||||||
|
|
||||||
|
// this is just checking if writing to the database worked
|
||||||
|
// can be optimized out by just emitting newState
|
||||||
|
var newList = await storage.getEntriesForDate(event.forDate);
|
||||||
|
|
||||||
var newFoodEntries = state.foodEntries;
|
var newFoodEntries = state.foodEntries;
|
||||||
newFoodEntries.addAll({event.forDate: entriesForDate});
|
newFoodEntries.addAll({event.forDate: newList});
|
||||||
|
|
||||||
emit(GlobalEntryState(foodEntries: newFoodEntries));
|
emit(GlobalEntryState(foodEntries: newFoodEntries));
|
||||||
}
|
}
|
||||||
@ -76,38 +81,31 @@ class FoodEntryBloc extends Bloc<FoodEvent, GlobalEntryState> {
|
|||||||
|
|
||||||
await storage.writeEntriesForDate(event.forDate, entriesForDate);
|
await storage.writeEntriesForDate(event.forDate, entriesForDate);
|
||||||
|
|
||||||
|
// this is just checking if writing to the database worked
|
||||||
|
// can be optimized out by just emitting newState
|
||||||
|
var newList = await storage.getEntriesForDate(event.forDate);
|
||||||
|
|
||||||
var newFoodEntries = state.foodEntries;
|
var newFoodEntries = state.foodEntries;
|
||||||
newFoodEntries.addAll({event.forDate: entriesForDate});
|
newFoodEntries.addAll({event.forDate: newList});
|
||||||
|
|
||||||
emit(GlobalEntryState(foodEntries: newFoodEntries));
|
emit(GlobalEntryState(foodEntries: newFoodEntries));
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleBarcodeScannedEvent(
|
void handleBarcodeScannedEvent(
|
||||||
BarcodeAboutToBeScanned event, Emitter<GlobalEntryState> emit) async {
|
BarcodeScanned event, Emitter<GlobalEntryState> emit) async {
|
||||||
ScanResult scanResult = ScanResult();
|
|
||||||
try {
|
|
||||||
scanResult = await BarcodeScanner.scan();
|
|
||||||
} on PlatformException catch (e) {
|
|
||||||
if (e.code == BarcodeScanner.cameraAccessDenied) {
|
|
||||||
emit(GlobalEntryState(
|
|
||||||
foodEntries: state.foodEntries,
|
|
||||||
appError:
|
|
||||||
GlobalAppError(GlobalAppErrorType.errCameraPermissionDenied)));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var client = FoodFactLookupClient();
|
|
||||||
var entriesForDate = state.foodEntries[event.forDate];
|
var entriesForDate = state.foodEntries[event.forDate];
|
||||||
if (entriesForDate == null) return;
|
if (entriesForDate == null) return;
|
||||||
|
|
||||||
|
var client = FoodFactLookupClient();
|
||||||
|
var scanResult = await event.scanResultFuture;
|
||||||
|
|
||||||
if (scanResult.type == ResultType.Cancelled) {
|
if (scanResult.type == ResultType.Cancelled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (scanResult.type == ResultType.Error) {
|
if (scanResult.type == ResultType.Error) {
|
||||||
emit(GlobalEntryState(
|
emit(GlobalEntryState(
|
||||||
foodEntries: state.foodEntries,
|
foodEntries: state.foodEntries,
|
||||||
appError: GlobalAppError(GlobalAppErrorType.errGeneralError)));
|
errorString: "Fehler beim Scannen des Barcodes"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var responseFuture = client.retrieveFoodInfo(scanResult.rawContent);
|
var responseFuture = client.retrieveFoodInfo(scanResult.rawContent);
|
||||||
@ -119,10 +117,10 @@ class FoodEntryBloc extends Bloc<FoodEvent, GlobalEntryState> {
|
|||||||
waitingForNetwork: true,
|
waitingForNetwork: true,
|
||||||
isSelected: false,
|
isSelected: false,
|
||||||
);
|
);
|
||||||
|
|
||||||
entriesForDate.add(newEntryWaiting);
|
entriesForDate.add(newEntryWaiting);
|
||||||
state.foodEntries.addAll({event.forDate: entriesForDate});
|
var newFoodEntries = state.foodEntries;
|
||||||
emit(GlobalEntryState(foodEntries: state.foodEntries));
|
newFoodEntries.addAll({event.forDate: entriesForDate});
|
||||||
|
emit(GlobalEntryState(foodEntries: newFoodEntries));
|
||||||
|
|
||||||
await responseFuture.then((response) async {
|
await responseFuture.then((response) async {
|
||||||
var index = entriesForDate
|
var index = entriesForDate
|
||||||
@ -140,7 +138,7 @@ class FoodEntryBloc extends Bloc<FoodEvent, GlobalEntryState> {
|
|||||||
|
|
||||||
emit(GlobalEntryState(
|
emit(GlobalEntryState(
|
||||||
foodEntries: newFoodEntries,
|
foodEntries: newFoodEntries,
|
||||||
appError: GlobalAppError(GlobalAppErrorType.errbarcodeNotFound)));
|
errorString: "Barcode konnte nicht gefunden werden."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (response.status ==
|
if (response.status ==
|
||||||
@ -151,8 +149,7 @@ class FoodEntryBloc extends Bloc<FoodEvent, GlobalEntryState> {
|
|||||||
|
|
||||||
emit(GlobalEntryState(
|
emit(GlobalEntryState(
|
||||||
foodEntries: newFoodEntries,
|
foodEntries: newFoodEntries,
|
||||||
appError:
|
errorString: "OpenFoodFacts-Server konnte nicht erreicht werden."));
|
||||||
GlobalAppError(GlobalAppErrorType.errServerNotReachable)));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,8 +167,9 @@ class FoodEntryBloc extends Bloc<FoodEvent, GlobalEntryState> {
|
|||||||
await storage.writeEntriesForDate(event.forDate, entriesForDate);
|
await storage.writeEntriesForDate(event.forDate, entriesForDate);
|
||||||
storage.addFoodEntryToLookupDatabase(newEntryFinishedWaiting);
|
storage.addFoodEntryToLookupDatabase(newEntryFinishedWaiting);
|
||||||
|
|
||||||
|
var entriesFromStorage = await storage.getEntriesForDate(event.forDate);
|
||||||
var newFoodEntries = state.foodEntries;
|
var newFoodEntries = state.foodEntries;
|
||||||
newFoodEntries.addAll({event.forDate: entriesForDate});
|
newFoodEntries.addAll({event.forDate: entriesFromStorage});
|
||||||
|
|
||||||
emit(GlobalEntryState(foodEntries: newFoodEntries));
|
emit(GlobalEntryState(foodEntries: newFoodEntries));
|
||||||
});
|
});
|
||||||
@ -226,8 +224,10 @@ class FoodDeletionEvent extends FoodEvent {
|
|||||||
FoodDeletionEvent({required this.entryID, required super.forDate});
|
FoodDeletionEvent({required this.entryID, required super.forDate});
|
||||||
}
|
}
|
||||||
|
|
||||||
class BarcodeAboutToBeScanned extends FoodEvent {
|
class BarcodeScanned extends FoodEvent {
|
||||||
BarcodeAboutToBeScanned({required super.forDate});
|
final Future<ScanResult> scanResultFuture;
|
||||||
|
|
||||||
|
BarcodeScanned({required this.scanResultFuture, required super.forDate});
|
||||||
}
|
}
|
||||||
|
|
||||||
class FoodEntryTapped extends FoodEvent {
|
class FoodEntryTapped extends FoodEvent {
|
||||||
@ -236,16 +236,12 @@ class FoodEntryTapped extends FoodEvent {
|
|||||||
FoodEntryTapped({required this.entry, required super.forDate});
|
FoodEntryTapped({required this.entry, required super.forDate});
|
||||||
}
|
}
|
||||||
|
|
||||||
class PermissionException extends FoodEvent {
|
|
||||||
PermissionException({required super.forDate});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This is the state for one date/page
|
/// This is the state for one date/page
|
||||||
class GlobalEntryState {
|
class GlobalEntryState {
|
||||||
final Map<DateTime, List<FoodEntryState>> foodEntries;
|
final Map<DateTime, List<FoodEntryState>> foodEntries;
|
||||||
final GlobalAppError? appError;
|
final String? errorString;
|
||||||
|
|
||||||
GlobalEntryState({required this.foodEntries, this.appError});
|
GlobalEntryState({required this.foodEntries, this.errorString});
|
||||||
|
|
||||||
factory GlobalEntryState.init() {
|
factory GlobalEntryState.init() {
|
||||||
return GlobalEntryState(foodEntries: {});
|
return GlobalEntryState(foodEntries: {});
|
||||||
@ -309,29 +305,3 @@ class FoodEntryState {
|
|||||||
return '$id,"$name",$mass,$kcalPer100';
|
return '$id,"$name",$mass,$kcalPer100';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum GlobalAppErrorType {
|
|
||||||
errGeneralError,
|
|
||||||
errbarcodeNotFound,
|
|
||||||
errServerNotReachable,
|
|
||||||
errCameraPermissionDenied
|
|
||||||
}
|
|
||||||
|
|
||||||
class GlobalAppError {
|
|
||||||
final GlobalAppErrorType type;
|
|
||||||
|
|
||||||
GlobalAppError(this.type);
|
|
||||||
|
|
||||||
String toErrorString(BuildContext context) {
|
|
||||||
switch (type) {
|
|
||||||
case GlobalAppErrorType.errGeneralError:
|
|
||||||
return AppLocalizations.of(context)!.errGeneralBarcodeError;
|
|
||||||
case GlobalAppErrorType.errbarcodeNotFound:
|
|
||||||
return AppLocalizations.of(context)!.errBarcodeNotFound;
|
|
||||||
case GlobalAppErrorType.errServerNotReachable:
|
|
||||||
return AppLocalizations.of(context)!.errServerNotReachable;
|
|
||||||
case GlobalAppErrorType.errCameraPermissionDenied:
|
|
||||||
return AppLocalizations.of(context)!.errPermissionNotGranted;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
/* SPDX-License-Identifier: GPL-3.0-or-later */
|
/* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||||
/* Copyright (C) 2024 Marco Groß <mgross@sw-gross.de> */
|
/* Copyright (C) 2024 Marco Groß <mgross@sw-gross.de> */
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:calorimeter/food_entry/food_entry_bloc.dart';
|
import 'package:calorimeter/food_entry/food_entry_bloc.dart';
|
||||||
import 'package:calorimeter/utils/row_with_spacers_widget.dart';
|
import 'package:calorimeter/utils/row_with_spacers_widget.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
class FoodEntryWidget extends StatefulWidget {
|
class FoodEntryWidget extends StatefulWidget {
|
||||||
@ -24,8 +24,6 @@ class FoodEntryWidget extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _FoodEntryWidgetState extends State<FoodEntryWidget> {
|
class _FoodEntryWidgetState extends State<FoodEntryWidget> {
|
||||||
final animationDuration = const Duration(milliseconds: 150);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@ -34,94 +32,91 @@ class _FoodEntryWidgetState extends State<FoodEntryWidget> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () => widget.onTap(context, widget.entry),
|
||||||
widget.onTap(context, widget.entry);
|
child: Stack(
|
||||||
},
|
children: [
|
||||||
child: Stack(children: [
|
Positioned.fill(
|
||||||
RowWidget(
|
child: Stack(children: [
|
||||||
showDividers: !widget.entry.isSelected,
|
Positioned.fill(
|
||||||
widget.entry.waitingForNetwork
|
child: Padding(
|
||||||
? const Center(child: CircularProgressIndicator())
|
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
: Text(widget.entry.name),
|
child: RowWidget(
|
||||||
AnimatedOpacity(
|
widget.entry.waitingForNetwork
|
||||||
duration: animationDuration,
|
? const Center(child: CircularProgressIndicator())
|
||||||
opacity: widget.entry.isSelected ? 0.0 : 1.0,
|
: Text(widget.entry.name),
|
||||||
child: widget.entry.waitingForNetwork
|
widget.entry.waitingForNetwork
|
||||||
? Container()
|
? Container()
|
||||||
: Text(widget.entry.mass.ceil().toString(),
|
: Text(widget.entry.mass.ceil().toString(),
|
||||||
textAlign: TextAlign.end),
|
textAlign: TextAlign.end),
|
||||||
),
|
Opacity(
|
||||||
AnimatedOpacity(
|
opacity: widget.entry.isSelected ? 0.0 : 1.0,
|
||||||
duration: animationDuration,
|
child: widget.entry.waitingForNetwork
|
||||||
opacity: widget.entry.isSelected ? 0.0 : 1.0,
|
? Container()
|
||||||
child: widget.entry.waitingForNetwork
|
: Text(widget.entry.kcalPer100.ceil().toString(),
|
||||||
? Container()
|
textAlign: TextAlign.end),
|
||||||
: Text(widget.entry.kcalPer100.ceil().toString(),
|
),
|
||||||
textAlign: TextAlign.end),
|
Opacity(
|
||||||
),
|
opacity: widget.entry.isSelected ? 0.0 : 1.0,
|
||||||
AnimatedOpacity(
|
child: widget.entry.waitingForNetwork
|
||||||
duration: animationDuration,
|
? Container()
|
||||||
opacity: widget.entry.isSelected ? 0.0 : 1.0,
|
: Text(
|
||||||
child: widget.entry.waitingForNetwork
|
(widget.entry.mass *
|
||||||
? Container()
|
widget.entry.kcalPer100 /
|
||||||
: Text(
|
100)
|
||||||
(widget.entry.mass * widget.entry.kcalPer100 / 100)
|
.ceil()
|
||||||
.ceil()
|
.toString(),
|
||||||
.toString(),
|
textAlign: TextAlign.end),
|
||||||
textAlign: TextAlign.end),
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Opacity(
|
||||||
|
opacity: widget.entry.isSelected ? 0.66 : 0.0,
|
||||||
|
child: Container(
|
||||||
|
color: Theme.of(context).colorScheme.secondary)),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
Opacity(
|
||||||
|
opacity: widget.entry.isSelected ? 1.0 : 0.0,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
child: IconButton(
|
||||||
|
padding: const EdgeInsets.all(0.0),
|
||||||
|
icon: const Icon(Icons.edit),
|
||||||
|
onPressed: widget.entry.isSelected
|
||||||
|
? () async {
|
||||||
|
widget.onTap(context, widget.entry);
|
||||||
|
await showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (dialogContext) {
|
||||||
|
return FoodEntryChangeDialog(
|
||||||
|
entry: widget.entry,
|
||||||
|
onChange: (context, entry) {
|
||||||
|
widget.onChange(context, entry);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
: null),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
child: IconButton(
|
||||||
|
padding: const EdgeInsets.all(0.0),
|
||||||
|
iconSize: 24,
|
||||||
|
icon: const Icon(Icons.delete),
|
||||||
|
color: Colors.redAccent,
|
||||||
|
onPressed: widget.entry.isSelected
|
||||||
|
? () => widget.onDelete(context, widget.entry.id)
|
||||||
|
: null),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Positioned.fill(
|
],
|
||||||
child: Stack(children: [
|
),
|
||||||
AnimatedOpacity(
|
);
|
||||||
duration: animationDuration,
|
|
||||||
opacity: widget.entry.isSelected ? 0.66 : 0.0,
|
|
||||||
child: Container(
|
|
||||||
color: Theme.of(context).colorScheme.secondary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
AnimatedOpacity(
|
|
||||||
duration: animationDuration,
|
|
||||||
opacity: widget.entry.isSelected ? 1.0 : 0.0,
|
|
||||||
child: Center(
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
width: 64,
|
|
||||||
child: IconButton(
|
|
||||||
padding: const EdgeInsets.all(0.0),
|
|
||||||
icon: const Icon(Icons.edit),
|
|
||||||
onPressed: widget.entry.isSelected
|
|
||||||
? () async {
|
|
||||||
widget.onTap(context, widget.entry);
|
|
||||||
await showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (dialogContext) {
|
|
||||||
return FoodEntryChangeDialog(
|
|
||||||
entry: widget.entry,
|
|
||||||
onChange: (context, entry) {
|
|
||||||
widget.onChange(
|
|
||||||
context, entry);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
: null)),
|
|
||||||
SizedBox(
|
|
||||||
width: 64,
|
|
||||||
child: IconButton(
|
|
||||||
padding: const EdgeInsets.all(0.0),
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.delete,
|
|
||||||
color: Colors.redAccent,
|
|
||||||
),
|
|
||||||
onPressed: widget.entry.isSelected
|
|
||||||
? () =>
|
|
||||||
widget.onDelete(context, widget.entry.id)
|
|
||||||
: null))
|
|
||||||
])))
|
|
||||||
]))
|
|
||||||
]));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,45 +56,19 @@ class FoodFactModel {
|
|||||||
});
|
});
|
||||||
|
|
||||||
factory FoodFactModel.fromJson(Map<String, dynamic> json) {
|
factory FoodFactModel.fromJson(Map<String, dynamic> json) {
|
||||||
int kcalPer100gForModel = 0;
|
String quantityString = json['product']['product_quantity'] ?? "0";
|
||||||
int kcalPer100g = 0;
|
int quantity;
|
||||||
int kcalPer100gPrepared = 0;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
kcalPer100g = (json['product']['nutriments']['energy-kcal_100g'] as num)
|
quantity = int.parse(quantityString);
|
||||||
.toDouble()
|
|
||||||
.ceil();
|
|
||||||
kcalPer100gForModel = kcalPer100g;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
try {
|
quantity = 0;
|
||||||
kcalPer100gPrepared =
|
|
||||||
(json['product']['nutriments']['energy-kcal_prepared_100g'] as num)
|
|
||||||
.toDouble()
|
|
||||||
.ceil();
|
|
||||||
kcalPer100gForModel = kcalPer100gPrepared;
|
|
||||||
} catch (e) {
|
|
||||||
kcalPer100gForModel = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int quantityForModel = 0;
|
|
||||||
try {
|
|
||||||
String quantityString = json['product']['product_quantity'] ?? "0";
|
|
||||||
quantityForModel = double.parse(quantityString).ceil();
|
|
||||||
} catch (e) {
|
|
||||||
try {
|
|
||||||
quantityForModel =
|
|
||||||
(json['product']['product_quantity'] as num).toDouble().ceil();
|
|
||||||
} catch (e) {
|
|
||||||
quantityForModel = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return FoodFactModel(
|
return FoodFactModel(
|
||||||
name: json['product']['product_name'] ?? "",
|
name: json['product']['product_name'],
|
||||||
kcalPer100g: kcalPer100gForModel,
|
kcalPer100g: json['product']['nutriments']['energy-kcal_100g'],
|
||||||
mass: quantityForModel,
|
mass: quantity);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,21 +1,14 @@
|
|||||||
{
|
{
|
||||||
"today": "Heute",
|
|
||||||
"ok": "OK",
|
"ok": "OK",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"amount": "Menge",
|
"amount": "Menge",
|
||||||
"amountPer": "Menge in g oder ml",
|
"amountPer": "Menge in 100 g/ml",
|
||||||
"kcal": "kcal",
|
"kcalper": "kcal pro 100 g/ml",
|
||||||
"kcalper": "kcal pro 100 g oder ml",
|
|
||||||
"kcalSum": "kcal gesamt",
|
|
||||||
"kcalToday": "kcal heute",
|
"kcalToday": "kcal heute",
|
||||||
"menu": "Menü",
|
"menu": "Menü",
|
||||||
"settings": "Einstellungen",
|
"settings": "Einstellungen",
|
||||||
"yourSettings": "Deine persönlichen Einstellungen",
|
"yourSettings": "Deine persönlichen Einstellungen",
|
||||||
"dayLimit": "Kalorienlimit pro Tag",
|
"dayLimit": "Kalorienlimit pro Tag",
|
||||||
"errAmountNotANumber": "Menge muss eine Zahl sein",
|
"errAmountNotANumber": "Menge muss eine Zahl sein",
|
||||||
"errKcalNotANumber": "kcal muss eine Zahl sein",
|
"errKcalNotANumber": "kcal muss eine Zahl sein"
|
||||||
"errGeneralBarcodeError": "Fehler beim Scannen des Barcodes",
|
|
||||||
"errBarcodeNotFound": "Barcode konnte nicht gefunden werden.",
|
|
||||||
"errServerNotReachable": "OpenFoodFacts-Server konnte nicht erreicht werden.",
|
|
||||||
"errPermissionNotGranted": "Kamera-Berechtigung muss aktiviert werden."
|
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,14 @@
|
|||||||
{
|
{
|
||||||
"today": "Today",
|
|
||||||
"ok": "OK",
|
"ok": "OK",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"amount": "Amount",
|
"amount": "Amount",
|
||||||
"amountPer": "Amount in g or ml",
|
"amountPer": "Amount in 100 g/ml",
|
||||||
"kcal": "kcal",
|
"kcalper": "kcal per 100 g/ml",
|
||||||
"kcalper": "kcal per 100 g or ml",
|
|
||||||
"kcalSum": "kcal total",
|
|
||||||
"kcalToday": "kcal today",
|
"kcalToday": "kcal today",
|
||||||
"menu": "Menu",
|
"menu": "Menu",
|
||||||
"settings": "Settings",
|
"settings": "Settings",
|
||||||
"yourSettings": "Your personal settings",
|
"yourSettings": "Your personal settings",
|
||||||
"dayLimit": "Calorie limit per day",
|
"dayLimit": "Calorie limit per day",
|
||||||
"errAmountNotANumber": "Amount must be a number",
|
"errAmountNotANumber": "Amount must be a number",
|
||||||
"errKcalNotANumber": "kcal must be a number",
|
"errKcalNotANumber": "kcal must be a number"
|
||||||
"errGeneralBarcodeError": "Error while scanning the barcode.",
|
|
||||||
"errBarcodeNotFound": "Barcode could not be found.",
|
|
||||||
"errServerNotReachable": "OpenFoodFacts server could not be reached.",
|
|
||||||
"errPermissionNotGranted": "Permission to use camera must be given."
|
|
||||||
}
|
}
|
||||||
|
159
lib/main.dart
159
lib/main.dart
@ -1,6 +1,5 @@
|
|||||||
/* SPDX-License-Identifier: GPL-3.0-or-later */
|
/* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||||
/* Copyright (C) 2024 Marco Groß <mgross@sw-gross.de> */
|
/* Copyright (C) 2024 Marco Groß <mgross@sw-gross.de> */
|
||||||
|
|
||||||
import 'package:calorimeter/food_entry/food_entry_bloc.dart';
|
import 'package:calorimeter/food_entry/food_entry_bloc.dart';
|
||||||
import 'package:calorimeter/perdate/perdate_pageview_controller.dart';
|
import 'package:calorimeter/perdate/perdate_pageview_controller.dart';
|
||||||
import 'package:calorimeter/storage/storage.dart';
|
import 'package:calorimeter/storage/storage.dart';
|
||||||
@ -9,111 +8,103 @@ import 'package:calorimeter/utils/settings_bloc.dart';
|
|||||||
import 'package:calorimeter/utils/theme_bloc.dart';
|
import 'package:calorimeter/utils/theme_bloc.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
List<FoodEntryState> entriesForToday = [];
|
||||||
|
DateTime timeNow = DateTime.now();
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
var foodStorage = await FoodStorage.create();
|
var storage = await FoodStorage.create();
|
||||||
await foodStorage.buildFoodLookupDatabase();
|
await storage.buildFoodLookupDatabase();
|
||||||
|
|
||||||
|
timeNow = DateTimeHelper.now();
|
||||||
|
entriesForToday = await storage.getEntriesForDate(timeNow);
|
||||||
|
|
||||||
|
var kcalLimit = await storage.readLimit();
|
||||||
|
var brightness = await storage.readBrightness();
|
||||||
|
|
||||||
runApp(
|
runApp(
|
||||||
MainApp(storage: foodStorage),
|
MainApp(
|
||||||
|
storage: storage,
|
||||||
|
kcalLimit: kcalLimit,
|
||||||
|
brightness: brightness,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class MainApp extends StatefulWidget {
|
class MainApp extends StatelessWidget {
|
||||||
final FoodStorage storage;
|
final FoodStorage storage;
|
||||||
|
final double kcalLimit;
|
||||||
|
final String brightness;
|
||||||
|
|
||||||
const MainApp({super.key, required this.storage});
|
const MainApp(
|
||||||
|
{required this.storage,
|
||||||
@override
|
required this.kcalLimit,
|
||||||
State<MainApp> createState() => _MainAppState();
|
required this.brightness,
|
||||||
}
|
super.key});
|
||||||
|
|
||||||
class _MainAppState extends State<MainApp> {
|
|
||||||
late DateTime timeNow;
|
|
||||||
late List<FoodEntryState> entriesForToday;
|
|
||||||
|
|
||||||
late double kcalLimit;
|
|
||||||
late String brightness;
|
|
||||||
|
|
||||||
late Future<bool>? initFuture;
|
|
||||||
|
|
||||||
Future<bool> asyncInit() async {
|
|
||||||
timeNow = DateTimeHelper.now();
|
|
||||||
entriesForToday = await widget.storage.getEntriesForDate(timeNow);
|
|
||||||
|
|
||||||
kcalLimit = await widget.storage.readLimit();
|
|
||||||
brightness = await widget.storage.readBrightness();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
initFuture = asyncInit();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SafeArea(
|
return SafeArea(
|
||||||
child: FutureBuilder<Object>(
|
child: MultiBlocProvider(
|
||||||
future: initFuture,
|
providers: [
|
||||||
builder: (context, snapshot) {
|
BlocProvider(
|
||||||
if (snapshot.connectionState != ConnectionState.done) {
|
create: (context) => FoodEntryBloc(
|
||||||
return Center(child: CircularProgressIndicator());
|
storage: storage,
|
||||||
|
initialState:
|
||||||
|
GlobalEntryState(foodEntries: {timeNow: entriesForToday}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
BlocProvider(
|
||||||
|
create: (context) => SettingsDataBloc(
|
||||||
|
SettingsState(kcalLimit: kcalLimit),
|
||||||
|
storage: storage),
|
||||||
|
),
|
||||||
|
BlocProvider(
|
||||||
|
create: (context) => ThemeDataBloc(
|
||||||
|
ThemeState(brightness: brightness),
|
||||||
|
storage: storage),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
child: BlocBuilder<ThemeDataBloc, ThemeState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
var newBrightness = Brightness.light;
|
||||||
|
if (state.brightness == 'dark') {
|
||||||
|
newBrightness = Brightness.dark;
|
||||||
}
|
}
|
||||||
|
|
||||||
return MultiBlocProvider(
|
return MaterialApp.router(
|
||||||
providers: [
|
routerConfig: GoRouter(
|
||||||
BlocProvider(
|
routes: [
|
||||||
create: (context) => FoodEntryBloc(
|
GoRoute(
|
||||||
storage: widget.storage,
|
path: '/',
|
||||||
initialState: GlobalEntryState(
|
builder: (context, state) {
|
||||||
foodEntries: {timeNow: entriesForToday}),
|
return PerDatePageViewController(
|
||||||
|
initialDate: DateTimeHelper.now(),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
BlocProvider(
|
),
|
||||||
create: (context) => SettingsDataBloc(
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||||
SettingsState(kcalLimit: kcalLimit),
|
supportedLocales: [
|
||||||
storage: widget.storage),
|
Locale('en'),
|
||||||
),
|
Locale('de'),
|
||||||
BlocProvider(
|
|
||||||
create: (context) => ThemeDataBloc(
|
|
||||||
ThemeState(brightness: brightness),
|
|
||||||
storage: widget.storage),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
child: BlocBuilder<ThemeDataBloc, ThemeState>(
|
theme: ThemeData(
|
||||||
builder: (context, state) {
|
dividerTheme: const DividerThemeData(space: 2),
|
||||||
var newBrightness = Brightness.light;
|
colorScheme: ColorScheme.fromSeed(
|
||||||
if (state.brightness == 'dark') {
|
seedColor: Colors.lightBlue,
|
||||||
newBrightness = Brightness.dark;
|
brightness: newBrightness,
|
||||||
}
|
),
|
||||||
|
|
||||||
return MaterialApp(
|
|
||||||
home: PerDatePageViewController(
|
|
||||||
initialDate: DateTimeHelper.now()),
|
|
||||||
localizationsDelegates:
|
|
||||||
AppLocalizations.localizationsDelegates,
|
|
||||||
supportedLocales: [
|
|
||||||
Locale('en'),
|
|
||||||
...AppLocalizations.supportedLocales,
|
|
||||||
],
|
|
||||||
theme: ThemeData(
|
|
||||||
dividerTheme: const DividerThemeData(space: 2),
|
|
||||||
colorScheme: ColorScheme.fromSeed(
|
|
||||||
seedColor: Colors.lightBlue,
|
|
||||||
brightness: newBrightness,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,10 @@
|
|||||||
/* SPDX-License-Identifier: GPL-3.0-or-later */
|
/* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||||
/* Copyright (C) 2024 Marco Groß <mgross@sw-gross.de> */
|
/* Copyright (C) 2024 Marco Groß <mgross@sw-gross.de> */
|
||||||
|
|
||||||
import 'package:calorimeter/food_entry/enter_food_widget.dart';
|
import 'package:calorimeter/food_entry/enter_food_widget.dart';
|
||||||
import 'package:calorimeter/food_entry/food_entry_bloc.dart';
|
import 'package:calorimeter/food_entry/food_entry_bloc.dart';
|
||||||
import 'package:calorimeter/food_entry/food_entry_widget.dart';
|
import 'package:calorimeter/food_entry/food_entry_widget.dart';
|
||||||
import 'package:calorimeter/storage/storage.dart';
|
|
||||||
import 'package:calorimeter/utils/row_with_spacers_widget.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
|
||||||
|
|
||||||
class FoodEntryList extends StatelessWidget {
|
class FoodEntryList extends StatelessWidget {
|
||||||
final List<FoodEntryState> entries;
|
final List<FoodEntryState> entries;
|
||||||
@ -22,77 +18,51 @@ class FoodEntryList extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var headerStyle = TextStyle(
|
return ListView.builder(
|
||||||
fontWeight: FontWeight.bold,
|
itemCount: entries.length + 1,
|
||||||
color: Theme.of(context).colorScheme.onSurface);
|
itemBuilder: (BuildContext itemBuilderContext, int listIndex) {
|
||||||
|
//last item in list is the widget to enter food
|
||||||
return Column(
|
if (listIndex == entries.length) {
|
||||||
children: [
|
return Column(
|
||||||
if (entries.isNotEmpty)
|
children: [
|
||||||
RowWidget(
|
EnterFoodWidget(
|
||||||
showDividers: true,
|
onAdd: (context, entry) {
|
||||||
Text(AppLocalizations.of(context)!.name, style: headerStyle),
|
|
||||||
Align(
|
|
||||||
alignment: Alignment.centerRight,
|
|
||||||
child: Text(AppLocalizations.of(context)!.amountPer,
|
|
||||||
style: headerStyle),
|
|
||||||
),
|
|
||||||
Align(
|
|
||||||
alignment: Alignment.centerRight,
|
|
||||||
child: Text(AppLocalizations.of(context)!.kcalper,
|
|
||||||
style: headerStyle),
|
|
||||||
),
|
|
||||||
Align(
|
|
||||||
alignment: Alignment.centerRight,
|
|
||||||
child: Text(AppLocalizations.of(context)!.kcalSum,
|
|
||||||
style: headerStyle),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (entries.isNotEmpty) Divider(),
|
|
||||||
Expanded(
|
|
||||||
child: ListView.separated(
|
|
||||||
itemCount: entries.length + 1,
|
|
||||||
separatorBuilder: (context, index) {
|
|
||||||
return Divider();
|
|
||||||
},
|
|
||||||
itemBuilder: (BuildContext itemBuilderContext, int listIndex) {
|
|
||||||
//last item in list is the widget to enter food
|
|
||||||
if (listIndex == entries.length) {
|
|
||||||
return EnterFoodWidget(
|
|
||||||
foodEntryLookupDatabase:
|
|
||||||
FoodStorage.getInstance().getFoodEntryLookupDatabase,
|
|
||||||
onAdd: (context, entry) {
|
|
||||||
context
|
|
||||||
.read<FoodEntryBloc>()
|
|
||||||
.add(FoodEntryEvent(entry: entry, forDate: date));
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
var entryIndex = listIndex;
|
|
||||||
return FoodEntryWidget(
|
|
||||||
key: ValueKey(entries[entryIndex].id),
|
|
||||||
entry: entries[entryIndex],
|
|
||||||
onDelete: (_, id) {
|
|
||||||
context
|
context
|
||||||
.read<FoodEntryBloc>()
|
.read<FoodEntryBloc>()
|
||||||
.add(FoodDeletionEvent(entryID: id, forDate: date));
|
.add(FoodEntryEvent(entry: entry, forDate: date));
|
||||||
},
|
},
|
||||||
onChange: (_, changedEntry) {
|
),
|
||||||
context.read<FoodEntryBloc>().add(
|
const SizedBox(height: 75),
|
||||||
FoodChangedEvent(newEntry: changedEntry, forDate: date),
|
],
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
onTap: (_, tappedEntry) {
|
|
||||||
context.read<FoodEntryBloc>().add(
|
var entryIndex = listIndex;
|
||||||
FoodEntryTapped(entry: tappedEntry, forDate: date),
|
return Column(
|
||||||
);
|
children: [
|
||||||
},
|
FoodEntryWidget(
|
||||||
);
|
key: ValueKey(entries[entryIndex].id),
|
||||||
},
|
entry: entries[entryIndex],
|
||||||
),
|
onDelete: (_, id) {
|
||||||
),
|
context
|
||||||
],
|
.read<FoodEntryBloc>()
|
||||||
|
.add(FoodDeletionEvent(entryID: id, forDate: date));
|
||||||
|
},
|
||||||
|
onChange: (_, changedEntry) {
|
||||||
|
context.read<FoodEntryBloc>().add(
|
||||||
|
FoodChangedEvent(newEntry: changedEntry, forDate: date),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onTap: (_, tappedEntry) {
|
||||||
|
context.read<FoodEntryBloc>().add(
|
||||||
|
FoodEntryTapped(entry: tappedEntry, forDate: date),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
/* SPDX-License-Identifier: GPL-3.0-or-later */
|
/* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||||
/* Copyright (C) 2024 Marco Groß <mgross@sw-gross.de> */
|
/* Copyright (C) 2024 Marco Groß <mgross@sw-gross.de> */
|
||||||
|
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
|
|
||||||
import 'package:calorimeter/perdate/perdate_pageview_controller.dart';
|
import 'package:calorimeter/perdate/perdate_pageview_controller.dart';
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
/* Copyright (C) 2024 Marco Groß <mgross@sw-gross.de> */
|
/* Copyright (C) 2024 Marco Groß <mgross@sw-gross.de> */
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
|
|
||||||
|
import 'package:barcode_scan2/barcode_scan2.dart';
|
||||||
import 'package:calorimeter/food_entry/food_entry_bloc.dart';
|
import 'package:calorimeter/food_entry/food_entry_bloc.dart';
|
||||||
import 'package:calorimeter/perdate/perdate_pageview.dart';
|
import 'package:calorimeter/perdate/perdate_pageview.dart';
|
||||||
import 'package:calorimeter/utils/app_drawer.dart';
|
import 'package:calorimeter/utils/app_drawer.dart';
|
||||||
@ -39,9 +40,22 @@ class PerDatePageViewController extends StatelessWidget {
|
|||||||
initialDate: initialDate,
|
initialDate: initialDate,
|
||||||
initialOffset: initialOffset,
|
initialOffset: initialOffset,
|
||||||
),
|
),
|
||||||
child: Builder(
|
child: Builder(builder: (context) {
|
||||||
builder: (context) {
|
return BackButtonListener(
|
||||||
return Scaffold(
|
onBackButtonPressed: () async {
|
||||||
|
context.read<PageViewStateProvider>().backButtonWasPressed = true;
|
||||||
|
var visitedIndexes =
|
||||||
|
context.read<PageViewStateProvider>().visitedIndexes;
|
||||||
|
if (visitedIndexes.length == 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
visitedIndexes.removeLast();
|
||||||
|
pageController.jumpToPage(visitedIndexes.last);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
child: Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Builder(builder: (context) {
|
title: Builder(builder: (context) {
|
||||||
return Text(DateFormat.yMMMMd(
|
return Text(DateFormat.yMMMMd(
|
||||||
@ -60,45 +74,42 @@ class PerDatePageViewController extends StatelessWidget {
|
|||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
drawer: const AppDrawer(),
|
drawer: const AppDrawer(),
|
||||||
floatingActionButton: _getFABs(context),
|
floatingActionButton: OverflowBar(children: [
|
||||||
|
ScanFoodFAB(
|
||||||
|
onPressed: () {
|
||||||
|
var result = BarcodeScanner.scan();
|
||||||
|
context.read<FoodEntryBloc>().add(
|
||||||
|
BarcodeScanned(
|
||||||
|
scanResultFuture: result,
|
||||||
|
forDate: context
|
||||||
|
.read<PageViewStateProvider>()
|
||||||
|
.displayedDate,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
CalendarFAB(
|
||||||
|
startFromDate: DateTimeHelper.now(),
|
||||||
|
onDateSelected: (dateSelected) {
|
||||||
|
if (dateSelected == null) return;
|
||||||
|
|
||||||
|
var dateDiff = dateSelected.difference(initialDate).inDays;
|
||||||
|
|
||||||
|
log("dateDiff = $dateDiff");
|
||||||
|
pageController.jumpToPage(initialOffset - dateDiff);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]),
|
||||||
floatingActionButtonLocation:
|
floatingActionButtonLocation:
|
||||||
FloatingActionButtonLocation.endDocked,
|
FloatingActionButtonLocation.endDocked,
|
||||||
body: PerDatePageView(
|
body: PerDatePageView(
|
||||||
pageController: pageController,
|
pageController: pageController,
|
||||||
initialDate: initialDate,
|
initialDate: initialDate,
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
},
|
);
|
||||||
),
|
}),
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
OverflowBar _getFABs(BuildContext context) {
|
|
||||||
return OverflowBar(
|
|
||||||
children: [
|
|
||||||
ScanFoodFAB(
|
|
||||||
onPressed: () {
|
|
||||||
context.read<FoodEntryBloc>().add(
|
|
||||||
BarcodeAboutToBeScanned(
|
|
||||||
forDate:
|
|
||||||
context.read<PageViewStateProvider>().displayedDate,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
CalendarFAB(
|
|
||||||
startFromDate: DateTimeHelper.now(),
|
|
||||||
onDateSelected: (dateSelected) {
|
|
||||||
if (dateSelected == null) return;
|
|
||||||
|
|
||||||
var dateDiff = dateSelected.difference(initialDate).inDays;
|
|
||||||
|
|
||||||
log("dateDiff = $dateDiff");
|
|
||||||
pageController.jumpToPage(initialOffset - dateDiff);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -107,7 +118,6 @@ class PageViewStateProvider with ChangeNotifier {
|
|||||||
DateTime _displayedDate;
|
DateTime _displayedDate;
|
||||||
final List<int> _visitedIndexes;
|
final List<int> _visitedIndexes;
|
||||||
bool _backButtonWasPressed = false;
|
bool _backButtonWasPressed = false;
|
||||||
bool _isVisible = false;
|
|
||||||
|
|
||||||
PageViewStateProvider({required DateTime initialDate, int initialOffset = 0})
|
PageViewStateProvider({required DateTime initialDate, int initialOffset = 0})
|
||||||
: _displayedDate = initialDate,
|
: _displayedDate = initialDate,
|
||||||
@ -124,9 +134,6 @@ class PageViewStateProvider with ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
void setVisible(vis) => _isVisible = true;
|
|
||||||
get isVisible => _isVisible;
|
|
||||||
|
|
||||||
get visitedIndexes => _visitedIndexes;
|
get visitedIndexes => _visitedIndexes;
|
||||||
|
|
||||||
void addVisitedIndex(int index) {
|
void addVisitedIndex(int index) {
|
||||||
|
@ -29,8 +29,8 @@ class _PerDateWidgetState extends State<PerDateWidget>
|
|||||||
|
|
||||||
return BlocConsumer<FoodEntryBloc, GlobalEntryState>(
|
return BlocConsumer<FoodEntryBloc, GlobalEntryState>(
|
||||||
listener: (context, pageState) {
|
listener: (context, pageState) {
|
||||||
if (pageState.appError != null) {
|
if (pageState.errorString != null) {
|
||||||
showNewSnackbarWith(context, pageState.appError!);
|
showNewSnackbarWith(context, pageState.errorString!);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
builder: (context, pageState) {
|
builder: (context, pageState) {
|
||||||
@ -41,10 +41,9 @@ class _PerDateWidgetState extends State<PerDateWidget>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void showNewSnackbarWith(BuildContext context, GlobalAppError error) {
|
void showNewSnackbarWith(BuildContext context, String text) {
|
||||||
var snackbar = ErrorSnackbar(
|
var snackbar =
|
||||||
colorScheme: Theme.of(context).colorScheme,
|
ErrorSnackbar(colorScheme: Theme.of(context).colorScheme, text: text);
|
||||||
text: error.toErrorString(context));
|
|
||||||
|
|
||||||
ScaffoldMessenger.of(context)
|
ScaffoldMessenger.of(context)
|
||||||
..removeCurrentSnackBar()
|
..removeCurrentSnackBar()
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
/* SPDX-License-Identifier: GPL-3.0-or-later */
|
/* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||||
/* Copyright (C) 2024 Marco Groß <mgross@sw-gross.de> */
|
/* Copyright (C) 2024 Marco Groß <mgross@sw-gross.de> */
|
||||||
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:calorimeter/food_entry/food_entry_bloc.dart';
|
import 'package:calorimeter/food_entry/food_entry_bloc.dart';
|
||||||
@ -88,7 +87,6 @@ class FoodStorage {
|
|||||||
|
|
||||||
String fullString = '';
|
String fullString = '';
|
||||||
for (var entry in foodEntries) {
|
for (var entry in foodEntries) {
|
||||||
if (entry.waitingForNetwork) continue;
|
|
||||||
fullString += '${entry.toString()}\n';
|
fullString += '${entry.toString()}\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
/* SPDX-License-Identifier: GPL-3.0-or-later */
|
/* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||||
/* Copyright (C) 2024 Marco Groß <mgross@sw-gross.de> */
|
/* Copyright (C) 2024 Marco Groß <mgross@sw-gross.de> */
|
||||||
import 'package:calorimeter/perdate/perdate_pageview_controller.dart';
|
|
||||||
import 'package:calorimeter/utils/date_time_helper.dart';
|
|
||||||
import 'package:calorimeter/utils/settings.dart';
|
import 'package:calorimeter/utils/settings.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
@ -31,16 +29,6 @@ class AppDrawer extends StatelessWidget {
|
|||||||
title: Text(AppLocalizations.of(context)!.menu),
|
title: Text(AppLocalizations.of(context)!.menu),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ListTile(
|
|
||||||
title: Text(AppLocalizations.of(context)!.today),
|
|
||||||
trailing: const Icon(Icons.home),
|
|
||||||
onTap: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
Navigator.of(context).push(MaterialPageRoute(
|
|
||||||
builder: (context) => PerDatePageViewController(
|
|
||||||
initialDate: DateTimeHelper.now())));
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(AppLocalizations.of(context)!.settings),
|
title: Text(AppLocalizations.of(context)!.settings),
|
||||||
trailing: const Icon(Icons.settings),
|
trailing: const Icon(Icons.settings),
|
||||||
|
@ -7,61 +7,19 @@ class RowWidget extends StatelessWidget {
|
|||||||
final Widget? widget2;
|
final Widget? widget2;
|
||||||
final Widget? widget3;
|
final Widget? widget3;
|
||||||
final Widget? widget4;
|
final Widget? widget4;
|
||||||
final bool showDividers;
|
|
||||||
|
|
||||||
const RowWidget(this.widget1, this.widget2, this.widget3, this.widget4,
|
const RowWidget(this.widget1, this.widget2, this.widget3, this.widget4,
|
||||||
{super.key, required this.showDividers});
|
{super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return IntrinsicHeight(
|
return Row(
|
||||||
child: ConstrainedBox(
|
children: [
|
||||||
constraints: BoxConstraints(minHeight: 48),
|
Expanded(flex: 10, child: widget1 ?? Container()),
|
||||||
child: Row(
|
Expanded(flex: 6, child: widget2 ?? Container()),
|
||||||
children: [
|
Expanded(flex: 6, child: widget3 ?? Container()),
|
||||||
Expanded(
|
Expanded(flex: 6, child: widget4 ?? Container()),
|
||||||
flex: 10,
|
],
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
|
||||||
child: widget1 ?? Container(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Opacity(
|
|
||||||
opacity: showDividers ? 1.0 : 0.0,
|
|
||||||
child: VerticalDivider(),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
flex: 6,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
|
||||||
child: widget2 ?? Container(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Opacity(
|
|
||||||
opacity: showDividers ? 1.0 : 0.0,
|
|
||||||
child: VerticalDivider(),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
flex: 6,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
|
||||||
child: widget3 ?? Container(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Opacity(
|
|
||||||
opacity: showDividers ? 1.0 : 0.0,
|
|
||||||
child: VerticalDivider(),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
flex: 6,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
|
||||||
child: widget4 ?? Container(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,8 +36,6 @@ class _SettingsWidgetState extends State<SettingsWidget> {
|
|||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Text(AppLocalizations.of(context)!.dayLimit),
|
title: Text(AppLocalizations.of(context)!.dayLimit),
|
||||||
content: TextField(
|
content: TextField(
|
||||||
decoration: InputDecoration(
|
|
||||||
hintText: state.kcalLimit.toString()),
|
|
||||||
controller: kcalPerDayCtrl,
|
controller: kcalPerDayCtrl,
|
||||||
onSubmitted: (val) => submitDailyKcal()),
|
onSubmitted: (val) => submitDailyKcal()),
|
||||||
actions: [
|
actions: [
|
||||||
@ -63,7 +61,7 @@ class _SettingsWidgetState extends State<SettingsWidget> {
|
|||||||
try {
|
try {
|
||||||
setting = double.parse(kcalPerDayCtrl.text);
|
setting = double.parse(kcalPerDayCtrl.text);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setting = context.read<SettingsDataBloc>().dailyKcal;
|
setting = 2000.0;
|
||||||
}
|
}
|
||||||
context.read<SettingsDataBloc>().add(DailyKcalLimitUpdated(kcal: setting));
|
context.read<SettingsDataBloc>().add(DailyKcalLimitUpdated(kcal: setting));
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
|
@ -15,8 +15,6 @@ class SettingsDataBloc extends Bloc<SettingsEvent, SettingsState> {
|
|||||||
await storage.updateLimit(event.kcal);
|
await storage.updateLimit(event.kcal);
|
||||||
emit(SettingsState(kcalLimit: event.kcal));
|
emit(SettingsState(kcalLimit: event.kcal));
|
||||||
}
|
}
|
||||||
|
|
||||||
get dailyKcal => state.kcalLimit;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class SettingsEvent {}
|
class SettingsEvent {}
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 93 KiB |
Binary file not shown.
Before Width: | Height: | Size: 94 KiB |
Binary file not shown.
Before Width: | Height: | Size: 2.5 MiB |
BIN
metadata/android/en-US/images/phoneScreenshots/screen01.png
Normal file
BIN
metadata/android/en-US/images/phoneScreenshots/screen01.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 98 KiB |
BIN
metadata/android/en-US/images/phoneScreenshots/screen02.png
Normal file
BIN
metadata/android/en-US/images/phoneScreenshots/screen02.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 97 KiB |
306
pubspec.lock
306
pubspec.lock
@ -5,31 +5,31 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: _fe_analyzer_shared
|
name: _fe_analyzer_shared
|
||||||
sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab"
|
sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "76.0.0"
|
version: "72.0.0"
|
||||||
_macros:
|
_macros:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: dart
|
description: dart
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.3.3"
|
version: "0.3.2"
|
||||||
analyzer:
|
analyzer:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: analyzer
|
name: analyzer
|
||||||
sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e"
|
sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.11.0"
|
version: "6.7.0"
|
||||||
archive:
|
archive:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: archive
|
name: archive
|
||||||
sha256: "6199c74e3db4fbfbd04f66d739e72fe11c8a8957d5f219f1f4482dbde6420b5a"
|
sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.2"
|
version: "3.6.1"
|
||||||
args:
|
args:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -70,70 +70,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.1.1"
|
||||||
build:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: build
|
|
||||||
sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.4.2"
|
|
||||||
build_config:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: build_config
|
|
||||||
sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.1.2"
|
|
||||||
build_daemon:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: build_daemon
|
|
||||||
sha256: "294a2edaf4814a378725bfe6358210196f5ea37af89ecd81bfa32960113d4948"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "4.0.3"
|
|
||||||
build_resolvers:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: build_resolvers
|
|
||||||
sha256: "99d3980049739a985cf9b21f30881f46db3ebc62c5b8d5e60e27440876b1ba1e"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.4.3"
|
|
||||||
build_runner:
|
|
||||||
dependency: "direct dev"
|
|
||||||
description:
|
|
||||||
name: build_runner
|
|
||||||
sha256: "74691599a5bc750dc96a6b4bfd48f7d9d66453eab04c7f4063134800d6a5c573"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.4.14"
|
|
||||||
build_runner_core:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: build_runner_core
|
|
||||||
sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "8.0.0"
|
|
||||||
built_collection:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: built_collection
|
|
||||||
sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "5.1.1"
|
|
||||||
built_value:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: built_value
|
|
||||||
sha256: "28a712df2576b63c6c005c465989a348604960c0958d28be5303ba9baa841ac2"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "8.9.3"
|
|
||||||
characters:
|
characters:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -166,22 +102,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.1"
|
version: "1.1.1"
|
||||||
code_builder:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: code_builder
|
|
||||||
sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "4.10.1"
|
|
||||||
collection:
|
collection:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: collection
|
name: collection
|
||||||
sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf
|
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.19.0"
|
version: "1.18.0"
|
||||||
convert:
|
convert:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -206,14 +134,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.6"
|
version: "3.0.6"
|
||||||
dart_style:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: dart_style
|
|
||||||
sha256: "27eb0ae77836989a3bc541ce55595e8ceee0992807f14511552a898ddd0d88ac"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "3.0.1"
|
|
||||||
fake_async:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -234,10 +154,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: file
|
name: file
|
||||||
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
|
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.0.0"
|
version: "7.0.1"
|
||||||
fixnum:
|
fixnum:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -259,11 +179,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.1.6"
|
version: "8.1.6"
|
||||||
flutter_driver:
|
|
||||||
dependency: transitive
|
|
||||||
description: flutter
|
|
||||||
source: sdk
|
|
||||||
version: "0.0.0"
|
|
||||||
flutter_launcher_icons:
|
flutter_launcher_icons:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
@ -290,6 +205,11 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
flutter_web_plugins:
|
||||||
|
dependency: transitive
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
frontend_server_client:
|
frontend_server_client:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -298,11 +218,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.0"
|
version: "4.0.0"
|
||||||
fuchsia_remote_debug_protocol:
|
|
||||||
dependency: transitive
|
|
||||||
description: flutter
|
|
||||||
source: sdk
|
|
||||||
version: "0.0.0"
|
|
||||||
glob:
|
glob:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -311,51 +226,38 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.2"
|
version: "2.1.2"
|
||||||
graphs:
|
go_router:
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: graphs
|
|
||||||
sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.3.2"
|
|
||||||
http:
|
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: http
|
name: go_router
|
||||||
sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
|
sha256: "2fd11229f59e23e967b0775df8d5948a519cd7e1e8b6e849729e010587b46539"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.2"
|
version: "14.6.2"
|
||||||
http_multi_server:
|
http_multi_server:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: http_multi_server
|
name: http_multi_server
|
||||||
sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8
|
sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.2.2"
|
version: "3.2.1"
|
||||||
http_parser:
|
http_parser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: http_parser
|
name: http_parser
|
||||||
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
|
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.1.2"
|
version: "4.0.2"
|
||||||
image:
|
image:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image
|
name: image
|
||||||
sha256: "8346ad4b5173924b5ddddab782fc7d8a6300178c8b1dc427775405a01701c4a6"
|
sha256: f31d52537dc417fdcde36088fdf11d191026fd5e4fae742491ebd40e5a8bea7d
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.5.2"
|
version: "4.3.0"
|
||||||
integration_test:
|
|
||||||
dependency: "direct dev"
|
|
||||||
description: flutter
|
|
||||||
source: sdk
|
|
||||||
version: "0.0.0"
|
|
||||||
intl:
|
intl:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -368,10 +270,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: io
|
name: io
|
||||||
sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b
|
sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.5"
|
version: "1.0.4"
|
||||||
js:
|
js:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -392,18 +294,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: leak_tracker
|
name: leak_tracker
|
||||||
sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06"
|
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "10.0.7"
|
version: "10.0.5"
|
||||||
leak_tracker_flutter_testing:
|
leak_tracker_flutter_testing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: leak_tracker_flutter_testing
|
name: leak_tracker_flutter_testing
|
||||||
sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379"
|
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.8"
|
version: "3.0.5"
|
||||||
leak_tracker_testing:
|
leak_tracker_testing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -416,10 +318,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: lints
|
name: lints
|
||||||
sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7
|
sha256: "3315600f3fb3b135be672bf4a178c55f274bebe368325ae18462c89ac1e3b413"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.1.1"
|
version: "5.0.0"
|
||||||
logging:
|
logging:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -432,10 +334,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: macros
|
name: macros
|
||||||
sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656"
|
sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.3-main.0"
|
version: "0.1.2-main.4"
|
||||||
matcher:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -468,14 +370,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.0"
|
version: "2.0.0"
|
||||||
mockito:
|
|
||||||
dependency: "direct dev"
|
|
||||||
description:
|
|
||||||
name: mockito
|
|
||||||
sha256: f99d8d072e249f719a5531735d146d8cf04c580d93920b04de75bef6dfb2daf6
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "5.4.5"
|
|
||||||
nested:
|
nested:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -496,10 +390,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: package_config
|
name: package_config
|
||||||
sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67"
|
sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.1.0"
|
||||||
path:
|
path:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -520,10 +414,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_android
|
name: path_provider_android
|
||||||
sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2"
|
sha256: "8c4967f8b7cb46dc914e178daa29813d83ae502e0529d7b0478330616a691ef7"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.15"
|
version: "2.2.14"
|
||||||
path_provider_foundation:
|
path_provider_foundation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -541,7 +435,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.1"
|
version: "2.2.1"
|
||||||
path_provider_platform_interface:
|
path_provider_platform_interface:
|
||||||
dependency: "direct main"
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_platform_interface
|
name: path_provider_platform_interface
|
||||||
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
|
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
|
||||||
@ -568,12 +462,12 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: platform
|
name: platform
|
||||||
sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65"
|
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.5"
|
version: "3.1.6"
|
||||||
plugin_platform_interface:
|
plugin_platform_interface:
|
||||||
dependency: "direct main"
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: plugin_platform_interface
|
name: plugin_platform_interface
|
||||||
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||||
@ -588,22 +482,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.5.1"
|
version: "1.5.1"
|
||||||
posix:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: posix
|
|
||||||
sha256: a0117dc2167805aa9125b82eee515cc891819bac2f538c83646d355b16f58b9a
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "6.0.1"
|
|
||||||
process:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: process
|
|
||||||
sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "5.0.2"
|
|
||||||
protobuf:
|
protobuf:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -624,18 +502,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: pub_semver
|
name: pub_semver
|
||||||
sha256: "7b3cfbf654f3edd0c6298ecd5be782ce997ddf0e00531b9464b55245185bbbbd"
|
sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.5"
|
version: "2.1.4"
|
||||||
pubspec_parse:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: pubspec_parse
|
|
||||||
sha256: "81876843eb50dc2e1e5b151792c9a985c5ed2536914115ed04e9c8528f6647b0"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.4.0"
|
|
||||||
settings_ui:
|
settings_ui:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -648,10 +518,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shelf
|
name: shelf
|
||||||
sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
|
sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.2"
|
version: "1.4.1"
|
||||||
shelf_packages_handler:
|
shelf_packages_handler:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -680,15 +550,7 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.99"
|
||||||
source_gen:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: source_gen
|
|
||||||
sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.0.0"
|
|
||||||
source_map_stack_trace:
|
source_map_stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -701,10 +563,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: source_maps
|
name: source_maps
|
||||||
sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812"
|
sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.10.13"
|
version: "0.10.12"
|
||||||
source_span:
|
source_span:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -725,10 +587,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: stack_trace
|
name: stack_trace
|
||||||
sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377"
|
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.12.0"
|
version: "1.11.1"
|
||||||
stream_channel:
|
stream_channel:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -737,30 +599,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.2"
|
version: "2.1.2"
|
||||||
stream_transform:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: stream_transform
|
|
||||||
sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.1.1"
|
|
||||||
string_scanner:
|
string_scanner:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: string_scanner
|
name: string_scanner
|
||||||
sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3"
|
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.0"
|
version: "1.2.0"
|
||||||
sync_http:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: sync_http
|
|
||||||
sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.3.1"
|
|
||||||
term_glyph:
|
term_glyph:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -773,34 +619,26 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: test
|
name: test
|
||||||
sha256: "713a8789d62f3233c46b4a90b174737b2c04cb6ae4500f2aa8b1be8f03f5e67f"
|
sha256: "7ee44229615f8f642b68120165ae4c2a75fe77ae2065b1e55ae4711f6cf0899e"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.25.8"
|
version: "1.25.7"
|
||||||
test_api:
|
test_api:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c"
|
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.3"
|
version: "0.7.2"
|
||||||
test_core:
|
test_core:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_core
|
name: test_core
|
||||||
sha256: "12391302411737c176b0b5d6491f466b0dd56d4763e347b6714efbaa74d7953d"
|
sha256: "55ea5a652e38a1dfb32943a7973f3681a60f872f8c3a05a14664ad54ef9c6696"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.5"
|
version: "0.6.4"
|
||||||
timing:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: timing
|
|
||||||
sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.0.2"
|
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -837,18 +675,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: vm_service
|
name: vm_service
|
||||||
sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b
|
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "14.3.0"
|
version: "14.2.5"
|
||||||
watcher:
|
watcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: watcher
|
name: watcher
|
||||||
sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104"
|
sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.1"
|
version: "1.1.0"
|
||||||
web:
|
web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -873,14 +711,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.1"
|
version: "3.0.1"
|
||||||
webdriver:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: webdriver
|
|
||||||
sha256: "3d773670966f02a646319410766d3b5e1037efb7f07cc68f844d5e06cd4d61c8"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "3.0.4"
|
|
||||||
webkit_inspection_protocol:
|
webkit_inspection_protocol:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -909,10 +739,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: yaml
|
name: yaml
|
||||||
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
|
sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.3"
|
version: "3.1.2"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.6.0 <4.0.0"
|
dart: ">=3.5.3 <4.0.0"
|
||||||
flutter: ">=3.24.0"
|
flutter: ">=3.24.0"
|
||||||
|
@ -19,19 +19,13 @@ dependencies:
|
|||||||
barcode_scan2: ^4.3.3
|
barcode_scan2: ^4.3.3
|
||||||
provider: ^6.1.2
|
provider: ^6.1.2
|
||||||
test: ^1.25.7
|
test: ^1.25.7
|
||||||
path_provider_platform_interface: ^2.1.2
|
go_router: ^14.3.0
|
||||||
plugin_platform_interface: ^2.1.8
|
|
||||||
http: ^1.2.2
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_lints: ^5.0.0
|
flutter_lints: ^5.0.0
|
||||||
flutter_launcher_icons: ^0.14.1
|
flutter_launcher_icons: ^0.14.1
|
||||||
integration_test:
|
|
||||||
sdk: flutter
|
|
||||||
mockito: ^5.4.5
|
|
||||||
build_runner: ^2.4.14
|
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
|
59
test/split_with_ignore_test.dart
Normal file
59
test/split_with_ignore_test.dart
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import 'package:calorimeter/storage/storage.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group(
|
||||||
|
'Test custom split with ignore',
|
||||||
|
() {
|
||||||
|
test('string without ignoring', () {
|
||||||
|
var testString = 'This is a test string';
|
||||||
|
var resultingList = testString.splitWithIgnore(' ');
|
||||||
|
|
||||||
|
expect(resultingList[0], equals('This'));
|
||||||
|
expect(resultingList[1], equals('is'));
|
||||||
|
expect(resultingList[2], equals('a'));
|
||||||
|
expect(resultingList[3], equals('test'));
|
||||||
|
expect(resultingList[4], equals('string'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('string that does not contain the ignored character', () {
|
||||||
|
var testString = 'This is a test string';
|
||||||
|
var resultingList = testString.splitWithIgnore(' ', ignoreIn: '"');
|
||||||
|
|
||||||
|
expect(resultingList[0], equals('This'));
|
||||||
|
expect(resultingList[1], equals('is'));
|
||||||
|
expect(resultingList[2], equals('a'));
|
||||||
|
expect(resultingList[3], equals('test'));
|
||||||
|
expect(resultingList[4], equals('string'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'string that contains ignored character',
|
||||||
|
() {
|
||||||
|
var testString = 'This is "a test" string';
|
||||||
|
var resultingList = testString.splitWithIgnore(' ', ignoreIn: '"');
|
||||||
|
|
||||||
|
expect(resultingList[0], equals('This'));
|
||||||
|
expect(resultingList[1], equals('is'));
|
||||||
|
expect(resultingList[2], equals('"a test"'));
|
||||||
|
expect(resultingList[3], equals('string'));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test(
|
||||||
|
'string that contains commas that should be ignored',
|
||||||
|
() {
|
||||||
|
var testString =
|
||||||
|
'f9a96b80-71f9-11ef-8df4-f3628a737a16,"Erdnüsse, geröstet",120.0,100.0';
|
||||||
|
var resultingList = testString.splitWithIgnore(',', ignoreIn: '"');
|
||||||
|
|
||||||
|
expect(
|
||||||
|
resultingList[0], equals('f9a96b80-71f9-11ef-8df4-f3628a737a16'));
|
||||||
|
expect(resultingList[1], equals('"Erdnüsse, geröstet"'));
|
||||||
|
expect(resultingList[2], equals('120.0'));
|
||||||
|
expect(resultingList[3], equals('100.0'));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user