Files
nj/app/lib/features/achievement/bloc/achievement_bloc.dart
iven 8331db63ba feat(app): 设置页 UI + Mood/成就/贴纸 BLoC 接入 API + B7 测试扩展
前端改动:
- 新建设置页面 (主题切换/关于/隐私政策/用户协议/儿童隐私保护)
- SettingsBloc 注册到 MultiRepositoryProvider 全局可访问
- MoodBloc 修复编译错误 + 接入 /diary/stats/mood API
- MoodPage 添加错误状态展示和重试按钮
- AchievementBloc + 页面改造接入 /diary/achievements API
- StickerBloc + 页面改造接入 /diary/sticker-packs API
- TemplateBloc + 页面改造接入 /diary/templates API
- ProfilePage 设置入口改为跳转 /settings
- 添加 /settings 路由

后端改动:
- 扩展 mood_stats_service 测试 (连续天数算法/心情计数/边界场景)
- 新增 class_service 测试 (班级码生成/唯一性/错误映射)
- 新增 achievement_service 测试 (DTO 结构/序列化/map 构建)
- 新增 sticker_service 测试 (DTO 序列化/错误处理)
- 扩展 dto.rs 测试 (achievement/mood_stats/sticker/template/notification)
- 清理 2 个 unused import warning

验证:
- cargo check 0 error 0 warning
- flutter analyze 0 error
2026-06-01 11:19:43 +08:00

109 lines
2.7 KiB
Dart

// 成就 BLoC — 通过 API 加载成就列表
import 'package:flutter/material.dart';
import 'package:nuanji_app/data/remote/api_client.dart';
// ===== 模型 =====
/// 成就数据
class Achievement {
final String id;
final String code;
final String name;
final String? description;
final String? icon;
final String category;
final bool isUnlocked;
final DateTime? unlockedAt;
const Achievement({
required this.id,
required this.code,
required this.name,
this.description,
this.icon,
required this.category,
this.isUnlocked = false,
this.unlockedAt,
});
}
// ===== State =====
/// 成就页面状态
class AchievementState {
final List<Achievement> achievements;
final bool isLoading;
final String? errorMessage;
const AchievementState({
this.achievements = const [],
this.isLoading = false,
this.errorMessage,
});
int get unlockedCount =>
achievements.where((a) => a.isUnlocked).length;
AchievementState copyWith({
List<Achievement>? achievements,
bool? isLoading,
String? errorMessage,
}) =>
AchievementState(
achievements: achievements ?? this.achievements,
isLoading: isLoading ?? this.isLoading,
errorMessage: errorMessage,
);
}
// ===== BLoC =====
/// 成就 BLoC — ChangeNotifier 模式
class AchievementBloc extends ChangeNotifier {
final ApiClient _api;
AchievementState _state = const AchievementState();
AchievementState get state => _state;
AchievementBloc({required ApiClient api}) : _api = api;
/// 加载成就列表
void load() {
_state = _state.copyWith(isLoading: true);
notifyListeners();
_fetchAchievements();
}
Future<void> _fetchAchievements() async {
try {
final response = await _api.get('/diary/achievements');
final body = response.data as Map<String, dynamic>;
final list = body['data'] as List? ?? [];
final achievements = list.map((item) {
final m = item as Map<String, dynamic>;
return Achievement(
id: m['id'] as String,
code: m['code'] as String,
name: m['name'] as String,
description: m['description'] as String?,
icon: m['icon'] as String?,
category: m['category'] as String,
isUnlocked: m['is_unlocked'] as bool? ?? false,
unlockedAt: m['unlocked_at'] != null
? DateTime.tryParse(m['unlocked_at'] as String)
: null,
);
}).toList();
_state = _state.copyWith(isLoading: false, achievements: achievements);
} catch (e) {
_state = _state.copyWith(
isLoading: false,
errorMessage: '加载成就列表失败',
);
}
notifyListeners();
}
}