Compare commits
5 Commits
add-first-
...
master
Author | SHA1 | Date | |
---|---|---|---|
cfc712458f | |||
b87c288527 | |||
63e9b471b4 | |||
7b440e82aa | |||
69bee8de7f |
2
.flutter
2
.flutter
@ -1 +1 @@
|
||||
Subproject commit dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
|
||||
Subproject commit 17025dd88227cd9532c33fa78f5250d548d87e9a
|
@ -15,6 +15,7 @@ android {
|
||||
namespace = "de.swgross.calorimeter"
|
||||
compileSdk = flutter.compileSdkVersion
|
||||
ndkVersion = flutter.ndkVersion
|
||||
|
||||
dependenciesInfo {
|
||||
includeInApk = false
|
||||
includeInBundle = false
|
||||
@ -33,8 +34,8 @@ android {
|
||||
applicationId = "de.swgross.calorimeter"
|
||||
minSdk = flutter.minSdkVersion
|
||||
targetSdk = flutter.targetSdkVersion
|
||||
versionCode = 3
|
||||
versionName = "1.0.3"
|
||||
versionCode = 4
|
||||
versionName = "1.0.4"
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
|
@ -2,8 +2,11 @@
|
||||
/* Copyright (C) 2024 Marco Groß <mgross@sw-gross.de> */
|
||||
import 'package:barcode_scan2/barcode_scan2.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:calorimeter/storage/storage.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
class FoodEntryBloc extends Bloc<FoodEvent, GlobalEntryState> {
|
||||
@ -16,7 +19,7 @@ class FoodEntryBloc extends Bloc<FoodEvent, GlobalEntryState> {
|
||||
on<FoodEntryEvent>(handleFoodEntryEvent);
|
||||
on<FoodChangedEvent>(handleFoodChangedEvent);
|
||||
on<FoodDeletionEvent>(handleDeleteFoodEvent);
|
||||
on<BarcodeScanned>(handleBarcodeScannedEvent);
|
||||
on<BarcodeAboutToBeScanned>(handleBarcodeScannedEvent);
|
||||
on<FoodEntryTapped>(handleFoodEntryTapped);
|
||||
}
|
||||
void handlePageBeingInitialized(
|
||||
@ -92,12 +95,24 @@ class FoodEntryBloc extends Bloc<FoodEvent, GlobalEntryState> {
|
||||
}
|
||||
|
||||
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];
|
||||
if (entriesForDate == null) return;
|
||||
|
||||
var client = FoodFactLookupClient();
|
||||
var scanResult = await event.scanResultFuture;
|
||||
|
||||
if (scanResult.type == ResultType.Cancelled) {
|
||||
return;
|
||||
@ -105,7 +120,7 @@ class FoodEntryBloc extends Bloc<FoodEvent, GlobalEntryState> {
|
||||
if (scanResult.type == ResultType.Error) {
|
||||
emit(GlobalEntryState(
|
||||
foodEntries: state.foodEntries,
|
||||
errorString: "Fehler beim Scannen des Barcodes"));
|
||||
appError: GlobalAppError(GlobalAppErrorType.errGeneralError)));
|
||||
return;
|
||||
}
|
||||
var responseFuture = client.retrieveFoodInfo(scanResult.rawContent);
|
||||
@ -117,6 +132,7 @@ class FoodEntryBloc extends Bloc<FoodEvent, GlobalEntryState> {
|
||||
waitingForNetwork: true,
|
||||
isSelected: false,
|
||||
);
|
||||
|
||||
entriesForDate.add(newEntryWaiting);
|
||||
var newFoodEntries = state.foodEntries;
|
||||
newFoodEntries.addAll({event.forDate: entriesForDate});
|
||||
@ -138,7 +154,7 @@ class FoodEntryBloc extends Bloc<FoodEvent, GlobalEntryState> {
|
||||
|
||||
emit(GlobalEntryState(
|
||||
foodEntries: newFoodEntries,
|
||||
errorString: "Barcode konnte nicht gefunden werden."));
|
||||
appError: GlobalAppError(GlobalAppErrorType.errbarcodeNotFound)));
|
||||
return;
|
||||
}
|
||||
if (response.status ==
|
||||
@ -149,7 +165,8 @@ class FoodEntryBloc extends Bloc<FoodEvent, GlobalEntryState> {
|
||||
|
||||
emit(GlobalEntryState(
|
||||
foodEntries: newFoodEntries,
|
||||
errorString: "OpenFoodFacts-Server konnte nicht erreicht werden."));
|
||||
appError:
|
||||
GlobalAppError(GlobalAppErrorType.errServerNotReachable)));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -224,10 +241,8 @@ class FoodDeletionEvent extends FoodEvent {
|
||||
FoodDeletionEvent({required this.entryID, required super.forDate});
|
||||
}
|
||||
|
||||
class BarcodeScanned extends FoodEvent {
|
||||
final Future<ScanResult> scanResultFuture;
|
||||
|
||||
BarcodeScanned({required this.scanResultFuture, required super.forDate});
|
||||
class BarcodeAboutToBeScanned extends FoodEvent {
|
||||
BarcodeAboutToBeScanned({required super.forDate});
|
||||
}
|
||||
|
||||
class FoodEntryTapped extends FoodEvent {
|
||||
@ -236,12 +251,16 @@ class FoodEntryTapped extends FoodEvent {
|
||||
FoodEntryTapped({required this.entry, required super.forDate});
|
||||
}
|
||||
|
||||
class PermissionException extends FoodEvent {
|
||||
PermissionException({required super.forDate});
|
||||
}
|
||||
|
||||
/// This is the state for one date/page
|
||||
class GlobalEntryState {
|
||||
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() {
|
||||
return GlobalEntryState(foodEntries: {});
|
||||
@ -305,3 +324,29 @@ class FoodEntryState {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -56,19 +56,41 @@ class FoodFactModel {
|
||||
});
|
||||
|
||||
factory FoodFactModel.fromJson(Map<String, dynamic> json) {
|
||||
String quantityString = json['product']['product_quantity'] ?? "0";
|
||||
int quantity;
|
||||
int kcalPer100gForModel = 0;
|
||||
int kcalPer100g = 0;
|
||||
int kcalPer100gPrepared = 0;
|
||||
|
||||
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) {
|
||||
quantity = 0;
|
||||
}
|
||||
|
||||
return FoodFactModel(
|
||||
name: json['product']['product_name'],
|
||||
kcalPer100g: json['product']['nutriments']['energy-kcal_100g'],
|
||||
mass: quantity);
|
||||
name: json['product']['product_name'] ?? "",
|
||||
kcalPer100g: kcalPer100gForModel,
|
||||
mass: quantity.ceil(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,5 +10,9 @@
|
||||
"yourSettings": "Deine persönlichen Einstellungen",
|
||||
"dayLimit": "Kalorienlimit pro Tag",
|
||||
"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."
|
||||
}
|
||||
|
@ -10,5 +10,9 @@
|
||||
"yourSettings": "Your personal settings",
|
||||
"dayLimit": "Calorie limit per day",
|
||||
"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."
|
||||
}
|
||||
|
@ -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;
|
||||
});
|
||||
}));
|
||||
}
|
||||
}
|
@ -2,13 +2,11 @@
|
||||
/* Copyright (C) 2024 Marco Groß <mgross@sw-gross.de> */
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:barcode_scan2/barcode_scan2.dart';
|
||||
import 'package:calorimeter/food_entry/food_entry_bloc.dart';
|
||||
import 'package:calorimeter/perdate/perdate_pageview.dart';
|
||||
import 'package:calorimeter/utils/app_drawer.dart';
|
||||
import 'package:calorimeter/utils/calendar_floating_button.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/scan_food_floating_button.dart';
|
||||
import 'package:calorimeter/utils/sum_widget.dart';
|
||||
@ -76,19 +74,10 @@ class PerDatePageViewController extends StatelessWidget {
|
||||
),
|
||||
drawer: const AppDrawer(),
|
||||
floatingActionButton: OverflowBar(children: [
|
||||
FloatingActionButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).push(MaterialPageRoute(
|
||||
builder: (context) => const GraphsPageWidget()));
|
||||
},
|
||||
child: Icon(Icons.bar_chart)),
|
||||
const SizedBox(width: 8),
|
||||
ScanFoodFAB(
|
||||
onPressed: () {
|
||||
var result = BarcodeScanner.scan();
|
||||
context.read<FoodEntryBloc>().add(
|
||||
BarcodeScanned(
|
||||
scanResultFuture: result,
|
||||
BarcodeAboutToBeScanned(
|
||||
forDate: context
|
||||
.read<PageViewStateProvider>()
|
||||
.displayedDate,
|
||||
|
@ -29,8 +29,8 @@ class _PerDateWidgetState extends State<PerDateWidget>
|
||||
|
||||
return BlocConsumer<FoodEntryBloc, GlobalEntryState>(
|
||||
listener: (context, pageState) {
|
||||
if (pageState.errorString != null) {
|
||||
showNewSnackbarWith(context, pageState.errorString!);
|
||||
if (pageState.appError != null) {
|
||||
showNewSnackbarWith(context, pageState.appError!);
|
||||
}
|
||||
},
|
||||
builder: (context, pageState) {
|
||||
@ -41,9 +41,10 @@ class _PerDateWidgetState extends State<PerDateWidget>
|
||||
);
|
||||
}
|
||||
|
||||
void showNewSnackbarWith(BuildContext context, String text) {
|
||||
var snackbar =
|
||||
ErrorSnackbar(colorScheme: Theme.of(context).colorScheme, text: text);
|
||||
void showNewSnackbarWith(BuildContext context, GlobalAppError error) {
|
||||
var snackbar = ErrorSnackbar(
|
||||
colorScheme: Theme.of(context).colorScheme,
|
||||
text: error.toErrorString(context));
|
||||
|
||||
ScaffoldMessenger.of(context)
|
||||
..removeCurrentSnackBar()
|
||||
|
42
pubspec.lock
42
pubspec.lock
@ -26,10 +26,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: archive
|
||||
sha256: "08064924cbf0ab88280a0c3f60db9dd24fec693927e725ecb176f16c629d1cb8"
|
||||
sha256: "6199c74e3db4fbfbd04f66d739e72fe11c8a8957d5f219f1f4482dbde6420b5a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.1"
|
||||
version: "4.0.2"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -134,14 +134,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.6"
|
||||
equatable:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: equatable
|
||||
sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.7"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -174,14 +166,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
@ -254,10 +238,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_multi_server
|
||||
sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b"
|
||||
sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.1"
|
||||
version: "3.2.2"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -270,10 +254,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image
|
||||
sha256: "20842a5ad1555be624c314b0c0cc0566e8ece412f61e859a42efeb6d4101a26c"
|
||||
sha256: "8346ad4b5173924b5ddddab782fc7d8a6300178c8b1dc427775405a01701c4a6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.5.0"
|
||||
version: "4.5.2"
|
||||
intl:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -334,10 +318,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: lints
|
||||
sha256: "4a16b3f03741e1252fda5de3ce712666d010ba2122f8e912c94f9f7b90e1a4c3"
|
||||
sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.1.0"
|
||||
version: "5.1.1"
|
||||
logging:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -707,10 +691,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: watcher
|
||||
sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
|
||||
sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
version: "1.1.1"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -763,10 +747,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: yaml
|
||||
sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
|
||||
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
version: "3.1.3"
|
||||
sdks:
|
||||
dart: ">=3.6.0-0 <4.0.0"
|
||||
dart: ">=3.6.0 <4.0.0"
|
||||
flutter: ">=3.24.0"
|
||||
|
@ -20,7 +20,6 @@ dependencies:
|
||||
provider: ^6.1.2
|
||||
test: ^1.25.7
|
||||
go_router: ^14.3.0
|
||||
fl_chart: ^0.69.2
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
Loading…
Reference in New Issue
Block a user