Implement food entry lookup on entering a food name. #1
@ -1,3 +1,6 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:calodiary/storage/storage.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:calodiary/food_entry_bloc.dart';
|
||||
import 'package:calodiary/row_with_spacers_widget.dart';
|
||||
@ -16,22 +19,53 @@ class _EnterFoodWidgetState extends State<EnterFoodWidget> {
|
||||
TextEditingController nameController = TextEditingController();
|
||||
TextEditingController massController = TextEditingController();
|
||||
TextEditingController kcalPerMassController = TextEditingController();
|
||||
Map<String, double> suggestions = {};
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
suggestions = FoodStorage.getInstance().getFoodEntryLookupDatabase;
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var nameWidget = TextField(
|
||||
decoration: const InputDecoration(hintText: "Name"),
|
||||
controller: nameController,
|
||||
var nameWidget = Autocomplete<String>(
|
||||
optionsViewOpenDirection: OptionsViewOpenDirection.down,
|
||||
fieldViewBuilder: (context, controller, focusNode, onSubmitted) {
|
||||
nameController = controller;
|
||||
return TextFormField(
|
||||
controller: controller,
|
||||
focusNode: focusNode,
|
||||
decoration: const InputDecoration(label: Text("Name")));
|
||||
},
|
||||
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) {
|
||||
double kcalPerMassForSelectedFood = suggestions[selectedFood]!;
|
||||
setState(() {
|
||||
kcalPerMassController.text = kcalPerMassForSelectedFood.toString();
|
||||
});
|
||||
});
|
||||
|
||||
var massWidget = TextField(
|
||||
decoration: const InputDecoration(hintText: "Menge"),
|
||||
decoration: const InputDecoration(label: Text("Menge")),
|
||||
keyboardType: TextInputType.number,
|
||||
controller: massController,
|
||||
);
|
||||
|
||||
var kcalPerMassWidget = TextField(
|
||||
decoration: const InputDecoration(hintText: "kcal pro 100g"),
|
||||
decoration: const InputDecoration(label: Text("kcal pro")),
|
||||
keyboardType: TextInputType.number,
|
||||
controller: kcalPerMassController);
|
||||
|
||||
|
@ -4,7 +4,7 @@ import 'package:uuid/uuid.dart';
|
||||
|
||||
class FoodEntryBloc extends Bloc<FoodEvent, FoodEntryState> {
|
||||
final FoodEntryState initialState;
|
||||
final AppStorage storage;
|
||||
final FoodStorage storage;
|
||||
final DateTime forDate;
|
||||
|
||||
FoodEntryBloc(
|
||||
@ -12,16 +12,18 @@ class FoodEntryBloc extends Bloc<FoodEvent, FoodEntryState> {
|
||||
required this.forDate,
|
||||
required this.storage})
|
||||
: super(initialState) {
|
||||
on<FoodEntryEvent>(addFoodEntry);
|
||||
on<FoodEntryEvent>(handleFoodEntryEvent);
|
||||
on<FoodDeletionEvent>(deleteFood);
|
||||
on<PageChangedEvent>(updateEntries);
|
||||
}
|
||||
|
||||
void addFoodEntry(FoodEntryEvent event, Emitter<FoodEntryState> emit) async {
|
||||
void handleFoodEntryEvent(
|
||||
FoodEntryEvent event, Emitter<FoodEntryState> emit) async {
|
||||
FoodEntryState newState = FoodEntryState.from(state);
|
||||
newState.addEntry(event.entry);
|
||||
|
||||
await storage.writeEntriesForDate(forDate, newState.foodEntries);
|
||||
storage.addFoodEntryToLookupDatabase(event.entry);
|
||||
|
||||
emit(newState);
|
||||
}
|
||||
|
@ -4,14 +4,20 @@ import 'package:calodiary/row_with_spacers_widget.dart';
|
||||
|
||||
class FoodEntryWidget extends StatelessWidget {
|
||||
final FoodEntry entry;
|
||||
final Function(BuildContext context) onDelete;
|
||||
final Function(BuildContext context, String id) onDelete;
|
||||
|
||||
const FoodEntryWidget(
|
||||
{super.key, required this.entry, required this.onDelete});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
return Dismissible(
|
||||
key: ValueKey(entry.id),
|
||||
onDismissed: (direction) {
|
||||
onDelete(context, entry.id);
|
||||
},
|
||||
child: Card(
|
||||
elevation: 5.0,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 4.0),
|
||||
child: RowWidget(
|
||||
@ -22,11 +28,12 @@ class FoodEntryWidget extends StatelessWidget {
|
||||
IconButton(
|
||||
style: IconButton.styleFrom(padding: EdgeInsets.zero),
|
||||
onPressed: () {
|
||||
onDelete(context);
|
||||
onDelete(context, entry.id);
|
||||
},
|
||||
icon: const Icon(Icons.delete_forever_rounded)),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,8 @@ import 'package:go_router/go_router.dart';
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
var storage = await AppStorage.create();
|
||||
var storage = await FoodStorage.create();
|
||||
await storage.buildFoodLookupDatabase();
|
||||
var kcalLimit = await storage.readLimit();
|
||||
var brightness = await storage.readBrightness();
|
||||
|
||||
@ -24,7 +25,7 @@ void main() async {
|
||||
}
|
||||
|
||||
class MainApp extends StatelessWidget {
|
||||
final AppStorage storage;
|
||||
final FoodStorage storage;
|
||||
final double kcalLimit;
|
||||
final String brightness;
|
||||
|
||||
@ -36,7 +37,8 @@ class MainApp extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiBlocProvider(
|
||||
return SafeArea(
|
||||
child: MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider(
|
||||
create: (context) => SettingsDataBloc(
|
||||
@ -44,7 +46,8 @@ class MainApp extends StatelessWidget {
|
||||
storage: storage),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (context) => ThemeDataBloc(ThemeState(brightness: brightness),
|
||||
create: (context) => ThemeDataBloc(
|
||||
ThemeState(brightness: brightness),
|
||||
storage: storage),
|
||||
),
|
||||
],
|
||||
@ -69,6 +72,7 @@ class MainApp extends StatelessWidget {
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ class PerDateWidget extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _PerDateWidgetState extends State<PerDateWidget> {
|
||||
late AppStorage storage;
|
||||
late FoodStorage storage;
|
||||
late Future<List<FoodEntry>> entriesFuture;
|
||||
late List<FoodEntry> entries;
|
||||
|
||||
@ -27,7 +27,7 @@ class _PerDateWidgetState extends State<PerDateWidget> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
storage = AppStorage.getInstance();
|
||||
storage = FoodStorage.getInstance();
|
||||
entriesFuture = storage.getEntriesForDate(widget.date);
|
||||
entriesFuture.then((val) {
|
||||
entries = val;
|
||||
@ -93,15 +93,20 @@ class _PerDateWidgetState extends State<PerDateWidget> {
|
||||
);
|
||||
}
|
||||
|
||||
return FoodEntryWidget(
|
||||
return Column(
|
||||
children: [
|
||||
FoodEntryWidget(
|
||||
entry: state.foodEntries[index],
|
||||
onDelete: (callbackContext) {
|
||||
onDelete: (callbackContext, id) {
|
||||
callbackContext
|
||||
.read<FoodEntryBloc>()
|
||||
.add(FoodDeletionEvent(
|
||||
entryID: state.foodEntries[index].id,
|
||||
entryID: id,
|
||||
));
|
||||
},
|
||||
),
|
||||
const Divider(),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
|
@ -2,7 +2,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:calodiary/storage/storage.dart';
|
||||
|
||||
class SettingsDataBloc extends Bloc<SettingsEvent, SettingsState> {
|
||||
final AppStorage storage;
|
||||
final FoodStorage storage;
|
||||
|
||||
SettingsDataBloc(super.initialState, {required this.storage}) {
|
||||
on<DailyKcalLimitUpdated>(persistDailyLimit);
|
||||
|
@ -1,16 +1,19 @@
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:calodiary/food_entry_bloc.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:universal_platform/universal_platform.dart';
|
||||
|
||||
class AppStorage {
|
||||
static late AppStorage _instance;
|
||||
class FoodStorage {
|
||||
static late FoodStorage _instance;
|
||||
late String path;
|
||||
AppStorage._create();
|
||||
late Map<String, double> _foodLookupDatabase = {};
|
||||
|
||||
static Future<AppStorage> create() async {
|
||||
var storage = AppStorage._create();
|
||||
FoodStorage._create();
|
||||
|
||||
static Future<FoodStorage> create() async {
|
||||
var storage = FoodStorage._create();
|
||||
|
||||
Directory dir = Directory('');
|
||||
|
||||
@ -26,7 +29,7 @@ class AppStorage {
|
||||
return _instance;
|
||||
}
|
||||
|
||||
static AppStorage getInstance() => _instance;
|
||||
static FoodStorage getInstance() => _instance;
|
||||
|
||||
Future<List<FoodEntry>> getEntriesForDate(DateTime date) async {
|
||||
List<FoodEntry> entries = [];
|
||||
@ -132,4 +135,32 @@ class AppStorage {
|
||||
|
||||
await file.writeAsString(brightness);
|
||||
}
|
||||
|
||||
Future<void> buildFoodLookupDatabase() async {
|
||||
// get a list of dates of the last 365 days
|
||||
var dates = List<DateTime>.generate(365, (idx) {
|
||||
var pastDay = Duration(days: idx);
|
||||
return DateTime.now().subtract(pastDay);
|
||||
});
|
||||
|
||||
for (var date in dates) {
|
||||
addFoodEntryToLookupDatabaseFor(date);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> addFoodEntryToLookupDatabaseFor(DateTime date) async {
|
||||
var entriesForDate = await getEntriesForDate(date);
|
||||
|
||||
for (var entry in entriesForDate) {
|
||||
_foodLookupDatabase[entry.name] = entry.kcalPerMass;
|
||||
log("Added entry: ${entry.name}/${entry.kcalPerMass}");
|
||||
}
|
||||
}
|
||||
|
||||
void addFoodEntryToLookupDatabase(FoodEntry entry) {
|
||||
_foodLookupDatabase[entry.name] = entry.kcalPerMass;
|
||||
log("Added entry: ${entry.name}/${entry.kcalPerMass}");
|
||||
}
|
||||
|
||||
Map<String, double> get getFoodEntryLookupDatabase => _foodLookupDatabase;
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ import 'package:calodiary/storage/storage.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class ThemeDataBloc extends Bloc<ThemeToggleEvent, ThemeState> {
|
||||
final AppStorage storage;
|
||||
final FoodStorage storage;
|
||||
|
||||
ThemeDataBloc(super.initialState, {required this.storage}) {
|
||||
on<ThemeToggleEvent>(switchTheme);
|
||||
|
48
pubspec.lock
48
pubspec.lock
@ -53,10 +53,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: crypto
|
||||
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
|
||||
sha256: ec30d999af904f33454ba22ed9a86162b35e52b44ac4807d1d93c288041d7d27
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.3"
|
||||
version: "3.0.5"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -69,10 +69,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ffi
|
||||
sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
|
||||
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
version: "2.1.3"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -121,10 +121,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: go_router
|
||||
sha256: cdae1b9c8bd7efadcef6112e81c903662ef2ce105cbd220a04bbb7c3425b5554
|
||||
sha256: "2ddb88e9ad56ae15ee144ed10e33886777eb5ca2509a914850a5faa7b52ff459"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "14.2.0"
|
||||
version: "14.2.7"
|
||||
intl:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -193,10 +193,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "25dfcaf170a0190f47ca6355bdd4552cb8924b430512ff0cafb8db9bd41fe33b"
|
||||
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.14.0"
|
||||
version: "1.15.0"
|
||||
nested:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -217,18 +217,18 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: path_provider
|
||||
sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161
|
||||
sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
version: "2.1.4"
|
||||
path_provider_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
sha256: "9c96da072b421e98183f9ea7464898428e764bc0ce5567f27ec8693442e72514"
|
||||
sha256: "6f01f8e37ec30b07bc424b4deabac37cacb1bc7e2e515ad74486039918a37eb7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.5"
|
||||
version: "2.2.10"
|
||||
path_provider_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -257,10 +257,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_windows
|
||||
sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
|
||||
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
version: "2.3.0"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -350,10 +350,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "2419f20b0c8677b2d67c8ac4d1ac7372d862dc6c460cdbb052b40155408cd794"
|
||||
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.1"
|
||||
version: "0.7.2"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -374,10 +374,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: uuid
|
||||
sha256: "814e9e88f21a176ae1359149021870e87f7cddaf633ab678a5d2b0bff7fd1ba8"
|
||||
sha256: f33d6bb662f0e4f79dcd7ada2e6170f3b3a2530c28fc41f49a411ddedd576a77
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.4.0"
|
||||
version: "4.5.0"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -390,18 +390,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: "7475cb4dd713d57b6f7464c0e13f06da0d535d8b2067e188962a59bac2cf280b"
|
||||
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "14.2.2"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32
|
||||
sha256: a79dbe579cb51ecd6d30b17e0cae4e0ea15e2c0e66f69ad4198f22a6789e94f4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.5.1"
|
||||
version: "14.2.5"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
Loading…
Reference in New Issue
Block a user