Initial commit: CookIT recipe collection and week planner

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
CedricandClaude Opus 5.5 committed 2026-10-28 22:55:09 +01:00
commit 7d7a0bc60b
173 files changed
+22803

No files matched your search

@@ -0,0 +1,123 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../../localization/app_localizations.dart';
import '../../../recipes/models/recipe.dart';
import '../../../recipes/recipe_list_provider.dart';
import '../../../settings/settings_controller.dart';
import '../../day.dart';
import '../../models/plan_entry.dart';
import '../../plan_repository.dart';
import '../../week_fill.dart';
import '../../week_plan_provider.dart';
import 'plan_slot_sheet.dart';
/// "Neu würfeln": another Recipe of the same Course, by the rules of "Woche füllen".
Future<void> rerollEntry(BuildContext context, PlanEntry entry, Recipe current) async {
final l10n = AppLocalizations.of(context);
final messenger = ScaffoldMessenger.of(context);
final week = context.read<WeekPlanProvider>();
final recipes = context.read<RecipeListProvider>().allRecipes;
final plan = context.read<PlanRepository>();
final weekStart = weekStartOf(entry.day, context.read<SettingsController>().weekStart);
final replacement = pickReplacement(
candidates: [
for (final r in recipes)
if (current.courseId == null || r.courseId == current.courseId) r,
],
inWeek: week.recipeIdsBetween(weekStart, addDays(weekStart, 6)),
recent: week.recipeIdsBetween(addDays(weekStart, -7 * recentWeeks), addDays(weekStart, -1)),
random: Random(),
);
if (replacement == null) {
messenger.showSnackBar(SnackBar(content: Text(l10n.noOtherRecipe)));
return;
}
await plan.replaceRecipe(entry.id, replacement.id);
}
/// "Verschieben …": asks for a day and Meal.
Future<void> moveEntryWithPicker(BuildContext context, PlanEntry entry) async {
final l10n = AppLocalizations.of(context);
final plan = context.read<PlanRepository>();
final slot = await showPlanSlotSheet(
context,
action: l10n.moveEntryTitle,
initialDay: entry.day,
initialMealId: entry.mealId,
);
if (slot == null) return;
final (day, mealId) = slot;
await plan.moveEntry(entry.id, day, mealId);
}
/// Removes the entry, with "Rückgängig" in the SnackBar.
Future<void> removeEntryWithUndo(BuildContext context, PlanEntry entry, String name) async {
final l10n = AppLocalizations.of(context);
final messenger = ScaffoldMessenger.of(context);
final plan = context.read<PlanRepository>();
await plan.removeEntry(entry.id);
messenger
..hideCurrentSnackBar()
..showSnackBar(
SnackBar(
content: Text(l10n.entryRemoved(name)),
duration: const Duration(milliseconds: 1500),
action: SnackBarAction(
label: l10n.undo,
onPressed: () => plan.addEntry(entry.day, entry.mealId, recipeId: entry.recipeId, note: entry.note),
),
),
);
}
/// "Bearbeiten" on a note: a small dialog with its text.
Future<void> editNote(BuildContext context, PlanEntry entry) async {
final plan = context.read<PlanRepository>();
final text = await showDialog<String>(
context: context,
builder: (_) => _NoteDialog(note: entry.note ?? ''),
);
if (text != null && text.isNotEmpty) await plan.updateNote(entry.id, text);
}
class _NoteDialog extends StatefulWidget {
const _NoteDialog({required this.note});
final String note;
@override
State<_NoteDialog> createState() => _NoteDialogState();
}
class _NoteDialogState extends State<_NoteDialog> {
late final _text = TextEditingController(text: widget.note);
@override
void dispose() {
_text.dispose();
super.dispose();
}
void _save() => Navigator.pop(context, _text.text.trim());
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
return AlertDialog(
title: Text(l10n.editNoteTitle),
content: TextField(
controller: _text,
autofocus: true,
textCapitalization: TextCapitalization.sentences,
onSubmitted: (_) => _save(),
),
actions: [
TextButton(onPressed: () => Navigator.pop(context), child: Text(l10n.cancel)),
FilledButton(onPressed: _save, child: Text(l10n.save)),
],
);
}
}