feat: show dish recognition result as bottom sheet on home screen
Remove "Определить блюдо" from ScanScreen and the /scan/dish route. The + button on each meal card now triggers dish recognition inline — picks image, shows loading dialog, then presents DishResultSheet as a modal bottom sheet. After adding to diary the sheet closes and the user stays on home. Also fix Navigator.pop crash: showDialog uses the root navigator by default, so capture Navigator.of(context, rootNavigator: true) before the async gap and use it to close the loading dialog. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -13,7 +13,6 @@ import '../../features/products/products_screen.dart';
|
|||||||
import '../../features/products/add_product_screen.dart';
|
import '../../features/products/add_product_screen.dart';
|
||||||
import '../../features/scan/scan_screen.dart';
|
import '../../features/scan/scan_screen.dart';
|
||||||
import '../../features/scan/recognition_confirm_screen.dart';
|
import '../../features/scan/recognition_confirm_screen.dart';
|
||||||
import '../../features/scan/dish_result_screen.dart';
|
|
||||||
import '../../features/scan/recognition_service.dart';
|
import '../../features/scan/recognition_service.dart';
|
||||||
import '../../features/menu/diary_screen.dart';
|
import '../../features/menu/diary_screen.dart';
|
||||||
import '../../features/menu/menu_screen.dart';
|
import '../../features/menu/menu_screen.dart';
|
||||||
@@ -150,16 +149,6 @@ final routerProvider = Provider<GoRouter>((ref) {
|
|||||||
return RecognitionConfirmScreen(items: items);
|
return RecognitionConfirmScreen(items: items);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
GoRoute(
|
|
||||||
path: '/scan/dish',
|
|
||||||
builder: (context, state) {
|
|
||||||
final extra = state.extra as Map<String, dynamic>?;
|
|
||||||
final dish = extra?['dish'] as DishResult?;
|
|
||||||
final mealType = extra?['meal_type'] as String?;
|
|
||||||
if (dish == null) return const _InvalidRoute();
|
|
||||||
return DishResultScreen(dish: dish, preselectedMealType: mealType);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ShellRoute(
|
ShellRoute(
|
||||||
builder: (context, state, child) => MainShell(child: child),
|
builder: (context, state, child) => MainShell(child: child),
|
||||||
routes: [
|
routes: [
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'package:cached_network_image/cached_network_image.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:image_picker/image_picker.dart';
|
||||||
|
|
||||||
import '../../core/theme/app_colors.dart';
|
import '../../core/theme/app_colors.dart';
|
||||||
import '../../shared/models/diary_entry.dart';
|
import '../../shared/models/diary_entry.dart';
|
||||||
@@ -11,6 +12,8 @@ import '../../shared/models/home_summary.dart';
|
|||||||
import '../../shared/models/meal_type.dart';
|
import '../../shared/models/meal_type.dart';
|
||||||
import '../menu/menu_provider.dart';
|
import '../menu/menu_provider.dart';
|
||||||
import '../profile/profile_provider.dart';
|
import '../profile/profile_provider.dart';
|
||||||
|
import '../scan/dish_result_screen.dart';
|
||||||
|
import '../scan/recognition_service.dart';
|
||||||
import 'home_provider.dart';
|
import 'home_provider.dart';
|
||||||
|
|
||||||
// ── Root screen ───────────────────────────────────────────────
|
// ── Root screen ───────────────────────────────────────────────
|
||||||
@@ -727,6 +730,93 @@ class _DailyMealsSection extends ConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _pickAndShowDishResult(
|
||||||
|
BuildContext context,
|
||||||
|
WidgetRef ref,
|
||||||
|
String mealTypeId,
|
||||||
|
) async {
|
||||||
|
// 1. Choose image source
|
||||||
|
final source = await showModalBottomSheet<ImageSource>(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => SafeArea(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.camera_alt),
|
||||||
|
title: const Text('Камера'),
|
||||||
|
onTap: () => Navigator.pop(context, ImageSource.camera),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.photo_library),
|
||||||
|
title: const Text('Галерея'),
|
||||||
|
onTap: () => Navigator.pop(context, ImageSource.gallery),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (source == null || !context.mounted) return;
|
||||||
|
|
||||||
|
// 2. Pick image
|
||||||
|
final image = await ImagePicker().pickImage(
|
||||||
|
source: source,
|
||||||
|
imageQuality: 70,
|
||||||
|
maxWidth: 1024,
|
||||||
|
maxHeight: 1024,
|
||||||
|
);
|
||||||
|
if (image == null || !context.mounted) return;
|
||||||
|
|
||||||
|
// 3. Show loading
|
||||||
|
// Capture root navigator now (before await) to avoid using the wrong one later.
|
||||||
|
// showDialog defaults to useRootNavigator: true; Navigator.pop(context) would resolve
|
||||||
|
// to GoRouter's inner navigator instead, which only has /home and would crash.
|
||||||
|
final rootNavigator = Navigator.of(context, rootNavigator: true);
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
|
builder: (_) => const AlertDialog(
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
CircularProgressIndicator(),
|
||||||
|
SizedBox(height: 16),
|
||||||
|
Text('Распознаём...'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// 4. Call API
|
||||||
|
try {
|
||||||
|
final dish = await ref.read(recognitionServiceProvider).recognizeDish(image);
|
||||||
|
if (!context.mounted) return;
|
||||||
|
rootNavigator.pop(); // close loading
|
||||||
|
|
||||||
|
// 5. Show result as bottom sheet
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
useSafeArea: true,
|
||||||
|
builder: (sheetContext) => DishResultSheet(
|
||||||
|
dish: dish,
|
||||||
|
preselectedMealType: mealTypeId,
|
||||||
|
onAdded: () => Navigator.pop(sheetContext),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (recognitionError) {
|
||||||
|
debugPrint('Dish recognition error: $recognitionError');
|
||||||
|
if (context.mounted) {
|
||||||
|
rootNavigator.pop(); // close loading
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(
|
||||||
|
content: Text('Не удалось распознать. Попробуйте ещё раз.'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class _MealCard extends ConsumerWidget {
|
class _MealCard extends ConsumerWidget {
|
||||||
final MealTypeOption mealTypeOption;
|
final MealTypeOption mealTypeOption;
|
||||||
final List<DiaryEntry> entries;
|
final List<DiaryEntry> entries;
|
||||||
@@ -769,8 +859,8 @@ class _MealCard extends ConsumerWidget {
|
|||||||
icon: const Icon(Icons.add, size: 20),
|
icon: const Icon(Icons.add, size: 20),
|
||||||
visualDensity: VisualDensity.compact,
|
visualDensity: VisualDensity.compact,
|
||||||
tooltip: 'Добавить блюдо',
|
tooltip: 'Добавить блюдо',
|
||||||
onPressed: () =>
|
onPressed: () => _pickAndShowDishResult(
|
||||||
context.push('/scan', extra: mealTypeOption.id),
|
context, ref, mealTypeOption.id),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,30 +1,31 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
|
||||||
|
|
||||||
import '../../features/menu/menu_provider.dart';
|
import '../../features/menu/menu_provider.dart';
|
||||||
import '../../features/home/home_provider.dart';
|
import '../../features/home/home_provider.dart';
|
||||||
import '../../shared/models/meal_type.dart';
|
import '../../shared/models/meal_type.dart';
|
||||||
import 'recognition_service.dart';
|
import 'recognition_service.dart';
|
||||||
|
|
||||||
/// Shows the recognition candidates and lets the user confirm a dish entry
|
/// Bottom sheet that shows dish recognition candidates and lets the user
|
||||||
/// before adding it to the diary.
|
/// confirm a dish entry before adding it to the diary.
|
||||||
class DishResultScreen extends ConsumerStatefulWidget {
|
class DishResultSheet extends ConsumerStatefulWidget {
|
||||||
const DishResultScreen({
|
const DishResultSheet({
|
||||||
super.key,
|
super.key,
|
||||||
required this.dish,
|
required this.dish,
|
||||||
|
required this.onAdded,
|
||||||
this.preselectedMealType,
|
this.preselectedMealType,
|
||||||
});
|
});
|
||||||
|
|
||||||
final DishResult dish;
|
final DishResult dish;
|
||||||
|
final VoidCallback onAdded;
|
||||||
final String? preselectedMealType;
|
final String? preselectedMealType;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ConsumerState<DishResultScreen> createState() => _DishResultScreenState();
|
ConsumerState<DishResultSheet> createState() => _DishResultSheetState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DishResultScreenState extends ConsumerState<DishResultScreen> {
|
class _DishResultSheetState extends ConsumerState<DishResultSheet> {
|
||||||
late int _selectedIndex;
|
late int _selectedIndex;
|
||||||
late int _portionGrams;
|
late int _portionGrams;
|
||||||
late String _mealType;
|
late String _mealType;
|
||||||
@@ -106,7 +107,7 @@ class _DishResultScreenState extends ConsumerState<DishResultScreen> {
|
|||||||
'portion_g': _portionGrams,
|
'portion_g': _portionGrams,
|
||||||
'source': 'recognition',
|
'source': 'recognition',
|
||||||
});
|
});
|
||||||
if (mounted) context.go('/home');
|
if (mounted) widget.onAdded();
|
||||||
} catch (addError) {
|
} catch (addError) {
|
||||||
debugPrint('Add to diary error: $addError');
|
debugPrint('Add to diary error: $addError');
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
@@ -123,82 +124,112 @@ class _DishResultScreenState extends ConsumerState<DishResultScreen> {
|
|||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final hasCandidates = widget.dish.candidates.isNotEmpty;
|
final hasCandidates = widget.dish.candidates.isNotEmpty;
|
||||||
|
|
||||||
return Scaffold(
|
return Column(
|
||||||
appBar: AppBar(title: const Text('Распознано блюдо')),
|
children: [
|
||||||
bottomNavigationBar: hasCandidates
|
// Drag handle
|
||||||
? SafeArea(
|
Center(
|
||||||
child: Padding(
|
child: Container(
|
||||||
padding: const EdgeInsets.fromLTRB(20, 8, 20, 16),
|
width: 40,
|
||||||
child: FilledButton(
|
height: 4,
|
||||||
onPressed: _saving ? null : _addToDiary,
|
margin: const EdgeInsets.symmetric(vertical: 12),
|
||||||
child: _saving
|
decoration: BoxDecoration(
|
||||||
? const SizedBox(
|
color: theme.colorScheme.onSurfaceVariant.withValues(alpha: 0.4),
|
||||||
height: 20,
|
borderRadius: BorderRadius.circular(2),
|
||||||
width: 20,
|
),
|
||||||
child: CircularProgressIndicator(strokeWidth: 2),
|
),
|
||||||
)
|
),
|
||||||
: const Text('Добавить в журнал'),
|
// Title row
|
||||||
),
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(20, 0, 8, 0),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Text('Распознано блюдо', style: theme.textTheme.titleMedium),
|
||||||
|
const Spacer(),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.close),
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
),
|
),
|
||||||
)
|
],
|
||||||
: null,
|
),
|
||||||
body: hasCandidates
|
),
|
||||||
? ListView(
|
// Scrollable content
|
||||||
padding: const EdgeInsets.all(20),
|
Expanded(
|
||||||
children: [
|
child: hasCandidates
|
||||||
_CandidatesSection(
|
? ListView(
|
||||||
candidates: widget.dish.candidates,
|
padding: const EdgeInsets.all(20),
|
||||||
selectedIndex: _selectedIndex,
|
children: [
|
||||||
onSelect: _selectCandidate,
|
_CandidatesSection(
|
||||||
),
|
candidates: widget.dish.candidates,
|
||||||
const SizedBox(height: 20),
|
selectedIndex: _selectedIndex,
|
||||||
_NutritionCard(
|
onSelect: _selectCandidate,
|
||||||
calories: _scale(_selected.calories),
|
),
|
||||||
proteinG: _scale(_selected.proteinG),
|
const SizedBox(height: 20),
|
||||||
fatG: _scale(_selected.fatG),
|
_NutritionCard(
|
||||||
carbsG: _scale(_selected.carbsG),
|
calories: _scale(_selected.calories),
|
||||||
),
|
proteinG: _scale(_selected.proteinG),
|
||||||
const SizedBox(height: 8),
|
fatG: _scale(_selected.fatG),
|
||||||
Text(
|
carbsG: _scale(_selected.carbsG),
|
||||||
'КБЖУ приблизительные — определены по фото.',
|
),
|
||||||
style: theme.textTheme.bodySmall?.copyWith(
|
const SizedBox(height: 8),
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
Text(
|
||||||
|
'КБЖУ приблизительные — определены по фото.',
|
||||||
|
style: theme.textTheme.bodySmall?.copyWith(
|
||||||
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
_PortionRow(
|
||||||
|
controller: _portionController,
|
||||||
|
onMinus: () => _adjustPortion(-10),
|
||||||
|
onPlus: () => _adjustPortion(10),
|
||||||
|
onChanged: _onPortionEdited,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
_MealTypeDropdown(
|
||||||
|
selected: _mealType,
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value != null) setState(() => _mealType = value);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Блюдо не распознано',
|
||||||
|
style: theme.textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
FilledButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
child: const Text('Попробовать снова'),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
),
|
||||||
_PortionRow(
|
// Bottom button
|
||||||
controller: _portionController,
|
if (hasCandidates)
|
||||||
onMinus: () => _adjustPortion(-10),
|
SafeArea(
|
||||||
onPlus: () => _adjustPortion(10),
|
child: Padding(
|
||||||
onChanged: _onPortionEdited,
|
padding: const EdgeInsets.fromLTRB(20, 8, 20, 16),
|
||||||
),
|
child: FilledButton(
|
||||||
const SizedBox(height: 20),
|
onPressed: _saving ? null : _addToDiary,
|
||||||
_MealTypeDropdown(
|
child: _saving
|
||||||
selected: _mealType,
|
? const SizedBox(
|
||||||
onChanged: (value) {
|
height: 20,
|
||||||
if (value != null) setState(() => _mealType = value);
|
width: 20,
|
||||||
},
|
child: CircularProgressIndicator(strokeWidth: 2),
|
||||||
),
|
)
|
||||||
const SizedBox(height: 16),
|
: const Text('Добавить в журнал'),
|
||||||
],
|
|
||||||
)
|
|
||||||
: Center(
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'Блюдо не распознано',
|
|
||||||
style: theme.textTheme.titleMedium,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
FilledButton(
|
|
||||||
onPressed: () => context.pop(),
|
|
||||||
child: const Text('Попробовать снова'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
|
|
||||||
import '../../core/api/api_client.dart';
|
import '../../core/api/api_client.dart';
|
||||||
|
import '../../core/auth/auth_provider.dart';
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Models
|
// Models
|
||||||
@@ -181,3 +183,7 @@ class RecognitionService {
|
|||||||
return {'image_base64': base64Data, 'mime_type': mimeType};
|
return {'image_base64': base64Data, 'mime_type': mimeType};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final recognitionServiceProvider = Provider<RecognitionService>((ref) {
|
||||||
|
return RecognitionService(ref.read(apiClientProvider));
|
||||||
|
});
|
||||||
|
|||||||
@@ -3,17 +3,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
|
|
||||||
import '../../core/auth/auth_provider.dart';
|
|
||||||
import 'recognition_service.dart';
|
import 'recognition_service.dart';
|
||||||
|
|
||||||
// Provider wired to the shared ApiClient.
|
|
||||||
final _recognitionServiceProvider = Provider<RecognitionService>((ref) {
|
|
||||||
return RecognitionService(ref.read(apiClientProvider));
|
|
||||||
});
|
|
||||||
|
|
||||||
/// Entry screen — lets the user choose how to add products.
|
/// Entry screen — lets the user choose how to add products.
|
||||||
/// If [GoRouterState.extra] is a non-null String, it is treated as a meal type ID
|
|
||||||
/// and the screen immediately opens the camera for dish recognition.
|
|
||||||
class ScanScreen extends ConsumerStatefulWidget {
|
class ScanScreen extends ConsumerStatefulWidget {
|
||||||
const ScanScreen({super.key});
|
const ScanScreen({super.key});
|
||||||
|
|
||||||
@@ -22,24 +14,6 @@ class ScanScreen extends ConsumerStatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _ScanScreenState extends ConsumerState<ScanScreen> {
|
class _ScanScreenState extends ConsumerState<ScanScreen> {
|
||||||
bool _autoStarted = false;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didChangeDependencies() {
|
|
||||||
super.didChangeDependencies();
|
|
||||||
if (_autoStarted) return;
|
|
||||||
final mealType = GoRouterState.of(context).extra as String?;
|
|
||||||
if (mealType != null && mealType.isNotEmpty) {
|
|
||||||
_autoStarted = true;
|
|
||||||
// Defer to avoid calling context navigation during build.
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
||||||
if (mounted) {
|
|
||||||
_pickAndRecognize(context, _Mode.dish, mealType: mealType);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@@ -68,13 +42,6 @@ class _ScanScreenState extends ConsumerState<ScanScreen> {
|
|||||||
onTap: () => _pickAndRecognize(context, _Mode.products),
|
onTap: () => _pickAndRecognize(context, _Mode.products),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
_ModeCard(
|
|
||||||
emoji: '🍽️',
|
|
||||||
title: 'Определить блюдо',
|
|
||||||
subtitle: 'КБЖУ≈ по фото готового блюда',
|
|
||||||
onTap: () => _pickAndRecognize(context, _Mode.dish),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
_ModeCard(
|
_ModeCard(
|
||||||
emoji: '✏️',
|
emoji: '✏️',
|
||||||
title: 'Добавить вручную',
|
title: 'Добавить вручную',
|
||||||
@@ -88,9 +55,8 @@ class _ScanScreenState extends ConsumerState<ScanScreen> {
|
|||||||
|
|
||||||
Future<void> _pickAndRecognize(
|
Future<void> _pickAndRecognize(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
_Mode mode, {
|
_Mode mode,
|
||||||
String? mealType,
|
) async {
|
||||||
}) async {
|
|
||||||
final picker = ImagePicker();
|
final picker = ImagePicker();
|
||||||
|
|
||||||
List<XFile> files = [];
|
List<XFile> files = [];
|
||||||
@@ -118,7 +84,7 @@ class _ScanScreenState extends ConsumerState<ScanScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
final service = ref.read(_recognitionServiceProvider);
|
final service = ref.read(recognitionServiceProvider);
|
||||||
|
|
||||||
// Show loading overlay while the AI processes.
|
// Show loading overlay while the AI processes.
|
||||||
showDialog(
|
showDialog(
|
||||||
@@ -141,12 +107,6 @@ class _ScanScreenState extends ConsumerState<ScanScreen> {
|
|||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
context.push('/scan/confirm', extra: items);
|
context.push('/scan/confirm', extra: items);
|
||||||
}
|
}
|
||||||
case _Mode.dish:
|
|
||||||
final dish = await service.recognizeDish(files.first);
|
|
||||||
if (context.mounted) {
|
|
||||||
Navigator.pop(context);
|
|
||||||
context.push('/scan/dish', extra: {'dish': dish, 'meal_type': mealType});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (recognitionError) {
|
} catch (recognitionError) {
|
||||||
debugPrint('Recognition error: $recognitionError');
|
debugPrint('Recognition error: $recognitionError');
|
||||||
@@ -189,7 +149,7 @@ class _ScanScreenState extends ConsumerState<ScanScreen> {
|
|||||||
// Mode enum
|
// Mode enum
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
enum _Mode { receipt, products, dish }
|
enum _Mode { receipt, products }
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Widgets
|
// Widgets
|
||||||
|
|||||||
Reference in New Issue
Block a user