☰
pure_live 平台显示、偏好与第三方认证入口布局审计全解:从 6 项缺陷复现到边界保护闭环
2026/10/15 6:39:35 网站建设 项目流程
  • 音视频
  • 直播
  • 移动开发

【免费下载链接】pure_live

纯粹直播:哔哩哔哩/虎牙/斗鱼/快手/抖音/网易cc/YY直播/Twitch直播/SOOP直播/M38自定义源应有尽有。

项目地址:https://gitcode.com/gh_mirrors/pur/pure_live
点击查看免费下载

本篇技术指南围绕 pure_live 开源直播聚合客户端(Flutter 实现)在 2026-09-11 完成的「平台显示、平台偏好与第三方认证入口」专项审计展开。该审计对应验收组 A2-01 的增量,聚焦平台设置页的确定性布局、选择/排序边界保护与抖音认证路由规范化。读完本文,你将掌握 pure_live 平台偏好选择器的实现原理、窄屏大字号下控件纵向堆叠的布局策略、"至少保留一个平台"的控制器级约束、异常排序回调的防御逻辑,以及"规范路由 + 兼容别名"的深链迁移方案,并了解对应的自动化测试覆盖与质量门禁记录。

审计范围与基线

本次审计以提交5ffddea52af4bcb92e289d5e9865f3cfd83c0a9a为基线,对应验收组 A2-01 的「平台显示、平台偏好和第三方认证设置」增量。测试采用固定布局矩阵:320×480分辨率、英文语言、3.0 倍文字缩放;同时保留普通420×800布局、平台目录迁移和导航排序回归,以覆盖窄屏大字号与常规布局两条路径。

审计过程先做修改前复现与代码审查,失败记录保存在local-artifacts/build-records/20260911T090347891Z-quality-focused.json,仓库审计快照为local-artifacts/repository-audits/20260911T090319332Z-focused.json。两个新增测试文件首轮结果为0 PASS / 6 FAIL,稳定复现了六类问题,为后续修订提供了可量化的起点。

修改前稳定复现的六类问题

固定夹具下复现的问题覆盖了信息说明缺失、布局几何失效、排序边界失控、首选值失效、路由误命名与停用项误导六个维度:

  1. 平台偏好项说明缺失:偏好行只显示当前站名(如 bilibili),没有解释该选项对热门/分区首屏的实际影响;标签管理项同样遗漏了已有说明文案,用户无法理解入口含义。
  2. 窄屏大字号下控件错位:平台显示行在窄屏大字号下仍把开关和拖动控件固定在标题右侧。实测控件顶部位于 455.7,而标题底部已延伸到 615.7,二者在纵向空间上完全错位,没有形成可操作的纵向控制行。
  3. 拖动入口与排序边界失控:隐藏项和唯一可见平台仍显示拖动入口;排序回调对异常索引(负数、越界)没有边界保护,可能引发越界访问。
  4. 可见性约束缺失:页面允许隐藏最后一个可见平台;隐藏当前首选平台后,preferPlatform仍指向不可见平台,导致进入热门/分区时首选值失效。
  5. 路由误命名:路由表把抖音 Cookie 页面注册在历史误命名的/douyu_cookie,抖音账号行也继续跳转该路径;规范路径/douyin_cookie反而没有注册。
  6. 停用项视觉误导与弹窗溢出:已停用的斗鱼账号行仍显示"设置 Cookie"和右箭头,视觉上像可进入页面;平台退出确认弹窗在固定矩阵(320×480、3.0 倍字号)下向底部溢出 32 像素,操作按钮不可完整触达。

一个重要的边界约定:现有导入语义会保留用户明确导入的空平台列表,本批不重写备份/迁移数据;"至少保留一个平台"只约束页面上的新操作,避免把 UI 修复扩大成配置兼容性变更。这一点在 favorite_room_controller.dart 的hotAreasList读取/导入逻辑中保持一致——导入时不做强制补全。

平台偏好选择器:说明文案与可滚动弹窗

设置页入口与文案补齐

平台设置页位于 platform_settings_page.dart,由设置页(settings_page.dart)通过Get.to(() => const PlatformSettingsPage())进入。页面包含四个入口:

  • 平台显示(platform_display):跳转/hot_areas,管理首页直播平台排序;
  • 首选直播平台(prefer_platform):选择进入热门/分区时的首屏平台;
  • 三方认证(third_party_auth):跳转/settings_account管理平台授权;
  • 标签管理(tag_management):跳转/settingTags管理直播间过滤标签。

本次修订为平台偏好行补齐了说明与当前本地化站名:

Obx( () => context.buildTile( icon: Remix.heart_3_line, title: i18n("prefer_platform"), subtitle: i18n('prefer_platform_subtitle'), isLong: true, stackTrailingOnNarrow: true, trailing: Wrap( children: [ Text( _platformLabel(FavoriteRoomController.to.preferPlatform.value), ... ), Icon(Icons.chevron_right_rounded, ...), ], ), onTap: () => showPreferPlatformSelectorDialog(context), ), ),

其中_platformLabel通过i18nOr('site_$normalized', normalized)将平台 ID 映射为本地化站名——即偏好行现在显示的是当前选中平台的本地化名称(如"哔哩哔哩"),而不仅仅是内部 ID。对应中英文文案在 assets/translations/zh.json 与 assets/translations/en.json 中可查。

可滚动 AlertDialog 与末项可达性

平台偏好选择器改为整体可滚动的AlertDialog(scrollable: true),内部包含:

  • 顶部过滤输入框(prefer-platform-filter),支持按平台 ID 或名称搜索已显示平台;
  • 无匹配时显示"没有匹配的平台"空态;
  • RadioGroup+RadioListTile列出所有可见平台,选中后调用FavoriteRoomController.to.changePreferPlatform(value)并关闭弹窗。

关键改进点是末项可达:在 320×480、3.0 倍字号下,22 个适配器的最后一项"网络"(IPTV)也能滚动到并点击,整个选项行均可作为点击区域,不再依赖屏幕恰好放下全部选项。

平台显示行:按宽度与文字缩放切换布局

平台显示页(热门平台设置)位于 hot_areas_page.dart,基于ReorderableListView.builder实现,每个平台项包含开关(Switch)、拖动手柄(ReorderableDragStartListener)与 Logo。

纵向堆叠条件

布局决策在LayoutBuilder中完成:

final stackControls = constraints.maxWidth < 360 || MediaQuery.textScalerOf(context).scale(1) > 1.5; return ListTile( title: stackControls ? Column( crossAxisAlignment: CrossAxisAlignment.start, children: [title, const SizedBox(height: 6), controls], ) : title, trailing: stackControls ? null : controls, );

即当行宽小于 360 或文字缩放系数大于 1.5时,开关与拖动入口从trailing移入title下方的纵向Column,形成可操作的纵向控制行。这正是修复 320×480 + 3.0 倍字号下"控件顶部 455.7 vs 标题底部 615.7"错位的机制:不再把控件硬塞到标题右侧,而是自然堆叠。

拖动入口的可见性规则

拖动入口的渲染条件收紧了:

if (isShow && visibleCount > 1) ...[ ReorderableDragStartListener(...), ],
  • 隐藏项不展示拖动入口——隐藏平台不在可排序范围内;
  • 唯一可见平台不展示拖动入口——单项没有可排序对象。

同时页面顶部提示横幅(_buildTipBanner)用双语明确提示:drag_to_sort_tip("长按右侧图标并上下拖动,即可自定义主页直播平台的展示顺序")与at_least_one_platform_required("请至少保留一个可见直播平台")。

控制器边界保护:至少保留一个平台与首选自动修复

开关切换的约束逻辑

开关事件由 hot_areas_controller.dart 的onChanged处理,核心约束:

} else { if (!currentList.contains(id)) return; if (currentList.length <= 1) { ToastUtil.show(i18n('at_least_one_platform_required')); return; } currentList.remove(id); }

隐藏操作在列表仅剩一项时被拒绝并弹出提示,从控制器层面杜绝了"隐藏最后一个可见平台"。随后执行首选平台自动修复:

final currentPreference = FavoriteRoomController.to.preferPlatform.v; if (currentList.isNotEmpty && !currentList.contains(currentPreference)) { FavoriteRoomController.to.preferPlatform.v = currentList.first; }

隐藏当前首选平台时,preferPlatform自动指向剩余列表的第一项,保证首选值永远指向可见平台。

排序回调的防御逻辑

onReorder对异常索引做了完整边界保护:

if (oldIndex < 0 || oldIndex >= sites.length) return; final currentSavedIds = List<String>.from(FavoriteRoomController.to.hotAreasList.v); if (currentSavedIds.length <= 1 || !currentSavedIds.contains(sites[oldIndex].id)) return; if (newIndex < 0) return; if (newIndex > oldIndex) { newIndex -= 1; } final item = sites.removeAt(oldIndex); sites.insert(newIndex.clamp(0, sites.length), item);
  • 负数与越界回调直接返回,保持现状;
  • 单平台或源项不在可见列表时直接返回;
  • 目标索引clamp到合法区间;
  • 排序后重新拼接为「可见平台在前、隐藏平台在后」,且持久化顺序只包含可见平台(hotAreasList.v = newOrderSavedIds),隐藏平台不进入持久化序列。

偏好值的底层校验链

preferPlatform定义于 favorite_room_controller.dart,默认值为Sites.bilibiliSite。changePreferPlatform只接受hotAreasList中的规范化 ID:

void changePreferPlatform(String name) { final normalized = name.trim().toLowerCase(); if (hotAreasList.contains(normalized)) { preferPlatform.v = normalized; } }

启动时还有两层自愈:_normalizeSiteCatalogIds确保偏好值属于受支持平台(否则回落 bilibili),_normalizePreferredPlatform确保偏好值在可见列表中(否则取列表首项)。这些归一化在onInit与配置导入(fromJson)时都会执行。

偏好值如何影响首屏

preferPlatform的实际消费点有两处:

  • areas_controller.dart:分区页首次初始化 TabController 时,用preferPlatform定位初始 Tab 索引(未命中则回退到 0);
  • popular_controller.dart:热门页同样用偏好值决定初始 Tab。

因此"首选直播平台"的说明文案prefer_platform_subtitle("当进入热门/分区,首选的直播平台")与实现严格对应——它决定的是热门/分区页打开时的初始 Tab。同时hotAreasList通过ever监听驱动两个页面在平台列表变化时重建 Tab(见 areas_controller.dart),隐藏/排序修改即时生效。

路由修正:规范路径与旧深链兼容别名

双路径注册方案

本次修订新增规范路由/douyin_cookie,并保留/douyu_cookie作为旧深链兼容别名。两条路径各注册一次,都指向同一个DouyinCookiePage与DouyinCookieBinding:

GetPage( name: RoutePath.kDouyinCookie, // "/douyin_cookie"(规范) page: _smoothPage(() => const DouyinCookiePage()), bindings: [DouyinCookieBinding()], ), // Preserve the historical misnamed deep link while all in-app navigation // uses the canonical Douyin path above. GetPage( name: RoutePath.kDouyuCookie, // "/douyu_cookie"(旧深链别名) page: _smoothPage(() => const DouyinCookiePage()), bindings: [DouyinCookieBinding()], ),

路由常量定义在 route_path.dart:kDouyinCookie = "/douyin_cookie",kDouyuCookie = "/douyu_cookie"(注释明确标注其为历史别名,旧版本曾用它打开抖音 Cookie 页)。全表注册见 app_pages.dart。

应用内入口统一走规范路径

账号页(account_page.dart)的抖音账号行在未登录时通过Get.toNamed(RoutePath.kDouyinCookie)跳转,应用内导航一律使用规范路径。/douyu_cookie仅服务于历史深链(如旧版本生成的分享链接、书签或外部唤起),避免旧链接失效。抖音 Cookie 页本身是基于AccountCookieEditorPage的薄封装(douyin_cookie_page.dart),提供hintText/tipText/onSave三要素。

停用账号行的视觉收敛与退出确认弹窗

停用项不再伪装可点

斗鱼账号行在 account_page.dart 中基于DouyuUtils.sessionState(cookie.douyuCookie.v)判断会话状态,本次修订后:

  • 会话为none(无 Cookie)时副标题显示"未开启/Disabled",移除右箭头(trailing为null)与点击处理(onTap: null),视觉上明确不可进入;
  • ListTile.enabled: isEnabled配合theme.disabledColor,标题/副标题整体置灰;
  • 会话过期但可续期(expiredRefreshable)时仍视为已登录,因播放时 Cookie 会自动续期,不误导用户。

_buildAccountTile同时支持窄屏大字号下副标题两行显示(maxLines: expandedText ? 2 : 1),与平台显示行的纵向堆叠策略保持一致。

退出确认弹窗的有界滚动

退出/清空确认统一走_confirmDangerDialog,采用有界可滚动弹窗:

AlertDialog( scrollable: true, insetPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 20), content: ConstrainedBox(constraints: const BoxConstraints(maxWidth: 420), child: Text(message)), actionsOverflowDirection: VerticalDirection.down, actionsOverflowButtonSpacing: 8, ... )

scrollable: true让内容区在固定矩阵(320×480、3.0 倍字号)下可滚动而不溢出;insetPadding在垂直方向保留 20px 边距;actionsOverflowDirection: VerticalDirection.down让操作按钮在放不下时纵向排布且可完整触达,修复了原先"向底部溢出 32 像素"的问题。按钮同时设置了minimumSize: Size(48, 48),满足最小触达尺寸。

验证与质量门禁

修订后的验证以最终统一质量记录local-artifacts/build-records/20260911T091303512Z-quality-focused.json与仓库审计local-artifacts/repository-audits/20260911T091151425Z-focused.json为准:

  • 新增平台设置与账号导航测试 8/8 PASS,覆盖:偏好说明/末项可达、本地化当前值、控制行几何(纵向堆叠)、唯一项保护、首选自动修复、有效/异常/隐藏项排序、规范与兼容路由、停用账号视觉以及退出确认操作可达;
  • 联合底部导航、站点注册及 AcFun、花椒、映客、克拉克拉、猫耳、niconico、OPENREC、Picarto、TTingLive、TwitCasting、微博和小红书目录迁移测试,16 个文件最终49/49 PASS;
  • 全库flutter analyze执行一次,No issues found;修改的 Dart 文件格式检查无差异;
  • 仓库完整性审计错误为0(已知告警 2);质量任务结束时重型进程为0,实际 ADB 命令为0;
  • 本批未操作手机、未修改 LSP/Root/ADB、未构建候选包、未发布 3.2.0——这是一次纯代码层的确定性布局与边界修复闭环。

当前边界与后续闭环

本批完成后,A2-01 验收组保持RUN状态。已闭环的是确定性页面布局、选择/排序边界与抖音认证入口;仍按完整验收矩阵推进的事项包括:真实 Android / Windows 设备上的平台设置逐项操作、Cookie 登录实际结果、覆盖升级后的持久化验证,以及最终候选包验证。宏观验收状态保持20 PASS / 33 RUN / 9 NR,共 42 组未闭环,说明该审计是整体验收流程中的一环,而非终点。

对本主题的后续深入,可继续查阅:平台偏好与目录迁移的状态逻辑 favorite_room_controller.dart、平台显示页的响应式实现 hot_areas_page.dart、排序边界控制器 hot_areas_controller.dart、账号页停用项与退出弹窗 account_page.dart,以及路由别名方案 app_pages.dart。

  • 音视频
  • 直播
  • 移动开发

【免费下载链接】pure_live

纯粹直播:哔哩哔哩/虎牙/斗鱼/快手/抖音/网易cc/YY直播/Twitch直播/SOOP直播/M38自定义源应有尽有。

项目地址:https://gitcode.com/gh_mirrors/pur/pure_live
点击查看免费下载

相关推荐

上一篇:GitBucket安全工具选型解决方案指南:方法论与工具选型
下一篇:TypeScript项目配置优化:sway-farm编译性能与类型检查

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询