data.dart 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. import 'package:flutter/material.dart';
  2. import 'package:puzzleweave/models/api_helper.dart';
  3. import 'package:puzzleweave/models/cached_request.dart';
  4. import 'package:puzzleweave/models/items.dart';
  5. import 'package:puzzleweave/persistence/persistence.dart';
  6. import 'package:logging/logging.dart';
  7. import 'package:puzzleweave/utils/utils.dart';
  8. final Logger _log = Logger('data.dart');
  9. class Data {
  10. final Persistence _persistence;
  11. ValueNotifier<List<Work>> completedWorks = ValueNotifier([]); // 已完成的图
  12. ValueNotifier<List<Work>> completedCollections = ValueNotifier([]); // 已完成的收藏图
  13. Data({required Persistence persistence}) : _persistence = persistence;
  14. Future<void> loadDataFromPersistence() async {
  15. // for test 为了测试合集完成动画
  16. // var works = _persistence.completedWorks;
  17. // works = works.sublist(0, works.length - 1);
  18. // _persistence.completedWorks = works;
  19. // _persistence.completedCollections = [];
  20. // 1. 先初始化内置索引(独立、确定、不依赖缓存判断)
  21. await _initBuiltinRegistry();
  22. completedWorks.value = _persistence.completedWorks;
  23. completedCollections.value = _persistence.completedCollections;
  24. }
  25. /// 独立初始化内置图索引,确保无论网络/磁盘缓存如何,Asset 里的图都能被识别
  26. Future<void> _initBuiltinRegistry() async {
  27. try {
  28. _log.info('Initializing BuiltinRegistry from Asset...');
  29. // 直接读取资源文件,不走 CachedRequest 逻辑
  30. final latestData = await loadJSONFromAsset('assets/builtin/latest.json');
  31. if (latestData['data'] != null) {
  32. BuiltinRegistry.init(latestData['data']);
  33. _log.info('BuiltinRegistry initialized with ${latestData['total']} items.');
  34. }
  35. // 如果 collection.json 也有内置 ID 需要保护,也可以在这里 init
  36. // final collectionData = await loadJSONFromAsset('assets/builtin/collection.json');
  37. // BuiltinRegistry.init(collectionData['data']);
  38. } catch (e) {
  39. _log.severe('Failed to initialize BuiltinRegistry: $e');
  40. }
  41. }
  42. // 完成某个作品调用此接口记录到存储中
  43. // !!! 改造点:接受 ListItem 和可选的耗时
  44. void workDone(ListItem item, {Duration? timeSpent}) {
  45. final newWork = Work.fromListItem(item, timeSpent: timeSpent);
  46. // 1. 记录作品完成
  47. final updatedWorks = [...completedWorks.value, newWork];
  48. completedWorks.value = updatedWorks;
  49. _persistence.completedWorks = updatedWorks; // 存储更新后的列表
  50. // 2. 核心新增:清除已完成作品的缓存
  51. _clearCompletedItemCache(item);
  52. }
  53. /// 清除已完成作品的缓存文件。
  54. void _clearCompletedItemCache(ListItem item) async {
  55. // 删除进度状态json
  56. try {
  57. final file = await localFile(item.jsonPath);
  58. if (await file.exists()) {
  59. await file.delete();
  60. _log.info('Successfully cleared json cache file for ${item.id}');
  61. } else {
  62. _log.warning('json cache file not found for ${item.id} at path: ${item.jsonPath}');
  63. }
  64. } catch (e) {
  65. _log.severe('Failed to clear json cache for item ${item.id}, error: $e');
  66. }
  67. // 只有 RemoteItem 才会有需要清理的下载缓存
  68. if (item is RemoteItem) {
  69. _log.info('Work done for remote item ${item.id}. Attempting to clear cache at path: ${item.cachePath}');
  70. try {
  71. final file = await localFile(item.cachePath);
  72. if (await file.exists()) {
  73. await file.delete();
  74. _log.info('Successfully cleared cache file for ${item.id}');
  75. } else {
  76. _log.warning('Cache file not found for ${item.id} at path: ${item.cachePath}');
  77. }
  78. } catch (e) {
  79. _log.severe('Failed to clear cache for item ${item.id}, error: $e');
  80. }
  81. } else {
  82. _log.info('Work done for asset item ${item.id}. No network cache to clear.');
  83. }
  84. }
  85. // 获取当前的关卡index (基于已完成作品的数量)
  86. int get currentLevel => completedWorks.value.length;
  87. // 完成某个collection
  88. void collectionDone(ListItem item) {
  89. final newCollection = Work.fromListItem(item);
  90. final updatedCollections = [...completedCollections.value, newCollection];
  91. completedCollections.value = updatedCollections;
  92. _persistence.completedCollections = updatedCollections; // 存储更新后的列表
  93. }
  94. // 获取当前的合集index (基于已完成收藏的数量)
  95. int get currentCollectionIndex => completedCollections.value.length;
  96. CachedRequest? _latest;
  97. CachedRequest get latest {
  98. _latest ??= CachedRequest.fromUrl(
  99. ApiHelper.latestUri,
  100. transformFunction: (json) async {
  101. late List<ListItem> list;
  102. if (json['asset'] != null) {
  103. // from asset
  104. list = List<AssetItem>.from((json['data'] as Iterable).map((e) => AssetItem.fromJSON(e)));
  105. } else {
  106. // from remote
  107. list = List<RemoteItem>.from((json['data'] as Iterable).map((e) => RemoteItem.fromJSON(e)));
  108. }
  109. return list;
  110. },
  111. );
  112. return _latest!;
  113. }
  114. CachedRequest? _collection;
  115. CachedRequest get collection {
  116. _collection ??= CachedRequest.fromUrl(
  117. ApiHelper.collectionUri,
  118. transformFunction: (json) async {
  119. late List<ListItem> list;
  120. if (json['asset'] != null) {
  121. // from asset
  122. list = List<AssetItem>.from((json['data'] as Iterable).map((e) => AssetItem.fromJSON(e)));
  123. } else {
  124. // from remote
  125. list = List<RemoteItem>.from((json['data'] as Iterable).map((e) => RemoteItem.fromJSON(e)));
  126. }
  127. return list;
  128. },
  129. );
  130. return _collection!;
  131. }
  132. }
  133. // 内置图索引
  134. class BuiltinRegistry {
  135. static final Set<String> _builtinIds = {};
  136. // 在 App 启动(或 CachedRequest 加载内置数据时)调用
  137. static void init(List<dynamic> data) {
  138. _builtinIds.clear();
  139. for (var item in data) {
  140. if (item['_id'] != null) {
  141. _builtinIds.add(item['_id']);
  142. }
  143. }
  144. }
  145. static bool contains(String id) => _builtinIds.contains(id);
  146. }