Initial commit: CookIT recipe collection and week planner
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
7d7a0bc60b
173 files changed
+22803
No files matched your search
@@ -0,0 +1,154 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../../../design/cookit_colors.dart';
|
||||
import '../../../localization/app_localizations.dart';
|
||||
import '../../../recipes/recipe_list_provider.dart';
|
||||
import '../../../recipes/views/widgets/recipe_photo.dart';
|
||||
import '../../day.dart';
|
||||
import '../../day_format.dart';
|
||||
import '../../models/meal.dart';
|
||||
import '../../plan_repository.dart';
|
||||
|
||||
/// "+" on a Meal: search the collection for a Recipe, or keep what was typed as a note.
|
||||
Future<void> showAddEntrySheet(BuildContext context, DateTime day, Meal meal) => showModalBottomSheet<void>(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
showDragHandle: true,
|
||||
builder: (_) => _AddEntrySheet(day: day, meal: meal),
|
||||
);
|
||||
|
||||
class _AddEntrySheet extends StatefulWidget {
|
||||
const _AddEntrySheet({required this.day, required this.meal});
|
||||
|
||||
final DateTime day;
|
||||
final Meal meal;
|
||||
|
||||
@override
|
||||
State<_AddEntrySheet> createState() => _AddEntrySheetState();
|
||||
}
|
||||
|
||||
class _AddEntrySheetState extends State<_AddEntrySheet> {
|
||||
final _query = TextEditingController();
|
||||
|
||||
/// Only Recipes of this Course; null shows all.
|
||||
String? _courseId;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_query.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _add({String? recipeId, String? note}) async {
|
||||
final plan = context.read<PlanRepository>();
|
||||
Navigator.pop(context);
|
||||
await plan.addEntry(widget.day, widget.meal.id, recipeId: recipeId, note: note);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context);
|
||||
final c = CookItColors.of(context);
|
||||
final provider = context.watch<RecipeListProvider>();
|
||||
final text = _query.text.trim();
|
||||
final recipes = provider.allRecipes
|
||||
.where((r) => _courseId == null || r.courseId == _courseId)
|
||||
.where((r) => r.name.toLowerCase().contains(text.toLowerCase()))
|
||||
.toList();
|
||||
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(bottom: MediaQuery.viewInsetsOf(context).bottom),
|
||||
child: SizedBox(
|
||||
height: MediaQuery.sizeOf(context).height * 0.75,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 0, 20, 12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(
|
||||
l10n.addEntryTitle(widget.meal.name, dayLabel(l10n, widget.day, dayOf(DateTime.now()))),
|
||||
style: TextStyle(fontFamily: 'Nunito', fontWeight: FontWeight.w900, fontSize: 22, color: c.ink),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextField(
|
||||
controller: _query,
|
||||
autofocus: true,
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
textInputAction: TextInputAction.done,
|
||||
onChanged: (_) => setState(() {}),
|
||||
onSubmitted: (value) {
|
||||
if (value.trim().isNotEmpty) _add(note: value.trim());
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
hintText: l10n.addEntryHint,
|
||||
prefixIcon: Icon(Icons.search_rounded, color: c.green),
|
||||
fillColor: c.background,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 40,
|
||||
child: ListView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
children: [
|
||||
for (final course in [null, ...provider.courses])
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: ChoiceChip(
|
||||
label: Text(course == null ? l10n.filterAll : course.label),
|
||||
selected: _courseId == course?.id,
|
||||
showCheckmark: false,
|
||||
onSelected: (_) => setState(() => _courseId = course?.id),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Expanded(
|
||||
child: ListView(
|
||||
children: [
|
||||
if (text.isNotEmpty)
|
||||
ListTile(
|
||||
leading: CircleAvatar(
|
||||
backgroundColor: c.tiles[0],
|
||||
child: Icon(Icons.sticky_note_2_outlined, color: c.green),
|
||||
),
|
||||
title: Text(l10n.addAsNote(text), style: const TextStyle(fontWeight: FontWeight.w800)),
|
||||
onTap: () => _add(note: text),
|
||||
),
|
||||
for (final recipe in recipes)
|
||||
ListTile(
|
||||
leading: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: SizedBox(
|
||||
width: 44,
|
||||
height: 44,
|
||||
child: RecipePhoto(
|
||||
photo: recipe.mainPhoto,
|
||||
seed: recipe.id,
|
||||
placeholderEmoji: provider.courseOf(recipe)?.emoji,
|
||||
cacheWidth: 132,
|
||||
),
|
||||
),
|
||||
),
|
||||
title: Text(recipe.name),
|
||||
subtitle: provider.courseOf(recipe) == null ? null : Text(provider.courseOf(recipe)!.name),
|
||||
onTap: () => _add(recipeId: recipe.id),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user