Skip to content

Replace the copy buttons with an Export dialog: several copy formats plus a shareable PDF #44

Description

@guplem

TL;DR: Today the menu page and the shopping page each have one "Copy to clipboard" button with a single fixed text format and no feedback. This issue replaces both with an Export button that opens a dialog offering several copy formats and a PDF export, so a full menu and a full shopping list can be shared with another person and followed without the app.

Context

The goal is sharing. The user wants to hand a PDF to another person so both can follow the menu across the weeks and use the shopping list as a quick reference in the shop. Clipboard text cannot do that: it loses structure, it has no links, and it cannot carry the recipes.

What exists today. Two bare FloatingActionButtons, each hardwired to one format, neither giving any confirmation after copying:

  • Menu: menu_page.dart:109-116 calls MultiWeekMenu.toStringBeautified (multi_week_menu.dart:193-200 and menu.dart:288-323). It prints Week 1, then per day Lunch: Pasta Bolognese (3 pp). It has no ingredients, no cooking times, no instructions, and no "cook vs leftovers" wording (a leftover slot just prints (0 pp)). It also prints the raw Cooking.yield instead of the real servings-to-cook that the grid shows via MultiWeekMenu.servingsForCookEvent (multi_week_menu.dart:117-143), and it shows the people count only for slots with two or more sub-meals.
  • Shopping: shopping_page.dart:112 calls _buildMultiTripCopyText (shopping_page.dart:202-264). It gives per-trip sections with pack lines like 1 kg/pack: 2 packs, plus (freeze on arrival). It drops product links, waste and "best option" ranking, and the recipe breakdown. It also skips any ingredient whose amounts all round below 1 unit, and the single-trip path orders ingredients differently from the multi-trip path (insertion order vs alphabetical).

Where the export button should look for its pattern: export_recipe_to_markdown.dart is already an export dialog (static show() plus AlertDialog plus servings field plus Copy button plus snackbar). Multiple mutually exclusive choices as dialog actions already exist in _showLoadDialog (main.dart:156-172).

Constraints and gaps found while writing this issue (no human confirmed the choices made here):

  • No PDF capability exists. pubspec.yaml has no pdf, printing, share_plus, url_launcher, or path_provider. persistency.dart only ever writes strings (File.writeAsString), never bytes, and always through FilePicker.platform.saveFile. A PDF export needs a new dependency and a byte-writing path. See ADR 0003; save is desktop-only (unavailable on iOS and Android).
  • Product links cannot be clicked from the app today; they are opened with a Windows-only Process.run("start", ...) (shopping_product_row.dart:148-152). Inside a PDF the link must be a real PDF hyperlink, which works in any reader.
  • "Which meals need this ingredient" does not exist as data. IngredientSource stores only recipeName, perServingQuantities, and servings (ingredient_source.dart:10), built by Menu.ingredientSources (menu.dart:205-236), which dedups by recipe and loses the week, day, and meal slot. buildCookingTimeline (cooking_timeline.dart:45-104) has the day but not the recipe or the meal type. So the requested justification needs that link to be produced, not just formatted.
  • Recipe scaling is duplicated, not shared. amount * servings is inlined in play_recipe_page.dart:225-228 and export_recipe_to_markdown.dart:66, with different number formatting in each. The recipe base is always 1 serving and scaling is presentation-only (ADR 0006).
  • Dates: the menu has no calendar date today, so the PDF can only label days by weekday name until Set a real start date for the menu and count each week as 7 days from it #43 lands.

Other relevant ADRs: 0014 and 0015 (the per-trip copy output and the freeze-on-arrival note this replaces), 0010 (packsNeeded, packLabel, link), 0009 (export builders receive List<Recipe> as a parameter, never read providers), 0013 (SubMeal.people).

Acceptance Criteria

Export dialog (both pages)

  • The menu page and the shopping page each have one Export button that opens a dialog; the old direct-copy FABs are gone.
  • The dialog lists the available formats, copies to clipboard or writes a file depending on the choice, and confirms with a snackbar naming what happened (format copied, or file saved and where).
  • Copy formats on the menu page include a simplified one (the compact per-day text of today, with the leftover-vs-cook wording and the people count fixed) and a detailed one that adds, per cook event, the real servings from servingsForCookEvent and the recipe total time.
  • Copy formats on the shopping page include a simplified one (ingredient plus amount only) and a detailed one that adds the pack lines, the product name, and the product link URL.
  • The single-trip and multi-trip shopping copy paths use one ordering, so the same menu never produces two different ingredient orders.

Menu PDF

  • The PDF opens with a table, one row per day and one column per meal slot (or the reverse), showing for each slot what to eat, and marking clearly what must be cooked that day versus what is leftovers.
  • Each cook entry states the servings to cook for that event, taken from servingsForCookEvent, not the raw yield.
  • One table per week, each week labelled, so a multi-week menu produces one section per week.
  • After the tables, the PDF contains a recipe section with every recipe used in the menu, each printed once, with its ingredient list scaled to the total servings that menu needs and its numbered instructions with working and cooking times.
  • Ingredient amounts and units in the recipe section are formatted by shared code, so the PDF, cook mode, and the markdown export agree on the same number for the same input.

Shopping list PDF

  • The PDF keeps the per-trip structure of the shopping copy (one section per trip, or one list when a single trip is planned) and the freeze-on-arrival note.
  • Each ingredient lists every matching product with its pack label and the number of packs to buy, so a product that is out of stock in the shop can be swapped for another on the same line.
  • Each product store link is a clickable PDF hyperlink.
  • Each ingredient shows a justification: the meals that require it, naming week, day, and meal slot, plus the recipe and the amount each contributes.
  • Ingredients whose required amount rounds below one unit are still listed rather than silently dropped.

Files

  • Both PDFs are saved through a save-file picker with a sensible default filename, and the feature degrades with a clear message on platforms where saving is not available.

Related Issues

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestmenuMenu generation and configurationrecipesRecipe managementshoppingShopping list featurewaiting-for-human-checkNo human has verified this yet -- direct AI output

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions