一、页面核心定位 & 功能总览

✅ 页面核心

本页面 ProfilePage 是项目的个人中心页面,也是用户数据的聚合展示页面,核心承载「个人信息 + 收藏仓库 + 关注用户」三大核心模块,所有数据都是当前登录用户的专属私有数据

✅ 核心功能清单

  1. 加载并展示「当前登录用户」的完整个人信息(用户名、头像、简介、仓库数 / 粉丝数 / 关注数统计);
  2. 展示本地收藏仓库、关注用户、个人仓库的数量统计卡片;
  3. 点击统计卡片可跳转至「收藏仓库列表 / 关注用户列表」,加载并展示完整列表数据;
  4. 列表页面支持返回个人中心主页、空数据友好提示、加载状态展示;
  5. 支持页面数据一键刷新,重新加载用户信息和统计数据;
  6. 所有收藏 / 关注数据基于本地持久化存储,个人信息基于接口请求获取

用户打开ProfilePage
      ↓
  加载当前用户信息(API)
      ↓
  加载统计数据(并行):
  ├─ 从SharedPreferences获取收藏仓库数量
  ├─ 从SharedPreferences获取关注用户数量
  ├─ 从API获取用户仓库数量
      ↓
  显示摘要页面
      ↓
  用户点击卡片:
  ├─ 点击"收藏仓库" → 从SharedPreferences获取仓库名称列表 → 通过API补充详细信息 → 显示列表
  ├─ 点击"关注用户" → 从SharedPreferences获取用户名列表 → 通过API补充详细信息 → 显示列表
  ├─ 点击"我的仓库" 
      ↓
  用户点击返回按钮 → 回到摘要页面

二、数据来源

2.1 「我的」数据分类 & 对应获取方式

✔ 第一类:当前登录用户的个人核心信息 - 来源:【服务端 API 接口请求】
  • 包含数据:用户名 (login)、头像 (avatarUrl)、昵称 (name)、个人简介 (bio)、公开仓库数 (publicRepos)等;
  • 获取方式:调用api_client.dart中的 getCurrentUser() 方法;
  • 核心原理:通过配置的personalToken个人令牌,请求$baseUrl/user接口,接口返回当前登录用户的完整信息,通过GitCodeUser.fromJson()解析为实体类对象,供页面展示。
✔ 第二类:我的收藏仓库数据 - 来源:【本地持久化存储 + 服务端补全详情】
  • 包含数据:所有手动收藏的仓库列表;
  • 获取方式:
    1. 调用PersistenceStorage.getStarredRepositories(),从本地鸿蒙版 shared_preferences 中,获取「收藏的仓库全名列表」(仓库唯一标识:owner/repoName);
    2. 拿到仓库全名后,分批调用接口searchRepositories,根据仓库全名获取仓库的完整详情数据,组装成仓库列表;
  • 核心原理:APP 重启后数据不丢失,详情数据需要从服务端获取,保证数据最新。
✔ 第三类:我的关注用户数据 - 来源:【本地持久化存储 + 服务端补全详情】
  • 包含数据:所有手动关注的用户列表;
  • 获取方式:
    1. 调用PersistenceStorage.getFollowedUsers(),从本地鸿蒙版 shared_preferences 中,获取「关注的用户名列表」(用户唯一标识:login);
    2. 拿到用户名后,分批调用接口getUserDetails,根据用户名获取用户的完整详情数据,组装成用户列表;
  • 核心原理:和收藏仓库逻辑完全一致,详情数据从服务端获取。
✔ 第四类:我的个人仓库数据 - 来源:【服务端 API 接口请求】
  • 包含数据:当前登录用户创建的所有公开仓库;
  • 获取方式:调用api_client.dart中的getUserRepositories方法,传入当前用户的login,请求接口获取仓库列表;

关于本项目中数据调用形式的差异化设计,核心原因在于 API 接口的功能限制:目前 AtomGit 开放 API 暂未提供直接关联「当前登录用户关注关系、仓库收藏状态」的查询接口,无法通过单次请求获取用户的关注列表、收藏列表等关联数据。
因此,项目采用 本地+ 服务端 的混合方案实现相关功能:本地通过鸿蒙适配版 shared_preferences 存储关注的用户名、收藏的仓库标识(fullName),确保 APP 重启后数据不丢失;后续需展示完整详情时,再通过存储的标识分批请求服务端接口,补全仓库 / 用户的详细信息。
若后续 AtomGit 开放 API 支持直接查询个人关注列表、收藏列表的完整数据(无需本地存储标识),可无缝切换为纯服务端数据调用方案,以 API 返回的真实数据为准,现有页面逻辑无需大幅重构,仅需调整数据加载的源头即可。

2.2 代码中 getCurrentUser() 接口核心解析(用户信息的核心来源)

下方的这个方法,是获取当前登录用户信息的核心接口,也是整个页面的「数据入口」:

Future<GitCodeUser> getCurrentUser() async {
  final uri = Uri.parse('$baseUrl/user').replace(
    queryParameters: {
      'access_token': ApiConfig.personalToken, // 核心:个人令牌,身份认证凭证
    },
  );
  try {
    final response = await http.get(uri).timeout(const Duration(seconds: 10));
    if (response.statusCode == 200) {
      final jsonData = jsonDecode(response.body);
      return GitCodeUser.fromJson(jsonData); // 解析为用户实体类
    } else if (response.statusCode == 401) {
      throw Exception('Token无效或已过期,请检查配置'); // Token认证失败
    } else {
      throw Exception('获取用户信息失败,状态码: ${response.statusCode}');
    }
  } catch (e) {
    rethrow;
  }
}

核心要点:

  1. 身份认证:通过personalToken令牌请求接口,接口会根据令牌识别「当前登录的用户」,返回该用户的专属数据,这是「我的数据」的核心认证方式;
  2. 数据解析:接口返回的 JSON 数据,通过GitCodeUser.fromJson()方法解析为实体类,页面中直接调用实体类的属性(如_currentUser!.login),类型安全,不会出错;
  3. 异常处理:包含超时、网络错误、Token 失效等异常捕获,保证页面不会崩溃。

三、所有核心功能 分模块详细解析(按代码执行顺序,逐功能拆解)

3.1 初始化相关(页面启动的基础准备)

✔ 全局变量定义 & 枚举类作用
// 内容类型枚举:控制页面展示不同的内容
enum ContentType {
  summary,       // 默认:个人信息摘要主页
  starredRepos,  // 收藏仓库列表页
  followedUsers, // 关注用户列表页
}

// 全局变量
final GitCodeApiClient _apiClient = GitCodeApiClient(); // 网络请求实例
GitCodeUser? _currentUser; // 当前登录用户信息
bool _loadingUser = true;   // 用户信息加载状态
String? _userError;         // 用户信息加载错误信息
int _starredCount = 0;      // 收藏仓库数量
int _followedCount = 0;     // 关注用户数量
int _repoCount = 0;         // 个人仓库数量
ContentType _currentContentType = ContentType.summary; // 默认展示摘要页
List<GitCodeRepository> _starredRepos = []; // 收藏仓库列表
List<GitCodeUser> _followedUsers = [];       // 关注用户列表
bool _loadingStarredRepos = false; // 收藏仓库加载状态
bool _loadingFollowedUsers = false;// 关注用户加载状态

核心作用:

  • ContentType枚举是页面内容切换的核心开关:通过修改枚举值,页面会根据枚举值渲染不同的内容(主页 / 收藏列表 / 关注列表);
  • 所有状态变量私有化,通过setState()更新状态,驱动页面刷新,符合 Flutter 的「数据驱动 UI」思想;
  • 所有加载状态独立管理,避免页面出现「全局加载」的卡顿体验。

3.2 核心数据加载方法(页面的核心逻辑,所有数据的源头)

✔ 初始化加载所有数据 _loadInitialData()
Future<void> _loadInitialData() async {
  await _loadCurrentUser(); // 先加载用户信息
  await _loadAllStats();    // 再加载统计数据
}
✔ 加载当前用户信息 _loadCurrentUser()
Future<void> _loadCurrentUser() async {
  setState(() {_loadingUser = true;_userError = null;});
  try {
    final user = await _apiClient.getCurrentUser(); // 调用核心接口
    setState(() {_currentUser = user;_loadingUser = false;});
  } catch (e) {
    setState(() {_userError = e.toString();_loadingUser = false;});
  }
}
✔ 加载所有统计数据 _loadAllStats()
Future<void> _loadAllStats() async {
  try {
    await Future.wait([ // 并行加载,提升加载效率
      _loadStarredRepositoriesCount(),
      _loadFollowedUsersCount(),
      _loadUserRepositoriesCount(),
    ]);
  } catch (e) {print('加载统计数据失败: $e');}
}
✔ 三个统计数据加载方法(收藏数 / 关注数 / 仓库数)
// 收藏仓库数量:本地存储中取列表长度
Future<void> _loadStarredRepositoriesCount() async {
  final starred = await PersistenceStorage.getStarredRepositories();
  setState(() {_starredCount = starred.length;});
}

// 关注用户数量:本地存储中取列表长度
Future<void> _loadFollowedUsersCount() async {
  final followed = await PersistenceStorage.getFollowedUsers();
  setState(() {_followedCount = followed.length;});
}

// 个人仓库数量:从用户信息中直接获取(接口返回的字段)
Future<void> _loadUserRepositoriesCount() async {
  if (_currentUser == null) return;
  setState(() {_repoCount = _currentUser?.publicRepos ?? 0;});
}

3.3 收藏仓库 / 关注用户 列表加载核心功能(核心业务逻辑)

Future<void> _loadStarredRepositories() async {
  setState(() {
    _currentContentType = ContentType.starredRepos; // 切换页面内容为收藏列表
    _loadingStarredRepos = true;
    _starredRepos.clear();
  });

  try {
    final repoFullNames = await PersistenceStorage.getStarredRepositories(); // 本地取收藏标识
    if (repoFullNames.isEmpty) {setState(() {_loadingStarredRepos = false;});return;}

    final List<GitCodeRepository> loadedRepos = [];
    const concurrentLimit = 3; // 分批并发请求,避免接口限流
    for (var i = 0; i < repoFullNames.length; i += concurrentLimit) {
      final batch = repoFullNames.sublist(i, i + concurrentLimit > repoFullNames.length ? repoFullNames.length : i + concurrentLimit);
      final batchResults = await Future.wait(batch.map((fullName) => _fetchRepositoryByFullName(fullName)));
      // 过滤空数据+去重,保证列表数据干净
      for (final result in batchResults) {
        if (result != null && !loadedRepos.any((repo) => repo.fullName == result.fullName)) {
          loadedRepos.add(result);
        }
      }
    }

    setState(() {_starredRepos = loadedRepos;_loadingStarredRepos = false;});
  } catch (e) {
    print('加载收藏仓库失败: $e');
    setState(() {_loadingStarredRepos = false;});
  }
}

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐