import 'link_item.dart'; import 'link_section.dart'; import 'nav_node.dart'; /// 首页一次性需要的全部数据。 /// /// 打包成一个聚合对象:页面只调用一次仓库、只持有一份状态, /// 后续换成后端接口时也只需要在一个响应里组装出它。 class HomeData { const HomeData({ required this.siteName, required this.searchHint, required this.navNodes, required this.sections, this.submitUrl = '', }); /// 站点名,显示在侧栏顶部与浏览器标题。 final String siteName; /// 搜索框占位文案。 final String searchHint; /// 「提交网站」跳转的地址(Mattermost 之类的收集入口),来自后端 `site.submitUrl`。 /// /// 为空表示后端没配,点了就不做任何事。 final String submitUrl; /// 侧栏导航树。 final List navNodes; /// 内容区分组。 /// /// 数量可能少于导航叶子:原型里 `#icon`、`#stock`、`#dev`、`#api` /// 没有对应元素,点击时什么也不会发生。 final List sections; /// 导航树里的全部叶子,按自上而下的顺序展开。 List get navLeaves => navNodes .expand((NavNode node) => node.leaves) .toList(growable: false); /// 全部站点(扁平化)。 List get allItems => [ for (final LinkSection section in sections) ...section.items, ]; /// 站点总数。 int get itemCount => sections.fold(0, (int sum, LinkSection s) => sum + s.itemCount); /// 原型把 `.active` 写死在第一个导航条目上,这里取同一个值的来源。 String? get activeSectionId => navLeaves.isEmpty ? null : navLeaves.first.id; /// 按 id 取分区;原型里没有对应元素时返回 null。 LinkSection? sectionById(String id) { for (final LinkSection section in sections) { if (section.id == id) { return section; } } return null; } }