AtomGit Flutter鸿蒙客户端:设置页面

纯展示型页面的设计

设置页面在整个应用中是一个独特的页面类型------它不需要自己的状态管理,不需要异步加载数据,不需要处理错误状态。它的全部数据来自已有的 Provider 和服务实例,进入页面时所有信息已经可用。

这种"纯消费"模式是良好架构的自然结果------不是每个页面都需要自己的 Provider。当页面只需要展示已有信息时,直接从现有的 Provider 读取是最简化的做法。

dart 复制代码
class SettingsScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final authProvider = context.watch<AuthProvider>();
    final apiClient = context.read<AtomGitApiClient>();
    final isLoggedIn = authProvider.isLoggedIn;

    return Scaffold(
      appBar: AppBar(title: const Text('设置')),
      body: ListView(
        children: [
          _SectionHeader(title: '账户'),
          _buildAccountSection(context, isLoggedIn, authProvider),
          const Divider(),
          _SectionHeader(title: 'API 信息'),
          _buildApiInfoSection(apiClient),
          const Divider(),
          _SectionHeader(title: '关于'),
          _buildAboutSection(context),
        ],
      ),
    );
  }
}

页面结构:AppBar + ListView 包含三个分区,每个分区有标题和内容。ListView 而非 Column 的选择是考虑内容可能超出屏幕时的滚动体验。

context.watch vs context.read

两个方法在设置页同时使用,体现了 Provider 消费模式的核心区别:

  • context.watch<AuthProvider>():建立订阅。AuthProvider 的 notifyListeners() 会触发 SettingsScreen 重建。登录/登出时 UI 自动更新("已登录"变"未登录",按钮从"退出登录"变"登录")。
  • context.read<AtomGitApiClient>():一次性读取。ApiClient 是服务对象,不变,不需要订阅。

如果对 AuthProvider 也使用 read,用户在设置页点击"退出登录"后 UI 不会更新------虽然 isLoggedIn 已经变为 false,但由于没有订阅,Widget 不会重建,界面上仍然显示"已登录"。

账户区域

账户区域展示登录状态和操作入口:

dart 复制代码
Widget _buildAccountSection(
    BuildContext context,
    bool isLoggedIn,
    AuthProvider auth,
) {
  return ListTile(
    leading: Icon(
      isLoggedIn ? Icons.check_circle : Icons.account_circle,
      color: isLoggedIn ? Colors.green : null,
    ),
    title: Text('登录状态'),
    subtitle: Text(isLoggedIn ? '已登录' : '未登录'),
    trailing: isLoggedIn
        ? TextButton(
            onPressed: () => _showLogoutDialog(context, auth),
            child: const Text('退出登录'),
          )
        : TextButton(
            onPressed: () =>
                Navigator.pushNamed(context, '/login'),
            child: const Text('登录'),
          ),
  );
}

UI 细节:

  • 图标根据状态切换:已登录显示绿色 check_circle,未登录显示默认色 account_circle
  • 副标题直接显示"已登录"/"未登录"文字
  • trailing 位置的按钮也根据状态切换文本和功能

退出确认对话框

退出是破坏性操作。直接退出会清除 Token、触发全应用 UI 刷新。所以退出前展示确认对话框是必要的:

dart 复制代码
void _showLogoutDialog(BuildContext context, AuthProvider auth) {
  showDialog(
    context: context,
    builder: (ctx) => AlertDialog(
      title: const Text('退出登录'),
      content: const Text('确定要退出登录吗?'),
      actions: [
        TextButton(
          onPressed: () => Navigator.pop(ctx),
          child: const Text('取消'),
        ),
        TextButton(
          onPressed: () {
            auth.logout();
            Navigator.pop(ctx);
          },
          child: const Text('确定'),
        ),
      ],
    ),
  );
}

showDialog 返回一个 Future,resolve 值为 Navigator.pop(ctx) 传入的参数。当前实现不关心对话框结果(不 await),因为退出登录后整个应用状态会刷新。

对话框的 context(ctx)是对话框自己的 context,与 SettingsScreen 的 context 不同。Navigator.pop(ctx) 关闭的是对话框,不是设置页面。

退出登录的数据流

退出操作在 AlertDialog 的"确定"按钮中触发:

复制代码
auth.logout()
  → _accessToken = null(清除内存中的 Token)
  → _isLoggedIn = false(更新登录标志)
  → _apiClient.setAccessToken(null)(清除 API 客户端认证)
  → LocalStorage.instance.delete('access_token')(删除本地持久化文件)
  → notifyListeners()(通知所有监听者)

这个调用链的完整性至关重要。遗漏任何一步都会导致状态不一致:

  • 遗漏 _apiClient.setAccessToken(null):UI 显示未登录,但 API 请求仍携带旧 Token(造成混淆)
  • 遗漏 LocalStorage.delete:下次启动时 tryRestoreSession 会恢复已失效的 Token
  • 遗漏 notifyListeners():UI 不更新,用户看到"已登录"但实际已登出

API 信息区域

这个区域为开发者和高级用户提供 API 相关的诊断信息:

dart 复制代码
Widget _buildApiInfoSection(AtomGitApiClient apiClient) {
  return Column(children: [
    ListTile(
      leading: const Icon(Icons.link),
      title: const Text('API 地址'),
      subtitle: const Text(ApiConstants.baseUrl),
    ),
    ListTile(
      leading: const Icon(Icons.info_outline),
      title: const Text('API 版本'),
      subtitle: const Text('2023-02-21'),
    ),
    ListTile(
      leading: const Icon(Icons.speed),
      title: const Text('频率限制'),
      subtitle: Text(
        '${apiClient.rateLimitRemaining} / '
        '${ApiConstants.rateLimitAuthenticated} 次/小时',
      ),
    ),
  ]);
}

频率限制信息的展示是诊断性的。AtomGit API 对认证用户提供每小时 5000 次调用限制。用户可以通过这里看到当前剩余配额,了解是否接近限流。

apiClient.rateLimitRemaining 的值在每次 API 调用后自动更新(从响应 Header x-ratelimit-remaining 读取)。如果这个数字在快速减少,说明应用可能在短时间内发起了大量请求。

关于区域

dart 复制代码
Widget _buildAboutSection(BuildContext context) {
  return Column(children: [
    ListTile(
      leading: const Icon(Icons.apps),
      title: const Text('应用名称'),
      subtitle: const Text('AtomGit'),
    ),
    ListTile(
      leading: const Icon(Icons.build),
      title: const Text('技术栈'),
      subtitle: const Text('Flutter + HarmonyOS'),
    ),
    ListTile(
      leading: const Icon(Icons.info_outline),
      title: const Text('版本'),
      subtitle: const Text('1.0.0'),
    ),
  ]);
}

关于区域的信息目前是硬编码的。对于生产应用,版本号可以从 pubspec.yaml 或环境变量动态获取。

分区标题组件

dart 复制代码
class _SectionHeader extends StatelessWidget {
  final String title;

  const _SectionHeader({required this.title});

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.fromLTRB(16, 24, 16, 8),
      child: Text(
        title,
        style: Theme.of(context).textTheme.titleSmall?.copyWith(
              color: Theme.of(context).colorScheme.primary,
            ),
      ),
    );
  }
}

一个简单的标题组件,负责统一的样式管理。被多处复用(账户、API 信息、关于),避免在每个区域重复写 Padding + TextStyle。

EdgeInsets.fromLTRB(16, 24, 16, 8) 各方向的设计含义:

  • 16px 左:与 ListTile 的标准左边距对齐
  • 24px 上:与上一个区域拉开距离,形成视觉分组
  • 16px 右:对称对齐
  • 8px 下:标题与内容之间的紧凑间距

为什么不需要自己的 Provider

设置页面的特点决定了它不需要独立的状态管理层:

  1. 数据均来自现有源 。登录状态来自 AuthProvider(全局),API 信息来自 AtomGitApiClient 实例(全局),版本和名称来自常量。

  2. 无异步操作需要追踪。设置页没有"加载中"、"加载失败"、"重试"等异步状态。所有信息在进入页面时即可展示。

  3. 数据变化来自外部 。当用户登录/登出时,AuthProvidernotifyListeners 自动触发设置页重建,不需要设置页自己管理刷新。

  4. 无分页/增量加载 。设置页的数据量固定,不需要 hasMorepage 等分页状态。

这些特点让设置页成为 Provider 架构中的"叶子消费者"------它只消费状态,不生产或管理状态。这种模式的代码量最少,也最容易理解。

退出登录的连锁反应

设置页面只是退出操作的触发器,实际的状态清理和 UI 更新由 AuthProvider.logout() 驱动:

复制代码
AuthProvider.logout()
  → _accessToken = null
  → notifyListeners()

所有监听了 AuthProvider 的 Widget 重建:

MainShell
  └── Tab 状态不变(只是容器)

HomeTab(context.watch<AuthProvider>)
  → isLoggedIn = false
  → 从"我的仓库+热门仓库"切换到"欢迎页+搜索框"

ExploreTab
  → isLoggedIn = false
  → 显示登录引导

NotificationsTab(context.watch<AuthProvider>)
  → isLoggedIn = false
  → 从"占位"切换到"登录引导"

ProfileTab(context.watch<AuthProvider>)
  → isLoggedIn = false
  → didChangeDependencies 检测到登录状态变化
  → dispose UserProvider
  → 切换到"登录引导"

SettingsScreen(context.watch<AuthProvider>)
  → isLoggedIn = false
  → "已登录"变"未登录"
  → 按钮从"退出登录"变"登录"

整个链条是自动的------设置页不需要知道哪些页面需要更新,Provider 的广播机制自动完成状态传播。

扩展:添加设置项

当前设置页面是功能最少的页面,但架构预留了扩展空间。添加新的设置项只需在 ListView 中插入新的 ListTile:

dart 复制代码
// 添加主题切换(示例)
_SectionHeader(title: '外观'),
ListTile(
  leading: const Icon(Icons.palette),
  title: const Text('主题'),
  subtitle: const Text('跟随系统'),
  trailing: const Icon(Icons.chevron_right),
  onTap: () {
    // 打开主题选择器
  },
),
const Divider(),

// 添加缓存管理(示例)
_SectionHeader(title: '存储'),
ListTile(
  leading: const Icon(Icons.storage),
  title: const Text('清除缓存'),
  subtitle: Text('当前缓存: $_cacheSize'),
  onTap: () => _clearCache(),
),

如果设置项数据需要持久化(如主题偏好),可以借助 LocalStorage:

dart 复制代码
// 读取偏好
final theme = await LocalStorage.instance.read<String>('pref_theme');

// 保存偏好
await LocalStorage.instance.write('pref_theme', 'dark');

设置页在导航体系中的位置

设置页面是全屏路由(/settings),覆盖底部 Tab 栏。这与大多数应用的设计一致------设置是独立页面,不是 Tab 的一部分。用户在设置中完成操作后通过返回按钮(或 AppBar 的 back 箭头)回到之前的页面。

相关推荐
FrameNotWork1 小时前
HarmonyOS6.1 AI 模型管理架构设计与最佳实践
人工智能·harmonyos
wordbaby1 小时前
rn-cross-calendar:一个兼容 React 18/19、RN/RNOH 的跨平台日历组件
前端·react native·harmonyos
●VON2 小时前
AtomGit Flutter鸿蒙客户端:用户资料
flutter·华为·架构·跨平台·harmonyos·鸿蒙
悟空瞎说2 小时前
Flutter 三大主流本地存储全解:SharedPreferences、Hive、SQLite 实战指南
flutter
悟空瞎说2 小时前
Flutter Isolate 与 compute 全方位实战指南:后台任务优化,保障 UI 60 帧流畅
flutter
风华圆舞3 小时前
Stage 模型下 Flutter 鸿蒙壳工程怎么理解
flutter·华为·harmonyos
●VON3 小时前
AtomGit Flutter鸿蒙客户端:数据模型
android·服务器·安全·flutter·harmonyos·鸿蒙
祭曦念3 小时前
鸿蒙原生ArkTS布局之RowStart垂直对齐详解
华为·harmonyos
●VON3 小时前
AtomGit Flutter鸿蒙客户端:收藏仓库
flutter·架构·跨平台·harmonyos·鸿蒙