← API | 列表 | 交互组件整理报告
提示信息
# 同伴X 交互组件整理报告 v2.0

> 本报告由 v1.0(弹窗 + 空状态)扩展为 **完整 Widget 组件库说明文档**。  
> 开发时必须使用规范组件,禁止自行拼装原生 Widget。  
> 组件演示:登录后进入 `/showcase`(仅 Debug 模式)。

---

## 一、按钮组件

### 1.1 主操作按钮 `PrimaryActionButton`

**用途**:页面主要 CTA(提交、登录、确认等)  
**规范**:
- 高度固定 **48dp**,圆角 **24dp**(全胶囊)
- 内置 Loading 防抖:点击后自动禁用,`onPressed` 异步完成后自动恢复
- 颜色:主色 `AppColors.primary`;危险操作 `AppColors.error`(`isDestructive: true`)
- 禁用:`onPressed: null`,背景自动变 `AppColors.divider`

```dart
// 标准用法(必须传 async 闭包)
PrimaryActionButton(
  label: '立即登录',
  onPressed: () async {
    await ref.read(authController.notifier).login(phone, code);
  },
)

// 危险操作
PrimaryActionButton(
  label: '确认删除',
  isDestructive: true,
  onPressed: () async { ... },
)

// 全宽
PrimaryActionButton(...).wFull(context)
```

---

### 1.2 社交胶囊按钮 `AppCapsuleButton`

**用途**:用户关系操作(关注 / 已关注 / 回关 / 互相关注)

| variant | 样式 | 场景 |
|---|---|---|
| `primaryOutline` | 主色边框,透明背景 | + 关注 |
| `primaryFilled` | 主色填充 | 回关 |
| `secondaryOutline` | 灰色边框 | 已关注 |
| `surfaceVariant` | 灰背景 | 互相关注 |

```dart
AppCapsuleButton(
  variant: AppCapsuleVariant.primaryOutline,
  onTap: () => ref.read(followCtrl.notifier).follow(userId),
)
```

---

## 二、导航栏组件

### 2.1 `AppNavBar` — 通用导航栏

**规范**:高度 44pt(iOS HIG),`showBack: false` 用于 Tab 根页面

```dart
AppNavBar(
  title: '我的主页',
  showBack: true,          // 默认 true
  actions: [Icon(AppIcons.more, ...).p8().onTap(...)],
)
```

---

### 2.2 `AppTransparentNavBar` — 透明渐变导航栏

**规范**:**必须**传入 `scrollController`,否则透明度永不变化

```dart
AppTransparentNavBar(
  title: '活动详情',
  scrollController: scrollController,
  titleThreshold: 200,
)
```

---

### 2.3 `AppSearchNavBar` — 搜索导航栏

```dart
AppSearchNavBar(
  hintText: '搜索用户',
  controller: textCtrl,
  onSubmitted: (query) => ref.read(searchCtrl.notifier).search(query),
  autofocus: true,
)
```

---

### 2.4 `AppChatNavBar` — 聊天导航栏

```dart
AppChatNavBar(
  avatarUrl: user.avatar,
  name: user.nickname,
  isOnline: user.isOnline,
  actions: [Icon(AppIcons.more).p8().onTap(...)],
)
```

---

### 2.5 `AppBottomNavBar` — 底部导航栏

**规范**:基于 Material 3 `NavigationBar`,高度 64dp,已配置选中指示器颜色。  
**使用**:挂载在 `MainShellPage` 的 `bottomNavigationBar` 中,不要单独使用。

```dart
// 在 MainShellPage 中自动挂载,无需手动使用
AppBottomNavBar(
  currentIndex: currentIndex.value,
  onTap: (i) => currentIndex.value = i,
  unreadMessageCount: unreadCount, // 来自消息 Provider
)
```

---

## 三、弹窗 & 底部表单

### 3.1 确认弹窗 `AppDialog.show`

**规范**:所有二次确认弹窗必须使用此方法,禁止自行构建 `AlertDialog`

```dart
final result = await AppDialog.show(
  context: context,
  title: '确认删除?',
  content: '删除后将无法恢复,确定要删除这条信号吗?',
  confirmLabel: '确认删除',
  cancelLabel: '再想想',
  isDestructive: true, // 确认按钮变红
);
if (result == true) { /* 用户点了确认 */ }
```

**返回值**:`true` = 确认,`false` = 取消,`null` = 点击背景关闭

---

### 3.2 底部表单 `AppDialog.showSheet`

**规范**:所有 `showModalBottomSheet` 必须用此封装

```dart
await AppDialog.showSheet<void>(
  context: context,
  title: '选择操作',      // 可选,不传则无标题行
  showDragHandle: true,  // 默认显示拖拽指示条
  child: [...].vStack(),
);
```

---

### 3.3 历史弹窗规范(二次确认弹窗清单)

| 功能 | 标题 | 按钮类型 |
|---|---|---|
| 退出登录 | 退出登录 | 危险 (红) |
| 注销账号 | 确认注销? | 危险 (红),带5秒倒计时 |
| 清空所有消息 | 清空所有消息? | 危险 (红) |
| 删除信号 | 确认删除 | 危险 (红) |
| 拉黑用户 | 确定拉黑此用户? | 危险 (红) |
| 取消关注 | 取消关注 | 普通 |
| 提交举报 | 确认提交 | 普通 |
| 清空访客 | 清空访客? | 危险 (红) |
| 清空缓存 | 确认清空 | 危险 (红) |

---

## 四、Toast 通知

### 4.1 `AppToast` — 覆盖层 Toast

**规范**:Overlay 式实现,从顶部带动画滑入,2.5s 后自动消失,**不阻断交互**。  
左侧色块指示类型,支持 4 种:

| 方法 | 颜色 | 场景 |
|---|---|---|
| `AppToast.showInfo(msg)` | 主色蓝紫 | 普通提示 |
| `AppToast.showSuccess(msg)` | 绿色 | 操作成功 |
| `AppToast.showWarning(msg)` | 橙色 | 警告提醒 |
| `AppToast.showError(msg)` | 红色 | 错误失败 |
| `AppToast.show(msg)` | 主色 | 默认 info |

```dart
// 业务层错误处理
try {
  await api.submit();
  AppToast.showSuccess('提交成功');
} catch (e) {
  AppToast.showError(e is AppApiError ? e.msg : '操作失败,请重试');
}
```

---

## 五、头像组件

### 5.1 `CustomAvatar`

**规范**:禁止使用 `CircleAvatar`,统一使用此组件处理 OSS 裁剪、加载失败占位

| AvatarSize | 像素 | 场景 |
|---|---|---|
| `sm` | 32×32 | 评论列表 |
| `df` | 48×48 | 普通列表(默认) |
| `lg` | 64×64 | 聊天消息 |
| `xl` | 80×80 | 个人主页 |
| `xxl` | 120×120 | 设置大头像 |

```dart
CustomAvatar(
  url: user.avatarUrl,
  name: user.nickname,   // 加载失败显示首字母
  size: AvatarSize.df,
)
```

---

## 六、骨架屏 & 加载

**规范**:禁止裸用 `Shimmer.fromColors`;骨架效果统一通过 `AppSkeleton` 输出,shimmer 颜色由配置统一管理。

### 6.1 ✅ 推荐:`AppSkeleton.wrap` / `Skeletonizer`(自动生成骨架)

**优点**:复用真实 Widget 结构,无需手写骨架布局,loading/正常状态一键切换。
**依赖包**:`skeletonizer: ^1.4.2`(已集成至 `shared.dart`)

```dart
// 方式一:enabled 开关(最推荐)
AppSkeleton.wrap(
  enabled: isLoading,
  child: UserCard(user: isLoading ? User.empty() : user),
)

// 方式二:AsyncValue loading 分支
ref.watch(usersProvider).when(
  data: (users) => _buildList(users),
  loading: () => AppSkeleton.fromWidget(
    child: _buildList(List.generate(5, (_) => User.empty())),
  ),
  error: (e, _) => AppEmptyState(type: AppEmptyType.serverError),
)

// 方式三:直接用 Skeletonizer(需走 AppSkeleton.wrap 保证颜色一致)
// ❌ 错误:Skeletonizer(child: ...)  直接用
// ✅ 正确:AppSkeleton.wrap(enabled: true, child: ...)
```

---

### 6.2 兜底:`AppSkeleton` 内置工厂(无法复用真实 Widget 时使用)

```dart
AppSkeleton.listTile()         // 列表项骨架(头像+两行文字)
AppSkeleton.card(height: 160)  // 卡片骨架
AppSkeleton.circle(size: 48)   // 圆形骨架(头像占位)
AppSkeleton.page()             // 页面级骨架(顶部大图+列表)
```

---

### 6.2 `AppLoading`

**规范**:禁止裸用 `CircularProgressIndicator`

```dart
const AppLoading()            // 局部加载(居中转圈)
AppLoading(fullScreen: true)  // 全屏蒙层加载
```

---

## 七、空状态

### 7.1 `AppEmptyState`

**规范**:禁止手拼空状态 UI,统一使用此组件

| type | 场景 |
|---|---|
| `noData` | 通用暂无数据 |
| `networkError` | 网络失败 |
| `searchEmpty` | 搜索无结果 |
| `noMessage` | 消息列表为空 |
| `noNotification` | 通知为空 |
| `permissionDenied` | 无权限 |
| `serverError` | 服务异常 |
| `custom` | 自定义文案 |

```dart
// 带操作按钮
AppEmptyState(
  type: AppEmptyType.networkError,
  actionLabel: '重新加载',
  onAction: () => ref.refresh(myProvider),
)

// 自定义
AppEmptyState(
  type: AppEmptyType.custom,
  title: '还没有信号',
  subtitle: '快去发布第一条信号吧!',
  actionLabel: '立即发布',
  onAction: () => context.push(AppRoutes.publishSignal),
)
```

---

## 八、列表 & 刷新

### 8.1 `AppListView<T>`

**规范**:禁止手拼 `EasyRefresh` + 分页逻辑,使用封装组件一站式处理

```dart
AppListView<SignalModel>(
  items: signals,
  itemBuilder: (ctx, index, item) => SignalCard(signal: item),
  onRefresh: () async => ref.refresh(signalListProvider),
  onLoadMore: () async => ref.read(signalListCtrl.notifier).loadMore(),
  emptyType: AppEmptyType.noData,
)
```

---

## 九、TabBar

### 9.1 `AppTabBar`

**规范**:选中 12sp/粗体,未选中 12sp/普通,指示器宽度跟随文字

```dart
AppTabBar(
  tabs: const ['热门', '动态', '关注'],
  controller: tabController,
  onTap: (index) { ... },
)
```

---

## 十、空状态文案规范(历史清单)

| 场景 | 主标语 | 描述 |
|---|---|---|
| 关注列表为空 | 还没有关注任何人 | 去发现一些有趣的人 |
| 粉丝列表为空 | 暂时还没有粉丝 | 多发布高质量信号 |
| 黑名单为空 | 黑名单为空 | 世界很和平 |
| 信号收件箱为空 | 暂无信号 | 附近暂无新信号 |
| 信号记录为空 | 暂无信号记录 | 去发布第一条信号 |
| 消息列表为空 | 暂无消息 | 去发射信号,寻找志同道合的伙伴 |
| 访客记录为空 | 访客空空如也 | 主动出击去别人主页 |

---

**更新说明**:  
- v1.0:整理弹窗 + 空状态  
- v2.0:扩展为完整 Widget 组件库,新增 AppToast、AppDialog、AppBottomNavBar、各尺寸头像、按钮规范等