# 浏览频道与成员

> 用户流程：分配到默认频道 → 浏览频道列表 → 浏览频道中的人员分布 → poke 服务器内成员
> 对应程序流程：TsClient.getChannelList() 建立频道基线 → TsClient.getClientList() 建立成员基线 → 持续接收 onClient* 事件增量更新

---

## 一、状态树

### 同步状态

```mermaid
flowchart TD
    SYNC[同步状态<br/>建立服务器数据基线]

    SYNC --> S0[未同步 Unsynced<br/>已连接但尚无完整数据]
    SYNC --> S1[同步中 Syncing<br/>调用 getChannelList 和 getClientList]
    SYNC --> S2[已同步 Synchronized<br/>列表基线可供 UI 使用]
    SYNC --> S3[同步失败 SyncFailed<br/>列表请求失败或上下文失效]
```

### 成员状态

```mermaid
flowchart TD
    MEMBER[成员状态<br/>维护在线用户及频道位置]

    MEMBER --> M0[空基线 Empty<br/>尚未执行 ListClients]
    MEMBER --> M1[已有基线 Loaded<br/>getClientList 已建立成员表]
    MEMBER --> M2[增量更新 Updating<br/>OnClientEnter/Leave/Moved 正在归并]
    MEMBER --> M3[不一致 Inconsistent<br/>事件引用未知成员或频道]
```

### 成员实体状态树

```mermaid
flowchart TD
    ENTITY[成员实体状态树<br/>描述 ListClients 基线与实时事件如何协同]

    ENTITY --> BASE[基线分支<br/>由客户端请求建立完整成员集合]
    ENTITY --> DELTA[增量分支<br/>由服务端推送维护实时变化]
    ENTITY --> SELF[当前用户分支<br/>通过 ClientID 识别自己的事件]
    ENTITY --> REPAIR[修复分支<br/>处理事件缺失或引用不一致]

    BASE --> B1[getClientList 请求<br/>获取服务器当前全部在线客户端]
    B1 --> B2[按 clientID 建表<br/>使用客户端 ID 去重保存]
    B2 --> B3[按 channelID 建索引<br/>派生每个频道的成员列表]

    DELTA --> D1[onClientEnter<br/>新增或覆盖进入视野的成员]
    DELTA --> D2[onClientMoved<br/>覆盖成员的 channelID]
    DELTA --> D3[onClientLeave<br/>按 clientID 幂等删除成员]

    SELF --> S1[getClientId 本地调用<br/>读取服务器分配的自身 ID]
    S1 --> S2[事件 clientID 比对<br/>判断移动或离开事件是否属于自己]
    S2 --> S3[更新自身频道事实<br/>自己的移动决定实际所在频道]

    REPAIR --> R1[检测未知 clientID<br/>移动或离开事件找不到实体]
    R1 --> R2[重新调用 getClientList<br/>用完整列表修复成员基线]
    REPAIR --> R3[检测未知 channelID<br/>成员引用本地不存在的频道]
    R3 --> R4[重新调用 getChannelList<br/>用完整列表修复频道基线]
```

---

## 二、时序：成员进入、移动与离开

```mermaid
sequenceDiagram
    participant TS as TeamSpeak 服务器<br/>产生在线成员变化
    participant SDK as Go SDK (gomobile)<br/>解析 notify* 通知
    participant BRIDGE as TSBridge<br/>JNI 回调转 EventBus
    participant EB as EventBus<br/>事件合并与分发
    participant REDUCER as ViewModel<br/>debounce 后刷新成员
    participant STORE as 成员实体仓库<br/>以 clientID 保存唯一成员
    participant UI as 频道树 UI<br/>派生人数和成员预览

    TS-->>SDK: notifycliententerview
    SDK->>BRIDGE: OnClientEnter(Client)
    BRIDGE->>EB: emit(ClientEnter)

    TS-->>SDK: notifyclientmoved
    SDK->>BRIDGE: OnClientMoved(id, channelID)
    BRIDGE->>EB: emit(ClientMoved)

    TS-->>SDK: notifyclientleftview
    SDK->>BRIDGE: OnClientLeave(id, reason)
    BRIDGE->>EB: emit(ClientLeave)

    Note over EB: debounce 300ms 合并

    EB->>REDUCER: collect → refreshClientList()
    REDUCER->>STORE: TSBridge.getClientsJSON() → 全量刷新
    STORE-->>UI: 实体引用变化
```

---

## 三、事件依赖

### 成员相关前置依赖矩阵

| 操作或事件 | 必须依赖 | 建议依赖 | 依赖失败时的处理 |
| --- | --- | --- | --- |
| `getClientsJSON` 增量归并 | 已有频道基线更易校验 | 与 `getChannelsJSON` 同一同步批次 | 未知频道触发频道补偿读取 |
| `ClientEnter` 事件 | 已注册 EventBus 事件收集 | 已有成员基线 | debounce 后全量刷新 |
| `ClientMoved` 事件 | 已注册 EventBus 事件收集 | 已有成员基线和自身 ID | debounce 后全量刷新 |
| `ClientLeave` 事件 | 已注册 EventBus 事件收集 | 已有成员基线 | debounce 后全量刷新 |

### 服务端通知映射

| 服务端通知 | TSBridge 回调 → EventBus 事件 | 上层状态依赖 | 中文说明 |
| --- | --- | --- | --- |
| `notifycliententerview` | `OnClientEnter` → `ClientEnter` | 成员实体表、频道成员 Selector | 新成员进入视野后 debounce 合并刷新 |
| `notifyclientleftview` | `OnClientLeave` → `ClientLeave` | 成员实体表、离开原因 | debounce 合并刷新 |
| `notifyclientmoved` | `OnClientMoved` → `ClientMoved` | 成员位置、当前用户频道 | debounce 合并刷新 |
| `notifyclientpoke` | `OnPoked` → `Poked` | Poke 通知或成员卡 | 立即分发，展示发送者和 Poke 内容 |

### 缺失事件能力带来的依赖限制

根据 SDK 文档，当前公开事件不包含频道创建、频道更新和频道删除。因此：

1. `TsClient.getChannelList()` 是频道目录的主要权威来源。
2. 不能假设频道列表会依靠 `onClient*` 事件永久保持最新。
3. 发现成员引用未知频道时，应重新调用 `TsClient.getChannelList()`。
4. 如果产品需要实时频道管理，应扩展 SDK 对相应 `notify*` 通知的支持。

### 权威性划分

| 数据 | 推荐权威来源 | 原因 |
| --- | --- | --- |
| 成员实时变化 | EventBus `ClientEnter/Leave/Moved` 事件 | 服务端主动推送实时事实，debounce 后全量刷新 |
