import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../../core/api/api_error_mapper.dart'; import '../../../core/api/api_exception.dart'; import '../../../core/ui/async_state_views.dart'; import '../../auth/data/auth_providers.dart'; import '../data/recipe_providers.dart'; import '../domain/recipe.dart'; class RecipeEditScreen extends ConsumerStatefulWidget { final int recipeId; const RecipeEditScreen({super.key, required this.recipeId}); @override ConsumerState createState() => _RecipeEditScreenState(); } class _RecipeEditScreenState extends ConsumerState { final _formKey = GlobalKey(); bool _initialized = false; late TextEditingController _nameCtrl; late TextEditingController _descCtrl; late TextEditingController _servingsCtrl; late TextEditingController _instructionsCtrl; bool _isSaving = false; String? _saveError; @override void dispose() { if (_initialized) { _nameCtrl.dispose(); _descCtrl.dispose(); _servingsCtrl.dispose(); _instructionsCtrl.dispose(); } super.dispose(); } void _initControllers(Recipe recipe) { _nameCtrl = TextEditingController(text: recipe.title); _descCtrl = TextEditingController(text: recipe.description ?? ''); _servingsCtrl = TextEditingController(text: recipe.servings?.toString() ?? ''); _instructionsCtrl = TextEditingController(text: recipe.instructions ?? ''); _initialized = true; } Future _save() async { if (!(_formKey.currentState?.validate() ?? false)) return; setState(() { _isSaving = true; _saveError = null; }); try { final token = await ref.read(authStateProvider.future); final servings = int.tryParse(_servingsCtrl.text.trim()); await ref.read(recipeRepositoryProvider).updateRecipe( widget.recipeId, { 'name': _nameCtrl.text.trim(), 'description': _descCtrl.text.trim().isEmpty ? null : _descCtrl.text.trim(), if (servings != null) 'servings': servings, 'instructions': _instructionsCtrl.text.trim().isEmpty ? null : _instructionsCtrl.text.trim(), }, token: token, ); ref.invalidate(recipeDetailProvider(widget.recipeId)); ref.invalidate(recipesProvider); if (mounted) context.pop(); } on ApiException catch (e) { if (e.type == ApiErrorType.unauthorized) { await ref.read(authStateProvider.notifier).logout(); return; } setState(() { _saveError = mapErrorToUserMessage(e); _isSaving = false; }); } } @override Widget build(BuildContext context) { final recipeAsync = ref.watch(recipeDetailProvider(widget.recipeId)); return Scaffold( appBar: AppBar( title: const Text('Redigera recept'), actions: [ if (_initialized) TextButton( onPressed: _isSaving ? null : _save, child: _isSaving ? const SizedBox( height: 16, width: 16, child: CircularProgressIndicator(strokeWidth: 2), ) : const Text('Spara'), ), ], ), body: recipeAsync.when( loading: () => const LoadingStateView(label: 'Laddar recept...'), error: (error, _) => ErrorStateView( message: mapErrorToUserMessage(error), onRetry: () => ref.invalidate(recipeDetailProvider(widget.recipeId)), ), data: (recipe) { if (!_initialized) _initControllers(recipe); return _buildForm(context); }, ), ); } Widget _buildForm(BuildContext context) { return SingleChildScrollView( padding: const EdgeInsets.all(16), child: Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ TextFormField( controller: _nameCtrl, decoration: const InputDecoration(labelText: 'Receptnamn'), validator: (v) => (v == null || v.trim().isEmpty) ? 'Ange ett receptnamn.' : null, ), const SizedBox(height: 16), TextFormField( controller: _descCtrl, decoration: const InputDecoration(labelText: 'Beskrivning (valfritt)'), maxLines: 3, ), const SizedBox(height: 16), TextFormField( controller: _servingsCtrl, decoration: const InputDecoration(labelText: 'Antal portioner (valfritt)'), keyboardType: TextInputType.number, validator: (v) { if (v == null || v.trim().isEmpty) return null; if (int.tryParse(v.trim()) == null) { return 'Ange ett heltal.'; } return null; }, ), const SizedBox(height: 16), TextFormField( controller: _instructionsCtrl, decoration: const InputDecoration(labelText: 'Tillvägagångssätt (valfritt)'), maxLines: 10, textAlignVertical: TextAlignVertical.top, ), const SizedBox(height: 8), Text( 'Ingredienser redigeras via Skapa recept-flödet.', style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Theme.of(context).colorScheme.onSurfaceVariant), ), if (_saveError != null) ...[ const SizedBox(height: 16), Text( _saveError!, style: TextStyle(color: Theme.of(context).colorScheme.error), textAlign: TextAlign.center, ), ], const SizedBox(height: 24), FilledButton( onPressed: _isSaving ? null : _save, child: _isSaving ? const SizedBox( height: 18, width: 18, child: CircularProgressIndicator(strokeWidth: 2), ) : const Text('Spara ändringar'), ), const SizedBox(height: 40), ], ), ), ); } }