市场 UI
市场 UI 在 lib/features/market/ 下,三个页面 + 一个共享组件。全部用 Material Design 3 Expressive + Claymorphism 设计系统(复用 app.dart 里的 ClayContainer / IconBadge / tokens)。
market_page.dart— 市场首页(3 Tab + 卡片网格)plugin_detail_page.dart— 插件详情页(Friend / Application)pack_detail_page.dart— 整合包详情页preview_image.dart— 共享预览图组件
MarketPage
Section titled “MarketPage”ConsumerStatefulWidget + SingleTickerProviderStateMixin + TabController(length: 3)。
- AppBar:标题“插件市场” + refresh 按钮(叠
Badge红点,当有可用更新时) - TabBar:三个 Tab — 伙伴 / 应用 / 整合包
- TabBarView:三个
_MarketTab
_MarketTab
Section titled “_MarketTab”每个 Tab 是一个 ConsumerWidget:
RefreshIndicator包ref.watch(provider).when(loading/error/data)- loading:
CircularProgressIndicator - error:
cloud_off图标 + “加载失败” + 错误信息 - data:
GridView.builder(maxCrossAxisExtent: 500,自适应列数)
_MarketPluginCard
Section titled “_MarketPluginCard”ConsumerWidget,每个插件一张卡片:
ClayContainer(kRadiusLg,shadowIntensity: 0.6,tap 进详情页)- 顶部:
PreviewImage(Expanded填充卡片剩余空间,BoxFit.cover) - 底部:名称 / 作者 / 描述(2 行)/ 评分 + 安装按钮
安装按钮三态:
| 状态 | 按钮 |
|---|---|
| 未安装 | FilledButton("安装") |
| 已安装 + 有更新 | FilledButton.tonal("更新") |
| 已安装 + 无更新 | OutlinedButton("已安装")(禁用) |
| 安装中 | CircularProgressIndicator(18×18) |
AppBar Badge
Section titled “AppBar Badge”final hasUpdates = ref.watch(marketUpdatesProvider).maybeWhen( data: (updates) => updates.isNotEmpty, orElse: () => false,);
Badge( isLabelVisible: hasUpdates, backgroundColor: theme.colorScheme.error, child: const Icon(Icons.refresh_rounded),)有可用更新时 refresh 图标右上角显示红色小点。
Future<void> _install(MarketPlugin plugin) async { if (_installing.contains(plugin.id)) return; final manager = ref.read(pluginManagerProvider).maybeWhen(data: (m) => m, orElse: () => null); final isUpdate = manager?.pluginById(plugin.id) != null;
setState(() => _installing.add(plugin.id)); try { final installer = await ref.read(pluginInstallerProvider.future); final result = await installer.install(plugin); if (result.success) { SnackBar(isUpdate ? '更新成功' : '安装成功'); ref.invalidate(pluginManagerProvider); ref.invalidate(marketUpdatesProvider); } else { SnackBar('安装失败: ${result.error}'); } } finally { setState(() => _installing.remove(plugin.id)); }}void _openDetail(MarketPlugin plugin) { Navigator.pushNamed( context, plugin.type == PluginType.pack ? '/market/pack' : '/market/plugin', arguments: plugin, );}PluginDetailPage
Section titled “PluginDetailPage”ConsumerStatefulWidget。路由 /market/plugin,参数是 MarketPlugin。
final args = ModalRoute.of(context)?.settings.arguments;if (args is! MarketPlugin) { return Scaffold(body: Center(child: Text('参数错误')));}SingleChildScrollView 纵向滚动:
- 预览图:
ClayContainer(kRadiusXl)+AspectRatio(1)+PreviewImage - 名称:
headlineSmallw700 - 作者 + 版本 chip:
bodyMedium+Chip("v1.0.0") - 描述:
bodyLargeheight 1.6 - 平台 chips:
WrapofChip,当前平台用primaryContainer高亮 - 统计行:三个
_StatTile(文件大小 / 下载量 / 评分) - 源码链接:
SelectableText显示 GitHub raw URL(无 url_launcher,用户自己复制) - 安装按钮:全宽
FilledButton.icon
| 状态 | 按钮 |
|---|---|
| 未安装 | FilledButton.icon(Icons.download, "安装") |
| 已安装 + 有更新 | FilledButton.tonalIcon(Icons.upgrade, "更新") |
| 已安装 + 无更新 | OutlinedButton.icon(Icons.check, "已安装")(禁用) |
| 安装中 | FilledButton.icon(Icons.downloading, "安装中…")(禁用)+ LinearProgressIndicator(value: _progress) |
_progress 来自 PluginInstaller.install(onProgress) 的回调——流式下载时按字节进度更新。
Pack 详情
Section titled “Pack 详情”PackDetailPage 复用 PluginDetailBody(isPack: true),加一条说明:
整合包包含一组 Friend / Application 插件,点击安装将一并装入。
不展示子插件列表(子插件信息在 zip 内,不在 meta.json 里)。安装走同一个 PluginInstaller.install,内部调 installPack 展开子插件。
PreviewImage
Section titled “PreviewImage”ConsumerStatefulWidget,共享组件。参数 MarketDir dir + String id。
PreviewImage(dir: plugin.dir, id: plugin.id)initState里异步调repository.fetchPreview(dir, id)拿缓存 File- 拿到后
Image.file(file, fit: BoxFit.cover, gaplessPlayback: true) - 加载中:
CircularProgressIndicator(背景surfaceContainerHighest(0.3)) - 失败:
IconBadge(Icons.image_outlined)占位
跨卡片和详情页复用,避免重复加载。
在 lib/app.dart 的 MoodPetApp.routes 里:
routes: { '/plugins': (context) => const PluginManagementPage(), '/settings': (context) => const SettingsPage(), '/market': (context) => const MarketPage(), '/market/plugin': (context) => const PluginDetailPage(), '/market/pack': (context) => const PackDetailPage(),},/market 从插件管理页的 AppBar store 图标 push 进来。