CM4 机型配对成功后立即弹出的二选一页面:选 WiFi + 云端 → 进 sign-in 漏斗(已恢复 session 时跳过)→ /wifi-select 网络选择页(TAPD #1003130,Android 自动扫附近 2.4 GHz AP;iOS 不能枚举附近网络,因此提供“当前连接的网络”行以及手动输入入口)→ /wifi-setup 密码+配对页;选 BLE-only → 清栈到 Dashboard + 顶部叠 设备指南(TAPD #1003087 / 88e3ad7,与 scan-page 非 CM4 / WiFi 成功 / WiFi→BT fallback 三条收尾路径统一)。所有非 CM4 设备都会跳过此页;仅 CM4 进入该页。Scan 页实际按 device.deviceId.boosterFamily.isCm4 分流,因此 CM1/CM2/CM3/MW4/MW5 等非 CM4 设备都会直接走 BLE-only 完成路径。(legacy 全家族无 cloud / account 概念,per PLAN_ACCOUNT_SYSTEM.md 的 "guest mode" 划分)。
补充:除本页里 ref.read(authControllerProvider).isSignedIn 的即时判断外,App 顶层还会在启动时 ref.watch(authControllerProvider),用来恢复持久化 session;因此用户如果此前已登录,回到本页点击 WiFi 时会直接跳过 sign-in,进入后续 WiFi 流程。
lib/features/thermometer/presentation/pages/connection_mode_page.dart(ConsumerStatefulWidget——72151239 / TAPD #1003129 起从 StatelessWidget 升级以读 authControllerProvider,并维护 _committed 状态用于 dispose 时的 setup 清理)authControllerProvider(仅 read.isSignedIn,决定 WiFi 选项跳 sign-in 漏斗还是直入 WiFi 配置)AppPermissions.requestCM4LocalNetwork(iOS 本地网络权限)InternetReachabilityService.instance.hasInternetAccess()(WiFi 卡片 onTap 首步;双端均在 iOS 权限弹窗与路由跳转之前执行;无互联网时弹 addDeviceNoInternetTitle/addDeviceNoInternetBody 并 return,TAPD #1003469)deviceId(路由 arguments,由 Scan 页传过来)/connection-mode(ConnectionModePage.routeName)lib/app.dart 的 onGenerateRoute—— 因为需要 deviceId arguments,不在静态 routes map此外,两张连接方式卡片都不只有标题:标题下方各自还有一行副标题文本,并以较浅的半透明白字显示。
顶部仅一个返回 chevron_left 按钮。中段是大号粗体标题 + 灰色 deviceId 副标题。下方两张等宽选项卡片:第一张是云图标 "WiFi(推荐)"(蓝色 accent),WiFi 卡片下方夹一条蓝色 info banner(蓝牙图标 + l.connectionModeWifiBluetoothHint,fff9dba / TAPD #1003235:提示用户在配对完成前保持手机蓝牙开启,否则 WiFi 配网会持续失败——WiFi 流程全程跑在 BLE 上,详见 WiFi 配置 §Per Liang 2026-06-10),第二张是蓝牙图标 "Bluetooth-only"(绿色 accent)。每张卡片右侧带 chevron_right 暗示可点。整体居中布局,上下大量空白。
补充:除左上角按钮外,Android/iOS 系统返回同样属于“未作出连接方式选择”的离开路径。代码把这类放弃流程的清理统一放在 dispose():只要 _committed 仍为 false,页面销毁时就会调用 deviceSetupProvider.notifier.complete(deviceId),解除该设备本次 add-flow 期间的 alarm suppression / setup 标记。
IconButton.onPressed → Navigator.pop(context)pushReplacementNamed 打开本页,pop 不会回到 Scan 页。dispose,因 _committed 为 false,会调用 deviceSetupProvider.notifier.complete(deviceId) 解除该设备的 alarm suppression(BLE 连接已建立,不会被 pop 撤销)补充:WiFi 路径进入的 sign-in landing 页还有一个页面级同意闸。Google / Email(以及 iOS 上的 Apple)按钮都不是直接放行:代码先检查 _agreed,若用户尚未勾选用户协议/隐私协议同意框,则会先弹出提示对话框,只有勾选后再次点击,才会继续 OAuth 或邮箱登录/注册流程。
⚠️ 72151239 / TAPD #1003129 起前置 sign-in 漏斗 + 242ec8c / TAPD #1003130 起拆 WiFi 两步:CM4 WiFi 路径先经账户登录漏斗(sign-in landing → email + password / Google / Apple 仅 iOS(caa7e95:App Store guideline 4.8 要求 iOS 提供 Apple sign-in,但 Android 无原生 Apple auth、Apple 按钮经 Platform.isIOS 闸;Android 只显 Google + email) ;其中 Google / Apple 登录成功后会直接完成 auth funnel,不进入 6-digit code),有持久化 session 时直接跳过;漏斗终点不再是 /wifi-setup,而是新增的 /wifi-select 网络选择页(Android 扫附近 2.4 GHz AP 列出、auth_widgets.finishAuthFunnel(context, deviceId) push /wifi-select 同时把 auth-stack 全部 pop 掉——c5b09e4 / TAPD #1003149 起原 goToWifiSetup 改名为 finishAuthFunnel,deviceId 改为可空:携 deviceId 仍走「WiFi-onboarding」路径 push /wifi-select,为 null 则只 popUntil auth 路由、返回 sign-in 入口页(如 Settings 的 account-only 入口),不进 WiFi 流程),用户选中 SSID 或走 Skip / 手动输入后再 push /wifi-setup 完成密码 + 配对。当前代码的 authServiceProvider 会按 SupabaseConfig.useSupabase 在 SupabaseAuthService 与 StubAuthService 之间切换,并非固定只有 StubAuthService。BLE-only 路径不经登录、与 legacy guest-mode 一致。
_ConnectionOption.onTap(第一张卡片,Icons.cloud_outlined)InternetReachabilityService.instance.hasInternetAccess() 做真实 HTTP 连通性探测;若无互联网则弹 addDeviceNoInternetTitle/addDeviceNoInternetBody 对话框并停留本页(TAPD #1003469)。通过后,iOS 再弹本地网络权限解释对话框,用户同意后请求权限;然后根据 authControllerProvider.isSignedIn 跳目标——已登录直入 /wifi-select(跳过登录漏斗)、未登录跳 /sign-in(登录入口,创建账号后经邮箱登录注册才进入邮箱验证码;已有账号登录成功则直接调用 finishAuthFunnel(context, deviceId) 进入后续流程。)。两种目标都带 deviceId arguments。GestureDetector.onTap (in _ConnectionOption)
→ if (Platform.isIOS):
showDialog<bool>(...) // CM4 权限解释
→ 用户取消 → return(留在本页)
→ 用户继续 → AppPermissions.requestCM4LocalNetwork()
→ signedIn = ref.read(authControllerProvider).isSignedIn
→ Navigator.pushReplacementNamed(
signedIn ? '/wifi-select' : '/sign-in',
arguments: deviceId)
AlertDialog(addDeviceNoInternet*);用户确认后仍留在本页且 _committed 保持 false。通过后,iOS 再弹应用内权限解释 AlertDialog;用户继续后调用 AppPermissions.requestCM4LocalNetwork()。本页代码本身不直接弹系统「本地网络」权限框;路由栈用 pushReplacement 替换本页(防止用户用返回键回到 ConnectionMode);sign-in 漏斗持久化 session 通过 SharedPreferences,App 重启后 authControllerProvider.restore() 灌回,下次 WiFi 选项 skip 漏斗。另一个关键副作用是:代码只有在真正决定前进到 /wifi-select 或 /sign-in 之前,才把 _committed 设为 true。因此如果 iOS 权限说明弹窗里用户取消,本页仍视为“未作出选择”,后续离开时 dispose() 会执行 deviceSetupProvider.notifier.complete(deviceId) 清掉 setup 标记;而一旦用户已选择 WiFi,该标记会保留穿过登录与 WiFi 配网流程,直到后续完成添加时再统一清除。_ConnectionOption.onTap(第二张卡片,Icons.bluetooth)GestureDetector.onTap
→ _committed = true
→ DeviceGuidePage.showAfterDeviceAdd(context)
≡ ProviderScope.containerOf(context, listen: false)
.read(deviceSetupProvider.notifier)
.completeAll();
Navigator.of(context)
..pushNamedAndRemoveUntil('/dashboard', (route) => false)
..pushNamed('/device-guide')
showAfterDeviceAdd helper,保证「Settings → Operation Guide」与「设备 add 完成后」看到的指南内容完全一致无 Provider 订阅。本页是纯静态导航选择 —— 不需要重建。
| 项 | iOS | Android |
|---|---|---|
| WiFi 选项点击行为 | 先执行 InternetReachabilityService.hasInternetAccess();无网则弹 addDeviceNoInternet* 并停留本页。有网后再弹 cm4PermissionDialogTitle 对话框;确认后调用 AppPermissions.requestCM4LocalNetwork()(占位钩子,当前直接 return true;系统「本地网络」弹窗在后续 wifi_setup_page 首次访问局域网时触发) |
同样先执行互联网探测;无网弹窗并停留。有网后跳过权限解释对话框,直接 pushReplacementNamed 到 /wifi-select(或 /sign-in 若未登录) |
Platform.isIOS 分支在 ConnectionModePage 里传给第一张 _ConnectionOption(WiFi 卡片)的 onTap 回调中;_ConnectionOption 本身只接收并转发 onTap,这里不应写死为 _ConnectionOption.onTap 的某一行号。理由:iOS 14+ 要求 App 显式请求本地网络权限才能扫到同 WiFi 的 CM4,而 Android 没有此要求。