- §3 补充头像上传(3.3)和保存(3.4)数据流 - §8 新增编辑资料 UI 规格表 - §9 更新待完成事项(#6 已关闭) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
8.5 KiB
8.5 KiB
我的(Mine)Tab — 架构文档
对应 Gitea issues #5–#13,#39–#41(UI 重设计),#49–#50(编辑资料完整实现) 参考实现:
im-client-ios-swift-demoFeatures/Settings + Features/Profile
1. 功能范围
| Issue | 功能 | 状态 |
|---|---|---|
| #5 | Tab 重命名 & 个人资料卡片 | ✅ 已实现 |
| #6 | 编辑个人资料(昵称/bio/头像) | ✅ 全量实现(#49/#50 补全头像上传) |
| #7 | 退出登录 | ✅ 已实现 |
| #8 | 主题持久化 | ⏳ TODO(解开 ThemeModeNotifier 注释) |
| #9 | 语言设置 | ✅ UI 框架(l10n_sdk 待接入) |
| #10 | 黑名单管理 | ✅ 页面框架(API 待实现) |
| #11 | 聊天文件夹 | ⏳ TODO stub |
| #12 | 网络诊断 | ✅ 已实现(4步诊断) |
| #13 | 关于 / 版本 | ✅ 已实现 |
2. 目录结构
features/settings/
├── di/
│ └── settings_providers.dart # UseCase DI 装配
├── presentation/
│ ├── settings_view_model.dart # 我的页 ViewModel (NotifierProvider)
│ ├── edit_profile_view_model.dart # 编辑资料 ViewModel
│ ├── network_diagnostics_view_model.dart # 网络诊断 ViewModel
│ └── theme_view_model.dart # 主题 ViewModel (@riverpod)
├── usecases/
│ ├── fetch_profile_usecase.dart # GET /app/api/user/profile
│ ├── update_profile_usecase.dart # POST /app/api/user/update-profile
│ ├── logout_usecase.dart # POST /app/api/auth/logout + WS + DB
│ └── set_theme_usecase.dart # 幂等主题切换
└── view/
├── settings_page.dart # 我的主页
├── edit_profile_page.dart # 编辑资料页
├── language_page.dart # 语言选择页
├── blocklist_page.dart # 黑名单页(框架)
├── network_diagnostics_page.dart # 网络诊断页
├── about_page.dart # 关于页
├── theme_view.dart # 主题选择页
└── widgets/
├── settings_section_header.dart
└── theme_option_tile.dart
data/remote/
├── get_profile_request.dart # GET /app/api/user/profile(已有)
└── update_profile_request.dart # POST /app/api/user/update-profile(新增)
3. 数据流
3.1 资料加载
SettingsPage (build)
└─ ref.watch(settingsViewModelProvider)
└─ SettingsViewModel.build()
└─ Future.microtask(loadProfile)
└─ FetchProfileUseCase.execute()
└─ NetworksSdkApi.executeRequest(GetProfileRequest())
└─ GET /app/api/user/profile (JWT token in header)
└─ ProfileResponse → SettingsState {nickname, avatarUrl, maskedContact, uid}
3.2 退出登录
SettingsPage._confirmLogout()
└─ AlertDialog confirm
└─ SettingsViewModel.logout()
├─ LogoutUseCase.execute()
│ ├─ AuthRepository.logout() → POST /app/api/auth/logout
│ ├─ SocketManager.disconnect() → 断开 WebSocket
│ └─ StorageSdkLifecycle.closeDatabase()
└─ AuthNotifier.logout() → go_router 重定向 /login
3.3 编辑资料 — 头像上传(#49)
EditProfilePage._showAvatarSourceSheet()
└─ BottomSheet → 相册 / 拍照
└─ EditProfileViewModel.pickAndUploadAvatar(ImageSource)
├─ ImagePicker.pickImage(source, maxWidth:900, maxHeight:900, quality:85)
├─ ImageCropper.cropImage(1:1, IOSUiSettings / AndroidUiSettings)
├─ state.isUploadingAvatar = true
├─ NetworksSdkApi.executeRequest(UploadFileRequest(filePath))
│ └─ POST /app/api/upload/file (FormData multipart)
│ └─ UploadResult.url
└─ state.avatarUrl = url (UI 实时预览更新)
3.4 编辑资料保存(#50)
EditProfilePage → 保存按钮(nickname.isNotEmpty && !isUploadingAvatar)
└─ EditProfileViewModel.save()
└─ UpdateProfileUseCase.execute(nickname, bio, profilePicUrl)
└─ NetworksSdkApi.executeRequest(UpdateProfileRequest)
└─ POST /app/api/user/update-profile
└─ SettingsViewModel.loadProfile() (刷新资料卡)
└─ Navigator.pop()
4. API 端点
| 操作 | Method | Path |
|---|---|---|
| 获取当前用户资料 | GET | /app/api/user/profile |
| 更新用户资料 | POST | /app/api/user/update-profile |
| 退出登录 | POST | /app/api/auth/logout |
| 黑名单列表(待实现) | GET | /app/api/account/block/list |
| 解除拉黑(待实现) | POST | /app/api/account/block/remove |
| 聊天文件夹(待实现) | POST | /app/api/account/store/get-store |
5. Provider 设计
所有 Settings ViewModel 使用 Notifier<T> + 手动 NotifierProvider,不使用 @riverpod 代码生成,避免额外 build_runner 依赖:
class SettingsViewModel extends Notifier<SettingsState> { ... }
final settingsViewModelProvider = NotifierProvider<SettingsViewModel, SettingsState>(
SettingsViewModel.new,
);
DI 链路:
settingsViewModelProvider
└─ fetchProfileUseCaseProvider → networkSdkApiProvider
└─ logoutUseCaseProvider
├─ authRepositoryProvider → networkSdkApiProvider
├─ socketManagerProvider
└─ storageSdkProvider
6. 路由
新增 Shell 外全屏路由(parentNavigatorKey: _rootKey),TabBar 在子页面隐藏:
/settings/edit-profile EditProfilePage
/settings/blocklist BlocklistPage
/settings/language LanguagePage
/settings/network-diagnostics NetworkDiagnosticsPage
/settings/about AboutPage
/settings/theme ThemeView(原有)
认证守卫 auth_guard.dart switch 已补全上述路由。
7. UI 重设计(#39 / #40 / #41)
7.1 ProfileHeroCard (#39 / #41)
| 元素 | 规格 |
|---|---|
| 头像 | 72pt 圆形;无头像时 8 色渐变占位(uid%8) |
| 昵称 | fontWeight w700,titleMedium |
| Handle | @J{uid},bodySmall onSurfaceVariant |
| 手机号 | 掩码 +CC ***XXXX,bodySmall |
| Bio | 非空显示,为空显示「添加一句话简介」(斜体,半透明) |
| AppBar | compact,右侧:QR 图标 + 编辑铅笔 |
渐变色方案(_ProfileHeroCard._gradients[uid.abs() % 8]):
0: [#4776E6, #8E54E9] 1: [#11998E, #38EF7D]
2: [#FC466B, #3F5EFB] 3: [#F7971E, #FFD200]
4: [#56CCF2, #2F80ED] 5: [#EB3349, #F45C43]
6: [#1FA2FF, #12D8FA] 7: [#9D50BB, #6E48AA]
7.2 彩色图标行与分组卡片 (#40)
_IconBox:36pt 圆角正方形(8pt)白色图标,iOS Settings 风格。
| 卡片组 | 菜单项 | 颜色 |
|---|---|---|
| 账户 | 我的钱包 | #FFAA5B |
| 账户安全 | #8A5CF6 | |
| 工具 | 收藏 | #FFAF45 |
| 最近呼叫 | #4CB050 | |
| 链接设备 | #5667FF | |
| 聊天文件夹 | #F2994A | |
| 偏好设置 | 通知和声音 | #FF8B5E |
| 隐私设置 | #0BB8A9 | |
| 黑名单 | #FF4B4B | |
| 语言 | #5667FF | |
| 主题 | #8A5CF6 | |
| 关于 | 用户协议 | gray |
| 隐私政策 | gray | |
| 版本号(静态) | gray,无 chevron |
7.3 SettingsState bio 字段 (#41)
SettingsState.bio: String(默认'')SettingsViewModel.loadProfile()赋值bio: profile.bio- 数据来源:
ProfileResponse.bio→GET /app/api/user/profile
8. 编辑资料 UI 设计(#49 / #50)
| 元素 | 规格 |
|---|---|
| 头像 | 88pt 圆形;无头像时 8 色渐变占位;右下 28pt 相机角标(primary色) |
| 上传进度 | CircularProgressIndicator 环绕头像,isUploadingAvatar=true 时显示 |
| 选图 Sheet | BottomSheet(从相册选取 / 拍照),16pt 圆角,拖动条 |
| 昵称字段 | Card 分组,x/50 右侧计数,maxLength:50 |
| 简介字段 | Card 分组,x/200 右侧计数,maxLines:null(自动展开),maxLength:200 |
| 保存按钮 | AppBar 右侧 TextButton;昵称为空或上传中时 disabled |
| 错误 Banner | errorContainer 色圆角卡片,⚠️ 图标 + 文本 |
9. 待完成事项
- #8 主题持久化:解开
ThemeModeNotifier.build()和setMode()中的 TODO - #10 黑名单 API:实现
FetchBlocklistUseCase+UnblockUseCase - #11 聊天文件夹:
ChatCategoryViewModel+account/storeAPI