data.dart 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  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. final tmpfile = await localFile('${item.cachePath}.tmp');
  79. if (await tmpfile.exists()) {
  80. await tmpfile.delete();
  81. _log.info('Successfully cleared cache tmp file for ${item.id}');
  82. }
  83. } catch (e) {
  84. _log.severe('Failed to clear cache for item ${item.id}, error: $e');
  85. }
  86. } else {
  87. _log.info('Work done for asset item ${item.id}. No network cache to clear.');
  88. }
  89. }
  90. // 获取当前的关卡index (基于已完成作品的数量)
  91. int get currentLevel => completedWorks.value.length;
  92. // 完成某个collection
  93. void collectionDone(ListItem item) {
  94. final newCollection = Work.fromListItem(item);
  95. final updatedCollections = [...completedCollections.value, newCollection];
  96. completedCollections.value = updatedCollections;
  97. _persistence.completedCollections = updatedCollections; // 存储更新后的列表
  98. }
  99. // 获取当前的合集index (基于已完成收藏的数量)
  100. int get currentCollectionIndex => completedCollections.value.length;
  101. CachedRequest? _latest;
  102. CachedRequest get latest {
  103. _latest ??= CachedRequest.fromUrl(
  104. ApiHelper.latestUri,
  105. transformFunction: (json) async {
  106. late List<ListItem> list;
  107. if (json['asset'] != null) {
  108. // from asset
  109. list = List<AssetItem>.from((json['data'] as Iterable).map((e) => AssetItem.fromJSON(e)));
  110. } else {
  111. // from remote
  112. list = List<RemoteItem>.from((json['data'] as Iterable).map((e) => RemoteItem.fromJSON(e)));
  113. }
  114. return list;
  115. },
  116. );
  117. return _latest!;
  118. }
  119. CachedRequest? _collection;
  120. CachedRequest get collection {
  121. _collection ??= CachedRequest.fromUrl(
  122. ApiHelper.collectionUri,
  123. transformFunction: (json) async {
  124. late List<ListItem> list;
  125. if (json['asset'] != null) {
  126. // from asset
  127. list = List<AssetItem>.from((json['data'] as Iterable).map((e) => AssetItem.fromJSON(e)));
  128. } else {
  129. // from remote
  130. list = List<RemoteItem>.from((json['data'] as Iterable).map((e) => RemoteItem.fromJSON(e)));
  131. }
  132. return list;
  133. },
  134. );
  135. return _collection!;
  136. }
  137. }
  138. // 内置图索引
  139. class BuiltinRegistry {
  140. static final Set<String> _builtinIds = {};
  141. // 在 App 启动(或 CachedRequest 加载内置数据时)调用
  142. static void init(List<dynamic> data) {
  143. _builtinIds.clear();
  144. for (var item in data) {
  145. if (item['_id'] != null) {
  146. _builtinIds.add(item['_id']);
  147. }
  148. }
  149. }
  150. static bool contains(String id) => _builtinIds.contains(id);
  151. }