nutrition_ai 0.0.1 nutrition_ai: ^0.0.1 copied to clipboard
Passio Nutrition AI SDK for Flutter. Supports Android and iOS.
import 'dart:developer';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:nutrition_ai/nutrition_ai.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/domain/entity/app_secret/app_secret.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.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(
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(),
},
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.version() ??
'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: () {
// configureSDK();
// },
// child: const Text('Configure SDK'),
// )
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(),
// Text(_passioStatus?.mode.toString() ?? 'Not setup yet')
],
),
),
);
}
void configureSDK() async {
PassioStatus? passioStatus;
debugPrint("configureSDK() async");
String passioKey = AppSecret.passioKey;
var configuration = PassioConfiguration(passioKey);
// Before Shipping remove the debugMode line
configuration.debugMode = -333;
try {
passioStatus = await NutritionAI.instance.configureSDK(configuration);
if (passioStatus.mode == PassioMode.isReadyForDetection) {
_sdkIsReady = true;
print(passioStatus.activeModels);
}
} on PlatformException {
passioStatus = PassioStatus();
passioStatus.debugMessage = 'Failed to get platform version.';
}
setState(() {
_passioStatus = passioStatus;
});
}
}
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;
@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))
],
),
);
}
void _checkPermission() async {
if (await Permission.camera.request().isGranted) {
_startFoodDetection();
}
}
void _startFoodDetection() {
var detectionConfig = FoodDetectionConfiguration();
detectionConfig.detectBarcodes = true;
detectionConfig.detectPackagedFood = true;
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;
updateResult(passioID, barcode, packagedFoodCode);
}
void updateResult(PassioID? passioID, Barcode? barcode,
PackagedFoodCode? packagedFoodCode) 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) {
_attributes =
await NutritionAI.instance.lookupPassioAttributesFor(passioID);
_displayedFood = passioID;
setIconState();
}
} else {
_attributes = null;
_displayedFood = null;
setIconState();
}
}
void setIconState() async {
if (_attributes == null) {
_image = null;
setState(() {});
return;
}
var (defaultIcon, cachedIcon) = await NutritionAI.instance
.lookupIconsFor(_attributes!.passioID, type: _attributes!.entityType);
if (cachedIcon != null) {
_image = cachedIcon;
setState(() {});
return;
}
_image = 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;
const PassioResult(
{required this.attributes, required this.image, super.key});
String _resultString() {
if (attributes == null) {
return 'Searching...';
}
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');
}
}
}
//