Files
nj/app/lib/features/stickers/bloc/sticker_bloc.dart
iven 7e928ae1e1
Some checks failed
Main Merge / backend (push) Has been cancelled
Main Merge / frontend (push) Has been cancelled
fix(app): 修复 P2~P4 共 10 项前端问题
P2 必须修复:
- 教师布置主题 classId 从硬编码改为班级下拉选择器
- 班级日记墙使用服务端 classId 过滤替代前端过滤
- Profile 统计栏接入 JournalRepository 真实数据
- WeeklyPage 从全硬编码改为 JournalRepository 数据驱动

P3 建议改进:
- 提取 mood_utils.dart 公共函数,消除 4 处重复定义
- 贴纸库搜索框连接 StickerBloc 按名称过滤

P4 细节打磨:
- 家长页多孩子时显示 DropdownButton 选择器
- 搜索结果日记卡片点击跳转 /editor?id=
- MonthlyPage 照片数量从 JournalElement 统计
- calendar_page/mood_page/search_page 统一使用 moodToEmoji/moodToLabel
2026-06-02 20:21:51 +08:00

201 lines
5.1 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 贴纸 BLoC — 通过 API 加载贴纸包和贴纸数据
import 'package:flutter/material.dart';
import 'package:nuanji_app/data/remote/api_client.dart';
// ===== 模型 =====
/// 贴纸包
class StickerPack {
final String id;
final String name;
final String? description;
final String? coverImageUrl;
final int stickerCount;
final bool isFree;
final String? category;
const StickerPack({
required this.id,
required this.name,
this.description,
this.coverImageUrl,
this.stickerCount = 0,
this.isFree = true,
this.category,
});
/// 兼容旧 UI 的 emoji 封面(优先用 coverImageUrl否则用默认
String get displayCover => coverImageUrl ?? '🎨';
}
/// 单张贴纸
class Sticker {
final String id;
final String packId;
final String name;
final String imageUrl;
final String? category;
const Sticker({
required this.id,
required this.packId,
required this.name,
required this.imageUrl,
this.category,
});
}
// ===== State =====
/// 贴纸页面状态
class StickerState {
final List<StickerPack> packs;
final String selectedCategory;
final String searchQuery;
final bool isLoading;
final String? errorMessage;
const StickerState({
this.packs = const [],
this.selectedCategory = '全部',
this.searchQuery = '',
this.isLoading = false,
this.errorMessage,
});
/// 按分类 + 搜索关键词过滤贴纸包
List<StickerPack> get filteredPacks {
var result = selectedCategory == '全部'
? packs
: packs.where((p) => p.category == selectedCategory).toList();
if (searchQuery.isNotEmpty) {
final query = searchQuery.toLowerCase();
result = result.where((p) => p.name.toLowerCase().contains(query)).toList();
}
return result;
}
/// 所有分类(去重 + 加"全部"
List<String> get categories {
final cats = packs
.map((p) => p.category)
.whereType<String>()
.toSet()
.toList();
return ['全部', ...cats];
}
StickerState copyWith({
List<StickerPack>? packs,
String? selectedCategory,
String? searchQuery,
bool? isLoading,
String? errorMessage,
}) =>
StickerState(
packs: packs ?? this.packs,
selectedCategory: selectedCategory ?? this.selectedCategory,
searchQuery: searchQuery ?? this.searchQuery,
isLoading: isLoading ?? this.isLoading,
errorMessage: errorMessage,
);
}
// ===== BLoC =====
/// 贴纸 BLoC — ChangeNotifier 模式
class StickerBloc extends ChangeNotifier {
final ApiClient _api;
StickerState _state = const StickerState();
StickerState get state => _state;
StickerBloc({required ApiClient api}) : _api = api;
/// 加载贴纸包列表
void load() {
_state = _state.copyWith(isLoading: true);
notifyListeners();
_fetchPacks();
}
/// 选择分类
void selectCategory(String category) {
_state = _state.copyWith(selectedCategory: category);
notifyListeners();
}
/// 搜索贴纸包(按名称前端过滤)
void search(String query) {
_state = _state.copyWith(searchQuery: query);
notifyListeners();
}
/// 按分类加载贴纸包
void loadByCategory(String? category) {
_state = _state.copyWith(isLoading: true);
notifyListeners();
_fetchPacks(category: category);
}
Future<void> _fetchPacks({String? category}) async {
try {
final queryParams = category != null && category != '全部'
? {'category': category}
: null;
final response = await _api.get(
'/diary/sticker-packs',
queryParams: queryParams,
);
final body = response.data as Map<String, dynamic>;
final list = body['data'] as List? ?? [];
final packs = list.map((item) {
final m = item as Map<String, dynamic>;
return StickerPack(
id: m['id'] as String,
name: m['name'] as String,
description: m['description'] as String?,
coverImageUrl: m['cover_image_url'] as String?,
stickerCount: (m['sticker_count'] as num?)?.toInt() ?? 0,
isFree: m['is_free'] as bool? ?? true,
category: m['category'] as String?,
);
}).toList();
_state = _state.copyWith(isLoading: false, packs: packs);
} catch (e) {
_state = _state.copyWith(
isLoading: false,
errorMessage: '加载贴纸包失败',
);
}
notifyListeners();
}
/// 获取贴纸包内的贴纸列表
Future<List<Sticker>> fetchStickersInPack(String packId) async {
try {
final response = await _api.get('/diary/sticker-packs/$packId/stickers');
final body = response.data as Map<String, dynamic>;
final list = body['data'] as List? ?? [];
return list.map((item) {
final m = item as Map<String, dynamic>;
return Sticker(
id: m['id'] as String,
packId: m['pack_id'] as String,
name: m['name'] as String,
imageUrl: m['image_url'] as String,
category: m['category'] as String?,
);
}).toList();
} catch (e) {
return [];
}
}
}