调节目标温度的专用页:左右步进按钮、数字键盘弹窗、放大滚轮 ruler、按肉类显示 2–5 档熟度速选,最后通过 Save 把 (target, meat) 返回上一页(cooking 页或 meat-presets 页)。
lib/features/thermometer/presentation/pages/preset_page.dart(563 行)temperatureUnitProvider(仅订阅显示单位)MagnifyingRuler(来自 widgets/magnifying_ruler.dart),_NumpadSheet(同文件内)lib/features/thermometer/presentation/data/meat_doneness_tables.dart(27761fc / TAPD #1003051 起替代页内旧 _donenessLevels)。donenessTableFor(meatName) 按肉名返回 List<DonenessBand>,每条 band 携 donenessKey / minF / maxF / isUsda / isRecommended;recommended 行从 bands.firstWhere(isRecommended) 反取。覆盖 12 + 2 (Veal / Hamburger CM4-only 待 Liang 与 CM4 屏对齐) 种肉表,Custom 槽与未识别名走通用 5-band 默认表回退。新增 localizedDonenessKey(AppLocalizations, String) 同文件——与 meat_localization.dart 的 firmware-mapped localizedDoneness(AppLocalizations, Doneness) 区分(后者基于 5 项 enum,无 Crispy)initialTargetTempF(默认 122°F = 50°C)、initialMeat(默认 'Beef')、family(BoosterFamily,默认 legacy,透传给 肉类预设页 的蛋白质 grid)、initialIsCustom(bool,默认 false,Custom 槽标记)、deviceId / probeNumber(均可空,仅烹饪上下文进入时非空,供肉类预设页的 early-warning chip 直接落盘用)。两条进入路径参数完全不同:lib/app.dart 静态路由表走 PresetPage() 全默认值构造;烹饪页 与肉类预设页都改用 navigateInstant 直接实例化并带上全部 6 个参数(仅把 RouteSettings.name 设为 PresetPage.routeName 用于追踪,并不走 Navigator.pushNamed)——实际使用中几乎总是走后一条路径。/preset(PresetPage.routeName)lib/app.dart 静态 routes map顶部返回按钮 + 居中标题("Set target temp")。中段大号温度数字(52pt)夹在左右两个步进 chevron 之间,点击数字弹出数字键盘 sheet。下方一条 120pt 高的放大滚轮 (MagnifyingRuler),左右滑动调温——dc19e3c 起按显示单位原生缩放:°F 模式走 32–212 °F、1 °F 粒度;°C 模式走 0–100 °C、1 °C 粒度(之前 °C 模式仍跑 °F 内部刻度 + per-tick 转换,°C 整数标签实际对应非整 °F、滑动手感不稳)。Major / mid tick 由原生显示值判定(°C 0/10/…/100 整十做 major、5/15/… 做 mid)。存储仍以 °F 为准——MagnifyingRuler.value 入参 / onChanged 出参都是 °F,单位转换只发生在边界。didUpdateWidget 在 useCelsius 翻转时也重新派生 offset(不只是 value 变化),所以温度单位 toggle 时 ruler 不会跟手势打架。下方列表显示 1 个 "Recommended" 行(仅当肉表存在 isRecommended band 时渲染)+ 当前肉种的 doneness 档(27761fc / TAPD #1003051:从 donenessTableFor(_selectedMeat) 按肉种取——Beef / Mutton / Lamb / Ostrich / Venison / Ham / Veal 5 档、Chicken 3 档含 USDA Crispy 174–179°F、Duck / Goose / Lobster / Hamburger 2 档、Fish / Pork / Turkey 3–4 档,Custom 槽 / 未识别名 走默认 5-band 回退表 Rare → Well Done),命中当前温度的行高亮,USDA 档带绿色 USDA 角标,recommended 行通过绿色文字高亮显示,不渲染渐变背景。底部两个胶囊按钮:黄色 "Presets & Goals (...)"(带肉种名,且肉种名会先经 localizedMeatName 本地化显示,不是固定英文 Beef,点击后跳到肉类预设页)+ 橙色 "Save"。
IconButton.onPressed → Navigator.pop(context)Navigator.pop(context),返回到上一层调用方;本页不传 result,因此若调用方在 .then((result)) 中接收结果,会拿到 null。IconButton.onPressed(左减 1°F、右加 1°F)_targetTempF ± 1,clamp(32, 212),触发 selectionClick 震动补充:数字键盘弹出后并不是空白输入框,而是会先用当前温度预填 _buffer。若当前为 °C 显示模式,则先把 initial 华氏值转换为摄氏并 round() 后再显示;若用户清空后直接确认,才会回退到 initial。
GestureDetector.onTap → _showNumpad_NumpadSheet(数字网格仅含 0-9 + Clear(C)+ Backspace(⌫));用户再点击下方独立的确认按钮关闭并应用GestureDetector.onTap → _showNumpad
→ showModalBottomSheet(_NumpadSheet(initial, useCelsius, onConfirm: (v) => setState(_targetTempF = v)))
→ 用户输入 → _NumpadSheet._buffer 累积
→ "Set Temperature" 按钮 → _parsedTempF() (含 °C → °F 转换 + clamp 32-212) → onConfirm(temp) → Navigator.pop
MagnifyingRuler.onChanged → setState(() => _targetTempF = v)GestureDetector.onTap → _targetTempF = (rec.minF + rec.maxF) / 2,其中 rec = donenessTableFor(_selectedMeat, family: widget.family) 中通过 indexWhere 安全获取的 isRecommended band(允许为 null)(27761fc:每种肉的推荐中点不同——Beef → 134.5°F、Chicken → 170°F、Fish → 127°F、Duck → 165°F、Pork → 158.5°F …,Custom 槽 / 未识别名走默认 5-band 回退表,midpoint 仍为 134.5°F)GestureDetector.onTap → _targetTempF = (d.minF + d.maxF) / 2(每档中点)补充:从当前代码看,进入肉类预设页后,Early Warning 芯片不属于本页 Save 才提交的内容;它在肉类预设页内点按即生效,会立刻写入全局 earlyWarnLevelProvider,并在存在 deviceId / probeNumber 上下文时同步写入该 probe 的 pendingCookParamsProvider 与活动中的 cookingSessionsProvider。
GestureDetector.onTap → navigateInstant<Map>(MeatPresetsPage())GestureDetector.onTap
→ navigateInstant<Map>(MeatPresetsPage(), settings: RouteSettings('/meat-presets'))
→ .then((result)):
result['meatType'] → setState(_selectedMeat = ...)
result['targetTempF']? → setState(_targetTempF = ...)
_selectedMeat、_isCustom、可选 _targetTempF 更新;按钮 label 变化(含肉种名)FilledButton.onPressed → Navigator.pop(context, {'targetTempF': _targetTempF, 'meatType': _selectedMeat, 'isCustom': _isCustom})FilledButton.onPressed
→ Navigator.pop(context, {'targetTempF': _targetTempF, 'meatType': _selectedMeat})
→ cooking_page _navigateToAdjust .then((result)) 命中 → setState 更新 + _syncTargetToSession + alarmServiceProvider.reset + _pushTargetToDevice
BleDeviceService.setProbeTarget 写入设备(C5 dispatch 崩塌后的统一入口,详见 06-烹饪)仅 watch temperatureUnitProvider(用于显示单位切换)。所有调温都是本地 setState 驱动 —— 该页是「编辑器」,所有变更暂存内存,按 Save 才生效。
无显著差异。所有震动反馈在 iOS/Android 都触发。
:140 / :161 与 _NumpadSheet._parsedTempF 的 :458。120°C 的 v4.2 内温显示上限不一致 🔴 需要确认(可能是有意——预设温度仅烹饪用途;显示范围更广)。DonenessBand.donenessKey(meat_doneness_tables.dart)是 'Rare' / 'Medium Rare' / 'Medium' / 'Medium Well' / 'Well Done' / 'Crispy' 等英文字符串,再通过 localizedDonenessKey(AppLocalizations, String) switch 翻译到 ARB key。可读性 OK 但不够 idiomatic(更典型的是直接用 enum)。27761fc 起改名为 localizedDonenessKey 以与 meat_localization.dart 的 firmware-mapped localizedDoneness(AppLocalizations, Doneness) 区分——后者绑 5 项 enum,无 Crispy。