202 lines
7.3 KiB
Dart
202 lines
7.3 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:provider/provider.dart';
|
|
|
|
import '../../design/cookit_colors.dart';
|
|
import '../../localization/app_localizations.dart';
|
|
import '../../recipes/amount.dart';
|
|
import '../../recipes/models/recipe.dart';
|
|
import '../../recipes/time_format.dart';
|
|
import '../../recipes/unit_catalog.dart';
|
|
import '../../recipes/views/recipe_detail_view.dart';
|
|
import '../recipe_share_service.dart';
|
|
import '../shared_recipe.dart';
|
|
|
|
/// A Recipe from another cook, shown before anything is saved: what it holds,
|
|
/// a hint when one of that name exists already, and "Speichern".
|
|
class SharedRecipeView extends StatefulWidget {
|
|
const SharedRecipeView({super.key, required this.shared});
|
|
|
|
static const routeName = '/received';
|
|
|
|
final SharedRecipe shared;
|
|
|
|
@override
|
|
State<SharedRecipeView> createState() => _SharedRecipeViewState();
|
|
}
|
|
|
|
class _SharedRecipeViewState extends State<SharedRecipeView> {
|
|
late final Future<bool> _nameTaken = context.read<RecipeShareService>().hasRecipeNamed(widget.shared.recipe.name);
|
|
var _saving = false;
|
|
|
|
Future<void> _save() async {
|
|
final l10n = AppLocalizations.of(context);
|
|
final messenger = ScaffoldMessenger.of(context);
|
|
final navigator = Navigator.of(context);
|
|
setState(() => _saving = true);
|
|
final recipe = await context.read<RecipeShareService>().save(widget.shared);
|
|
navigator.pushReplacementNamed(RecipeDetailView.routeName, arguments: recipe.id);
|
|
messenger
|
|
..hideCurrentSnackBar()
|
|
..showSnackBar(SnackBar(content: Text(l10n.recipeSaved(recipe.name))));
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final l10n = AppLocalizations.of(context);
|
|
final c = CookItColors.of(context);
|
|
final units = context.watch<UnitCatalog>();
|
|
final shared = widget.shared;
|
|
final recipe = shared.recipe;
|
|
final sentUnits = {for (final u in shared.units) u.key: u.label};
|
|
String unitLabel(String key) =>
|
|
units.byKey(key) != null || !sentUnits.containsKey(key) ? units.label(l10n, key) : sentUnits[key]!;
|
|
final facts = [
|
|
if (shared.courseName != null) shared.courseName!,
|
|
if (recipe.servings != null) '${formatNumber(recipe.servings!)} ${l10n.factServings}',
|
|
if (recipe.totalMinutes != null) formatMinutes(l10n, recipe.totalMinutes!),
|
|
].join(' · ');
|
|
final heading = TextStyle(fontFamily: 'Nunito', fontWeight: FontWeight.w800, fontSize: 17, color: c.green);
|
|
var number = 0;
|
|
|
|
return Scaffold(
|
|
appBar: AppBar(title: Text(l10n.receivedRecipe)),
|
|
bottomNavigationBar: SafeArea(
|
|
child: Padding(
|
|
padding: const EdgeInsets.fromLTRB(20, 8, 20, 16),
|
|
child: SizedBox(
|
|
height: 52,
|
|
child: FilledButton.icon(
|
|
onPressed: _saving ? null : _save,
|
|
icon: const Icon(Icons.download_done_rounded),
|
|
label: Text(l10n.save),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
body: ListView(
|
|
padding: const EdgeInsets.fromLTRB(20, 8, 20, 24),
|
|
children: [
|
|
if (shared.photos.isNotEmpty) ...[
|
|
ClipRRect(
|
|
borderRadius: BorderRadius.circular(22),
|
|
child: AspectRatio(
|
|
aspectRatio: 16 / 10,
|
|
child: Image.memory(shared.photos.first, fit: BoxFit.cover, cacheWidth: 1000),
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
],
|
|
Text(
|
|
recipe.name,
|
|
style: TextStyle(fontFamily: 'Nunito', fontWeight: FontWeight.w900, fontSize: 28, color: c.ink),
|
|
),
|
|
if (facts.isNotEmpty) ...[
|
|
const SizedBox(height: 4),
|
|
Text(
|
|
facts,
|
|
style: TextStyle(fontWeight: FontWeight.w700, color: c.inkMuted),
|
|
),
|
|
],
|
|
FutureBuilder<bool>(
|
|
future: _nameTaken,
|
|
builder: (context, snapshot) => snapshot.data != true
|
|
? const SizedBox.shrink()
|
|
: Container(
|
|
margin: const EdgeInsets.only(top: 14),
|
|
padding: const EdgeInsets.all(12),
|
|
decoration: BoxDecoration(color: c.honey, borderRadius: BorderRadius.circular(14)),
|
|
child: Text(
|
|
l10n.recipeNameTaken(recipe.name),
|
|
style: TextStyle(fontWeight: FontWeight.w700, color: c.onHoney),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
Text(l10n.ingredients, style: heading),
|
|
if (recipe.ingredientGroups.isEmpty) Text(l10n.noIngredients),
|
|
for (final group in recipe.ingredientGroups) ...[
|
|
if (group.name != null)
|
|
Padding(
|
|
padding: const EdgeInsets.only(top: 10, bottom: 2),
|
|
child: Text(
|
|
group.name!,
|
|
style: TextStyle(fontWeight: FontWeight.w800, color: c.ink),
|
|
),
|
|
),
|
|
for (final line in group.lines) _IngredientLine(line: line, unitLabel: unitLabel),
|
|
],
|
|
const SizedBox(height: 20),
|
|
Text(l10n.steps, style: heading),
|
|
if (recipe.stepSections.isEmpty) Text(l10n.noSteps),
|
|
for (final section in recipe.stepSections) ...[
|
|
if (section.title != null)
|
|
Padding(
|
|
padding: const EdgeInsets.only(top: 10, bottom: 2),
|
|
child: Text(
|
|
section.title!,
|
|
style: TextStyle(fontWeight: FontWeight.w800, color: c.ink),
|
|
),
|
|
),
|
|
for (final step in section.steps)
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(vertical: 4),
|
|
child: Text('${++number}. $step', style: TextStyle(fontSize: 15, height: 1.4, color: c.ink)),
|
|
),
|
|
],
|
|
if (recipe.notes != null) ...[
|
|
const SizedBox(height: 20),
|
|
Text(l10n.notes, style: heading),
|
|
Text(recipe.notes!, style: TextStyle(fontSize: 15, height: 1.4, color: c.ink)),
|
|
],
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _IngredientLine extends StatelessWidget {
|
|
const _IngredientLine({required this.line, required this.unitLabel});
|
|
|
|
final IngredientLine line;
|
|
final String Function(String key) unitLabel;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final c = CookItColors.of(context);
|
|
final amount = [
|
|
formatAmount(line.amount, line.amountMax),
|
|
if (line.unitKey != null) unitLabel(line.unitKey!),
|
|
].where((s) => s.isNotEmpty).join(' ');
|
|
return Padding(
|
|
padding: const EdgeInsets.symmetric(vertical: 3),
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
SizedBox(
|
|
width: 96,
|
|
child: Text(
|
|
amount,
|
|
style: TextStyle(fontWeight: FontWeight.w800, color: c.ink),
|
|
),
|
|
),
|
|
Expanded(
|
|
child: Text.rich(
|
|
TextSpan(
|
|
text: line.name,
|
|
children: [
|
|
if (line.remark != null)
|
|
TextSpan(
|
|
text: ' ${line.remark}',
|
|
style: TextStyle(color: c.inkMuted),
|
|
),
|
|
],
|
|
),
|
|
style: TextStyle(color: c.ink),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|