nutrition_ai 0.0.4 nutrition_ai: ^0.0.4 copied to clipboard
Passio Nutrition AI SDK for Flutter. Supports Android and iOS.
import 'dart:async';
//import 'dart:developer';
import 'dart:math';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:nutrition_ai/nutrition_ai.dart';
import 'package:nutrition_ai_example/const/dimens.dart';
import 'package:nutrition_ai_example/domain/entity/app_secret/app_secret.dart';
import 'package:nutrition_ai_example/inject/injector.dart';
import 'package:nutrition_ai_example/presentation/food_search/food_search_page.dart';
import 'package:nutrition_ai_example/presentation/static_image/static_image_page.dart';
import 'package:nutrition_ai_example/router/routes.dart';
import 'package:permission_handler/permission_handler.dart';
Future<void> main() async {
await runZonedGuarded(() async {
WidgetsFlutterBinding.ensureInitialized();
await Injector.setup();
runApp(
ScreenUtilInit(
designSize: const Size(Dimens.designWidth, Dimens.designHeight),
minTextAdapt: true,
splitScreenMode: true,
builder: (context, child) {
return MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
localeResolutionCallback: (deviceLocale, supportedLocales) {
if (supportedLocales
.map((e) => e.languageCode)
.contains(deviceLocale?.languageCode)) {
return deviceLocale;
} else {
return const Locale('en', '');
}
},
// Start the app with the "/" named route. In this case, the app starts
// on the FirstScreen widget.
initialRoute: Routes.initialRoute,
routes: {
// When navigating to the [Routes.foodSearchPage] route, build the [FoodSearchPage] widget.
Routes.foodSearchPage: (context) => const FoodSearchPage(),
Routes.staticImagePage: (context) => const StaticImagePage(),
},
home: const MyApp(),
);
}),
);
}, (error, stackTrace) async {
if (kReleaseMode) {
/// Here we can track our error into the crashlytics.
} else {
// log('error: ${error.toString()}');
}
});
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String _platformVersion = 'Unknown';
PassioStatus? _passioStatus;
bool _sdkIsReady = false;
@override
void initState() {
super.initState();
initPlatformState();
}
// Platform messages are asynchronous, so we initialize in an async method.
Future<void> initPlatformState() async {
String platformVersion;
try {
platformVersion = await NutritionAI.instance.getSDKVersion() ??
'Unknown platform version';
} on PlatformException {
platformVersion = 'Failed to get platform version.';
}
configureSDK();
if (!mounted) return;
setState(() {
_platformVersion = platformVersion;
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
title: const Text('Passio SDK Plugin'),
),
body: Column(
children: [
const SizedBox(height: 20), // Adds space of 20 units
Center(
child: Text('SDK Version: $_platformVersion\n'),
),
Center(
child: _passioStatus == null
? const Text("Configuring SDK")
: Text(_passioStatus!.mode.name),
),
ElevatedButton(
onPressed: () {
testTags();
},
child: const Text('test tags'),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
iconURLFor();
},
child: const Text('test iconURLFor'),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
testTransform();
},
child: const Text('test transformCGRectForm'),
),
const SizedBox(height: 20),
_sdkIsReady
? // Adds space of 20 units
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const PassioCameraDetector()),
);
},
child: const Text('Camera Preview'),
)
: const CircularProgressIndicator(),
const SizedBox(height: 20), // Adds space of 20 units
_sdkIsReady
? ElevatedButton(
onPressed: () {
Navigator.pushNamed(context, Routes.foodSearchPage);
},
child: const Text('Text Search'),
)
: const SizedBox(),
const SizedBox(height: 20), // Adds space of 20 units
_sdkIsReady
? ElevatedButton(
onPressed: () {
Navigator.pushNamed(context, Routes.staticImagePage);
},
child: const Text('Static image'),
)
: const SizedBox(),
// Text(_passioStatus?.mode.toString() ?? 'Not setup yet')
],
),
),
);
}
void configureSDK() async {
PassioStatus? passioStatus;
debugPrint("configureSDK() async");
String passioKey = AppSecret.passioKey;
var configuration = PassioConfiguration(passioKey, debugMode: -333);
passioStatus = await NutritionAI.instance.configureSDK(configuration);
if (passioStatus.mode == PassioMode.isReadyForDetection) {
_sdkIsReady = true;
}
setState(() {
_passioStatus = passioStatus;
});
}
void testTags() async {
var pidAtt =
await NutritionAI.instance.fetchAttributesForBarcode("5411188118961");
debugPrint(
"1111 The right result for 5411188118961 is [vegan, vegetarian, gluten free] == ${pidAtt?.foodItem!.tags.toString()}");
var pidAtt2 =
await NutritionAI.instance.fetchAttributesForBarcode("753656713038");
debugPrint(
"2222 There are no tags 753656713038 = ${pidAtt2?.foodItem?.tags.toString()} ");
var tags = await NutritionAI.instance.fetchTagsFor("1603211380195");
debugPrint(
"33332 The right result for 1603211380195 is [vegan, vegetarian, gluten free]] == ${tags?.toString()}");
var tags2 = await NutritionAI.instance.fetchTagsFor("BAK0049");
debugPrint(
"4444 The right result for BAK0049 is empty ${tags2?.toString()}");
}
void iconURLFor() async {
var urlString = await NutritionAI.instance.iconURLFor("usda45296835");
debugPrint("link for usda45296835 $urlString");
}
void testTransform() async {
var boundingBox = const Rectangle(0.14928516745567322, 0.29599857330322266,
0.7111189961433411, 0.4279291033744812);
var toRect = const Rectangle(200.0, 200.0, 400.0, 400.0);
var result =
await NutritionAI.instance.transformCGRectForm(boundingBox, toRect);
debugPrint("transformCGRectForm result = $result");
}
}
class PassioCameraDetector extends StatefulWidget {
const PassioCameraDetector({super.key});
@override
State<PassioCameraDetector> createState() => _CameraDetectorState();
}
class _CameraDetectorState extends State<PassioCameraDetector>
implements FoodRecognitionListener {
PassioIDAttributes? _attributes;
PlatformImage? _image;
String? _displayedFood;
double? _volumeEstimate;
@override
void initState() {
super.initState();
_checkPermission();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
const PassioPreview(),
Align(
alignment: Alignment.bottomCenter,
child: PassioResult(
attributes: _attributes,
image: _image,
volumeEstimate: _volumeEstimate))
],
),
);
}
void _checkPermission() async {
if (await Permission.camera.request().isGranted) {
_startFoodDetection();
}
}
void _startFoodDetection() {
var detectionConfig = FoodDetectionConfiguration(
detectBarcodes: true, detectPackagedFood: true);
if (defaultTargetPlatform == TargetPlatform.iOS) {
detectionConfig.volumeDetectionMode = VolumeDetectionMode.auto;
}
NutritionAI.instance.startFoodDetection(detectionConfig, this);
debugPrint("Start Food Detection from main");
}
@override
void recognitionResults(FoodCandidates foodCandidates) {
var passioID = foodCandidates.detectedCandidates.firstOrNull?.passioID;
var barcode = foodCandidates.barcodeCandidates?.firstOrNull?.value;
var packagedFoodCode =
foodCandidates.packagedFoodCandidates?.firstOrNull?.packagedFoodCode;
var volumeEstimate = foodCandidates
.detectedCandidates.firstOrNull?.amountEstimate?.volumeEstimate;
updateResult(passioID, barcode, packagedFoodCode, volumeEstimate);
}
void updateResult(PassioID? passioID, Barcode? barcode,
PackagedFoodCode? packagedFoodCode, double? volumeEstimate) async {
// if (passioID == null && barcode == null && packagedFoodCode == null) {
// }
if (barcode != null) {
if (barcode != _displayedFood) {
_attributes =
await NutritionAI.instance.fetchAttributesForBarcode(barcode);
_displayedFood = barcode;
setState(() {});
setIconState();
}
} else if (packagedFoodCode != null) {
if (packagedFoodCode != _displayedFood) {
_attributes = await NutritionAI.instance
.fetchAttributesForPackagedFoodCode(packagedFoodCode);
_displayedFood = packagedFoodCode;
setIconState();
}
} else if (passioID != null) {
if (passioID != _displayedFood || volumeEstimate != _volumeEstimate) {
_attributes =
await NutritionAI.instance.lookupPassioAttributesFor(passioID);
_displayedFood = passioID;
_volumeEstimate = volumeEstimate;
setIconState();
}
} else {
_attributes = null;
_displayedFood = null;
setIconState();
}
}
void setIconState() async {
if (_attributes == null) {
_image = null;
setState(() {});
return;
}
var passioIcons = await NutritionAI.instance
.lookupIconsFor(_attributes!.passioID, type: _attributes!.entityType);
if (passioIcons.cachedIcon != null) {
_image = passioIcons.cachedIcon;
setState(() {});
return;
}
_image = passioIcons.defaultIcon;
setState(() {});
var remoteIcon =
await NutritionAI.instance.fetchIconFor(_attributes!.passioID);
if (remoteIcon != null) {
_image = remoteIcon;
setState(() {});
}
}
@override
void dispose() {
NutritionAI.instance.stopFoodDetection();
super.dispose();
}
}
class PassioResult extends StatelessWidget {
final PassioIDAttributes? attributes;
final PlatformImage? image;
final double? volumeEstimate;
const PassioResult(
{required this.attributes,
required this.image,
this.volumeEstimate,
super.key});
String _resultString() {
if (attributes == null) {
return 'Searching...';
}
if (volumeEstimate?.toInt() != null) {
String volume = volumeEstimate!.toInt().toString();
return attributes!.name + " vol= " + volume;
}
return attributes!.name;
}
@override
Widget build(BuildContext context) {
TargetPlatform platform = defaultTargetPlatform;
return Container(
width: double.infinity,
height: 100,
margin: const EdgeInsets.all(10),
padding: _getPadding(platform),
decoration: const BoxDecoration(color: Colors.lightBlueAccent),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
PassioIcon(image: image),
const SizedBox(width: 16),
Expanded(
child: Container(
alignment: Alignment.centerLeft,
height: 32,
child: FittedBox(
fit: BoxFit.contain,
child: Text(
_resultString(),
style:
const TextStyle(fontSize: 20, color: Colors.black),
softWrap: true,
),
)))
],
));
}
EdgeInsets _getPadding(TargetPlatform platform) {
switch (platform) {
case TargetPlatform.android:
return const EdgeInsets.symmetric(
vertical: 24, horizontal: 16); //Marin do your magic here
case TargetPlatform.iOS:
return const EdgeInsets.fromLTRB(10, 10, 10, 32);
default:
throw UnsupportedError('Unsupported platform');
}
}
}
//