Compare commits

..

5 Commits

Author SHA1 Message Date
cfc712458f New flutter version 2024-12-24 13:32:06 +01:00
b87c288527 Version 1.0.4
Fix handling of kcal amount extraction from json
2024-12-23 19:39:51 +01:00
63e9b471b4 Merge pull request 'Fix json misalignment and error representation' (#8) from fix-json-misalignment-and-error-representation into master
Reviewed-on: #8
2024-12-22 17:29:20 +00:00
7b440e82aa Remove unused function 2024-12-22 18:23:25 +01:00
69bee8de7f fix everything 2024-12-22 18:13:29 +01:00
11 changed files with 119 additions and 183 deletions

@ -1 +1 @@
Subproject commit dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 Subproject commit 17025dd88227cd9532c33fa78f5250d548d87e9a

View File

@ -15,6 +15,7 @@ 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
@ -33,8 +34,8 @@ android {
applicationId = "de.swgross.calorimeter" applicationId = "de.swgross.calorimeter"
minSdk = flutter.minSdkVersion minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion targetSdk = flutter.targetSdkVersion
versionCode = 3 versionCode = 4
versionName = "1.0.3" versionName = "1.0.4"
} }
signingConfigs { signingConfigs {

View File

@ -2,8 +2,11 @@
/* 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> {
@ -16,7 +19,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<BarcodeScanned>(handleBarcodeScannedEvent); on<BarcodeAboutToBeScanned>(handleBarcodeScannedEvent);
on<FoodEntryTapped>(handleFoodEntryTapped); on<FoodEntryTapped>(handleFoodEntryTapped);
} }
void handlePageBeingInitialized( void handlePageBeingInitialized(
@ -92,12 +95,24 @@ class FoodEntryBloc extends Bloc<FoodEvent, GlobalEntryState> {
} }
void handleBarcodeScannedEvent( void handleBarcodeScannedEvent(
BarcodeScanned event, Emitter<GlobalEntryState> emit) async { BarcodeAboutToBeScanned 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 entriesForDate = state.foodEntries[event.forDate]; var entriesForDate = state.foodEntries[event.forDate];
if (entriesForDate == null) return; if (entriesForDate == null) return;
var client = FoodFactLookupClient(); var client = FoodFactLookupClient();
var scanResult = await event.scanResultFuture;
if (scanResult.type == ResultType.Cancelled) { if (scanResult.type == ResultType.Cancelled) {
return; return;
@ -105,7 +120,7 @@ class FoodEntryBloc extends Bloc<FoodEvent, GlobalEntryState> {
if (scanResult.type == ResultType.Error) { if (scanResult.type == ResultType.Error) {
emit(GlobalEntryState( emit(GlobalEntryState(
foodEntries: state.foodEntries, foodEntries: state.foodEntries,
errorString: "Fehler beim Scannen des Barcodes")); appError: GlobalAppError(GlobalAppErrorType.errGeneralError)));
return; return;
} }
var responseFuture = client.retrieveFoodInfo(scanResult.rawContent); var responseFuture = client.retrieveFoodInfo(scanResult.rawContent);
@ -117,6 +132,7 @@ class FoodEntryBloc extends Bloc<FoodEvent, GlobalEntryState> {
waitingForNetwork: true, waitingForNetwork: true,
isSelected: false, isSelected: false,
); );
entriesForDate.add(newEntryWaiting); entriesForDate.add(newEntryWaiting);
var newFoodEntries = state.foodEntries; var newFoodEntries = state.foodEntries;
newFoodEntries.addAll({event.forDate: entriesForDate}); newFoodEntries.addAll({event.forDate: entriesForDate});
@ -138,7 +154,7 @@ class FoodEntryBloc extends Bloc<FoodEvent, GlobalEntryState> {
emit(GlobalEntryState( emit(GlobalEntryState(
foodEntries: newFoodEntries, foodEntries: newFoodEntries,
errorString: "Barcode konnte nicht gefunden werden.")); appError: GlobalAppError(GlobalAppErrorType.errbarcodeNotFound)));
return; return;
} }
if (response.status == if (response.status ==
@ -149,7 +165,8 @@ class FoodEntryBloc extends Bloc<FoodEvent, GlobalEntryState> {
emit(GlobalEntryState( emit(GlobalEntryState(
foodEntries: newFoodEntries, foodEntries: newFoodEntries,
errorString: "OpenFoodFacts-Server konnte nicht erreicht werden.")); appError:
GlobalAppError(GlobalAppErrorType.errServerNotReachable)));
return; return;
} }
@ -224,10 +241,8 @@ class FoodDeletionEvent extends FoodEvent {
FoodDeletionEvent({required this.entryID, required super.forDate}); FoodDeletionEvent({required this.entryID, required super.forDate});
} }
class BarcodeScanned extends FoodEvent { class BarcodeAboutToBeScanned extends FoodEvent {
final Future<ScanResult> scanResultFuture; BarcodeAboutToBeScanned({required super.forDate});
BarcodeScanned({required this.scanResultFuture, required super.forDate});
} }
class FoodEntryTapped extends FoodEvent { class FoodEntryTapped extends FoodEvent {
@ -236,12 +251,16 @@ 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 String? errorString; final GlobalAppError? appError;
GlobalEntryState({required this.foodEntries, this.errorString}); GlobalEntryState({required this.foodEntries, this.appError});
factory GlobalEntryState.init() { factory GlobalEntryState.init() {
return GlobalEntryState(foodEntries: {}); return GlobalEntryState(foodEntries: {});
@ -305,3 +324,29 @@ 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;
}
}
}

View File

@ -56,19 +56,41 @@ class FoodFactModel {
}); });
factory FoodFactModel.fromJson(Map<String, dynamic> json) { factory FoodFactModel.fromJson(Map<String, dynamic> json) {
String quantityString = json['product']['product_quantity'] ?? "0"; int kcalPer100gForModel = 0;
int quantity; int kcalPer100g = 0;
int kcalPer100gPrepared = 0;
try { try {
quantity = int.parse(quantityString); kcalPer100g = (json['product']['nutriments']['energy-kcal_100g'] as num)
.toDouble()
.ceil();
kcalPer100gForModel = kcalPer100g;
} catch (e) {
try {
kcalPer100gPrepared =
(json['product']['nutriments']['energy-kcal_prepared_100g'] as num)
.toDouble()
.ceil();
kcalPer100gForModel = kcalPer100gPrepared;
} catch (e) {
kcalPer100gForModel = 0;
}
}
String quantityString = json['product']['product_quantity'] ?? "0";
double quantity;
try {
quantity = double.parse(quantityString);
} catch (e) { } catch (e) {
quantity = 0; quantity = 0;
} }
return FoodFactModel( return FoodFactModel(
name: json['product']['product_name'], name: json['product']['product_name'] ?? "",
kcalPer100g: json['product']['nutriments']['energy-kcal_100g'], kcalPer100g: kcalPer100gForModel,
mass: quantity); mass: quantity.ceil(),
);
} }
} }

View File

@ -10,5 +10,9 @@
"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."
} }

View File

@ -10,5 +10,9 @@
"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."
} }

View File

@ -1,113 +0,0 @@
import 'dart:developer';
import 'package:calorimeter/utils/settings_bloc.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class GraphsPageWidget extends StatefulWidget {
const GraphsPageWidget({super.key});
@override
State<GraphsPageWidget> createState() => _GraphsPageWidgetState();
}
class _GraphsPageWidgetState extends State<GraphsPageWidget> {
late double data1;
@override
void initState() {
super.initState();
data1 = 40;
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(100),
child: BlocBuilder<SettingsDataBloc, SettingsState>(
builder: (context, state) {
return BarChart(
BarChartData(
extraLinesData: ExtraLinesData(
horizontalLines: [
HorizontalLine(
y: state.kcalLimit,
color: Colors.red,
),
],
),
titlesData: FlTitlesData(
topTitles: AxisTitles(),
rightTitles: AxisTitles(),
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (value, meta) {
final titles = <String>[
'Mo',
'Di',
'Mi',
'Do',
'Fr',
'Sa',
'So'
];
return SideTitleWidget(
axisSide: meta.axisSide,
child: Text(
titles[value.toInt()],
),
);
},
),
),
),
gridData: FlGridData(
horizontalInterval: state.kcalLimit / 10,
drawVerticalLine: false,
),
maxY: state.kcalLimit + 500,
barGroups: [
BarChartGroupData(
x: 0,
barRods: [BarChartRodData(toY: state.kcalLimit)],
),
BarChartGroupData(
x: 1,
barRods: [BarChartRodData(toY: state.kcalLimit - 500)],
),
BarChartGroupData(
x: 2,
barRods: [BarChartRodData(toY: state.kcalLimit + 200)],
),
BarChartGroupData(
x: 3,
barRods: [BarChartRodData(toY: state.kcalLimit + data1)],
),
BarChartGroupData(
x: 4,
barRods: [BarChartRodData(toY: state.kcalLimit + data1)],
),
BarChartGroupData(
x: 5,
barRods: [BarChartRodData(toY: state.kcalLimit + data1)],
),
BarChartGroupData(
x: 6,
barRods: [BarChartRodData(toY: state.kcalLimit + data1)],
),
],
),
);
}),
),
floatingActionButton: FloatingActionButton(onPressed: () {
setState(() {
data1 *= 1.1;
});
}));
}
}

View File

@ -2,13 +2,11 @@
/* 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';
import 'package:calorimeter/utils/calendar_floating_button.dart'; import 'package:calorimeter/utils/calendar_floating_button.dart';
import 'package:calorimeter/utils/date_time_helper.dart'; import 'package:calorimeter/utils/date_time_helper.dart';
import 'package:calorimeter/pages/graphs_page.dart';
import 'package:calorimeter/utils/rectangular_notch_shape.dart'; import 'package:calorimeter/utils/rectangular_notch_shape.dart';
import 'package:calorimeter/utils/scan_food_floating_button.dart'; import 'package:calorimeter/utils/scan_food_floating_button.dart';
import 'package:calorimeter/utils/sum_widget.dart'; import 'package:calorimeter/utils/sum_widget.dart';
@ -76,19 +74,10 @@ class PerDatePageViewController extends StatelessWidget {
), ),
drawer: const AppDrawer(), drawer: const AppDrawer(),
floatingActionButton: OverflowBar(children: [ floatingActionButton: OverflowBar(children: [
FloatingActionButton(
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => const GraphsPageWidget()));
},
child: Icon(Icons.bar_chart)),
const SizedBox(width: 8),
ScanFoodFAB( ScanFoodFAB(
onPressed: () { onPressed: () {
var result = BarcodeScanner.scan();
context.read<FoodEntryBloc>().add( context.read<FoodEntryBloc>().add(
BarcodeScanned( BarcodeAboutToBeScanned(
scanResultFuture: result,
forDate: context forDate: context
.read<PageViewStateProvider>() .read<PageViewStateProvider>()
.displayedDate, .displayedDate,

View File

@ -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.errorString != null) { if (pageState.appError != null) {
showNewSnackbarWith(context, pageState.errorString!); showNewSnackbarWith(context, pageState.appError!);
} }
}, },
builder: (context, pageState) { builder: (context, pageState) {
@ -41,9 +41,10 @@ class _PerDateWidgetState extends State<PerDateWidget>
); );
} }
void showNewSnackbarWith(BuildContext context, String text) { void showNewSnackbarWith(BuildContext context, GlobalAppError error) {
var snackbar = var snackbar = ErrorSnackbar(
ErrorSnackbar(colorScheme: Theme.of(context).colorScheme, text: text); colorScheme: Theme.of(context).colorScheme,
text: error.toErrorString(context));
ScaffoldMessenger.of(context) ScaffoldMessenger.of(context)
..removeCurrentSnackBar() ..removeCurrentSnackBar()

View File

@ -26,10 +26,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: archive name: archive
sha256: "08064924cbf0ab88280a0c3f60db9dd24fec693927e725ecb176f16c629d1cb8" sha256: "6199c74e3db4fbfbd04f66d739e72fe11c8a8957d5f219f1f4482dbde6420b5a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.1" version: "4.0.2"
args: args:
dependency: transitive dependency: transitive
description: description:
@ -134,14 +134,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.6" version: "3.0.6"
equatable:
dependency: transitive
description:
name: equatable
sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7"
url: "https://pub.dev"
source: hosted
version: "2.0.7"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
@ -174,14 +166,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.1" version: "1.1.1"
fl_chart:
dependency: "direct main"
description:
name: fl_chart
sha256: "74959b99b92b9eebeed1a4049426fd67c4abc3c5a0f4d12e2877097d6a11ae08"
url: "https://pub.dev"
source: hosted
version: "0.69.2"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@ -254,10 +238,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: http_multi_server name: http_multi_server
sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.2.1" version: "3.2.2"
http_parser: http_parser:
dependency: transitive dependency: transitive
description: description:
@ -270,10 +254,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: image name: image
sha256: "20842a5ad1555be624c314b0c0cc0566e8ece412f61e859a42efeb6d4101a26c" sha256: "8346ad4b5173924b5ddddab782fc7d8a6300178c8b1dc427775405a01701c4a6"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.5.0" version: "4.5.2"
intl: intl:
dependency: "direct main" dependency: "direct main"
description: description:
@ -334,10 +318,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: lints name: lints
sha256: "4a16b3f03741e1252fda5de3ce712666d010ba2122f8e912c94f9f7b90e1a4c3" sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.1.0" version: "5.1.1"
logging: logging:
dependency: transitive dependency: transitive
description: description:
@ -707,10 +691,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: watcher name: watcher
sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.0" version: "1.1.1"
web: web:
dependency: transitive dependency: transitive
description: description:
@ -763,10 +747,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: yaml name: yaml
sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.2" version: "3.1.3"
sdks: sdks:
dart: ">=3.6.0-0 <4.0.0" dart: ">=3.6.0 <4.0.0"
flutter: ">=3.24.0" flutter: ">=3.24.0"

View File

@ -20,7 +20,6 @@ dependencies:
provider: ^6.1.2 provider: ^6.1.2
test: ^1.25.7 test: ^1.25.7
go_router: ^14.3.0 go_router: ^14.3.0
fl_chart: ^0.69.2
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: