Commit 8fedd40b10e4d2e387246fdfd0a9fa37717124e2

Authored by 权海
1 parent 4ba6cb4d

feat(ui):优化原数据写入

@@ -15,6 +15,7 @@ import '../../../../core/services/raw_data_service/health_raw_models.dart'; @@ -15,6 +15,7 @@ import '../../../../core/services/raw_data_service/health_raw_models.dart';
15 import '../../../../core/services/raw_data_service/platform_ios/apple_health_raw_local_notification.dart'; 15 import '../../../../core/services/raw_data_service/platform_ios/apple_health_raw_local_notification.dart';
16 import '../../../../core/services/raw_data_service/platform_ohos/ohos_harmony_health_raw_data_sync_service_factory.dart'; 16 import '../../../../core/services/raw_data_service/platform_ohos/ohos_harmony_health_raw_data_sync_service_factory.dart';
17 import '../../../../core/services/raw_data_service/platform_ohos/ohos_health_raw_data_sync_service.dart'; 17 import '../../../../core/services/raw_data_service/platform_ohos/ohos_health_raw_data_sync_service.dart';
  18 +import '../../../../core/services/raw_data_service/platform_ohos/ohos_sqlite_write_benchmark.dart';
18 import '../../../../data/local/user_preferences_storage.dart'; 19 import '../../../../data/local/user_preferences_storage.dart';
19 import '../../../../l10n/l10n_extensions.dart'; 20 import '../../../../l10n/l10n_extensions.dart';
20 import '../../../../core/platform/pigeon_api_facade.dart'; 21 import '../../../../core/platform/pigeon_api_facade.dart';
@@ -38,6 +39,7 @@ enum DeveloperOptionsAction { @@ -38,6 +39,7 @@ enum DeveloperOptionsAction {
38 shareHealthPipelineRecord, 39 shareHealthPipelineRecord,
39 sendTestHrvLocalNotification, 40 sendTestHrvLocalNotification,
40 pullOhosRawData, 41 pullOhosRawData,
  42 + runOhosSqliteWriteBenchmark,
41 clearHealthRawDataDatabaseAndUploadLog, 43 clearHealthRawDataDatabaseAndUploadLog,
42 globalEnv, 44 globalEnv,
43 } 45 }
@@ -95,6 +97,10 @@ class DeveloperOptionsController extends GetxController { @@ -95,6 +97,10 @@ class DeveloperOptionsController extends GetxController {
95 action: DeveloperOptionsAction.pullOhosRawData, 97 action: DeveloperOptionsAction.pullOhosRawData,
96 ), 98 ),
97 DeveloperOptionsItem( 99 DeveloperOptionsItem(
  100 + title: '测试 OHOS SQLite 写入性能',
  101 + action: DeveloperOptionsAction.runOhosSqliteWriteBenchmark,
  102 + ),
  103 + DeveloperOptionsItem(
98 title: '清除本地数据库、上传日志', 104 title: '清除本地数据库、上传日志',
99 action: DeveloperOptionsAction.clearHealthRawDataDatabaseAndUploadLog, 105 action: DeveloperOptionsAction.clearHealthRawDataDatabaseAndUploadLog,
100 ), 106 ),
@@ -122,6 +128,10 @@ class DeveloperOptionsController extends GetxController { @@ -122,6 +128,10 @@ class DeveloperOptionsController extends GetxController {
122 action: DeveloperOptionsAction.pullOhosRawData, 128 action: DeveloperOptionsAction.pullOhosRawData,
123 ), 129 ),
124 DeveloperOptionsItem( 130 DeveloperOptionsItem(
  131 + title: '测试 OHOS SQLite 写入性能',
  132 + action: DeveloperOptionsAction.runOhosSqliteWriteBenchmark,
  133 + ),
  134 + DeveloperOptionsItem(
125 title: '清除本地数据库、上传日志', 135 title: '清除本地数据库、上传日志',
126 action: DeveloperOptionsAction.clearHealthRawDataDatabaseAndUploadLog, 136 action: DeveloperOptionsAction.clearHealthRawDataDatabaseAndUploadLog,
127 ), 137 ),
@@ -152,6 +162,9 @@ class DeveloperOptionsController extends GetxController { @@ -152,6 +162,9 @@ class DeveloperOptionsController extends GetxController {
152 case DeveloperOptionsAction.pullOhosRawData: 162 case DeveloperOptionsAction.pullOhosRawData:
153 await pullOhosRawData(); 163 await pullOhosRawData();
154 break; 164 break;
  165 + case DeveloperOptionsAction.runOhosSqliteWriteBenchmark:
  166 + await runOhosSqliteWriteBenchmark();
  167 + break;
155 case DeveloperOptionsAction.clearHealthRawDataDatabaseAndUploadLog: 168 case DeveloperOptionsAction.clearHealthRawDataDatabaseAndUploadLog:
156 await clearHealthRawDataDatabaseAndUploadLog(); 169 await clearHealthRawDataDatabaseAndUploadLog();
157 break; 170 break;
@@ -357,6 +370,20 @@ class DeveloperOptionsController extends GetxController { @@ -357,6 +370,20 @@ class DeveloperOptionsController extends GetxController {
357 } 370 }
358 } 371 }
359 372
  373 + Future<void> runOhosSqliteWriteBenchmark() async {
  374 + try {
  375 + late final String report;
  376 + await LoadingService.instance.run(() async {
  377 + report = await const OhosSqliteWriteBenchmark().run();
  378 + });
  379 + await _platformHostApi.shareText(report);
  380 + Get.snackbar('OHOS SQLite 性能测试完成', report);
  381 + } catch (error, stackTrace) {
  382 + AppLogger.e('Run OHOS SQLite write benchmark failed', error, stackTrace);
  383 + Get.snackbar('OHOS SQLite 性能测试失败', error.toString());
  384 + }
  385 + }
  386 +
360 Future<void> clearHealthRawDataDatabaseAndUploadLog() async { 387 Future<void> clearHealthRawDataDatabaseAndUploadLog() async {
361 try { 388 try {
362 await _healthRawDataCoreService.clearLocalDatabaseAndUploadLog(); 389 await _healthRawDataCoreService.clearLocalDatabaseAndUploadLog();
@@ -22,15 +22,17 @@ import '../health_sleep_calculator.dart'; @@ -22,15 +22,17 @@ import '../health_sleep_calculator.dart';
22 import '../platform_ios/apple_health_raw_data_core_service.dart'; 22 import '../platform_ios/apple_health_raw_data_core_service.dart';
23 import '../platform_ios/apple_health_raw_local_notification.dart'; 23 import '../platform_ios/apple_health_raw_local_notification.dart';
24 import 'huawei_health_raw_stress_calculator.dart'; 24 import 'huawei_health_raw_stress_calculator.dart';
  25 +import 'ohos_health_raw_data_events.dart';
25 import 'ohos_health_raw_data_sync_service.dart'; 26 import 'ohos_health_raw_data_sync_service.dart';
26 import 'ohos_health_raw_result_upload_service.dart'; 27 import 'ohos_health_raw_result_upload_service.dart';
27 28
28 class OHOSHealthRawDataCoreService { 29 class OHOSHealthRawDataCoreService {
29 - static const int defaultLookbackDays = 183; 30 + static const int defaultLookbackMonths = 2;
30 static const int defaultReadChunkDays = 7; 31 static const int defaultReadChunkDays = 7;
31 static const String _calculateLogMarker = '[OHOS_HEALTH_CALCULATE]'; 32 static const String _calculateLogMarker = '[OHOS_HEALTH_CALCULATE]';
32 static const String _dailyStressLogMarker = '[OHOS_DAILY_STRESS_CALC]'; 33 static const String _dailyStressLogMarker = '[OHOS_DAILY_STRESS_CALC]';
33 static const String _sleepCalcLogMarker = '[OHOS_SLEEP_CALC]'; 34 static const String _sleepCalcLogMarker = '[OHOS_SLEEP_CALC]';
  35 + static const String _profileLogMarker = '[OHOS_HEALTH_RAW_PROFILE]';
34 36
35 OHOSHealthRawDataCoreService({ 37 OHOSHealthRawDataCoreService({
36 HealthRawDataSource? rawDataSource, 38 HealthRawDataSource? rawDataSource,
@@ -225,24 +227,37 @@ class OHOSHealthRawDataCoreService { @@ -225,24 +227,37 @@ class OHOSHealthRawDataCoreService {
225 required int readChunkDays, 227 required int readChunkDays,
226 required int? forceStartTime, 228 required int? forceStartTime,
227 }) async { 229 }) async {
  230 + final totalStopwatch = Stopwatch()..start();
  231 + _profileLog(
  232 + 'core_start forceStartTime=${forceStartTime ?? ''} '
  233 + 'endTime=${endTime ?? ''} readChunkDays=$readChunkDays',
  234 + );
228 if (readChunkDays <= 0) { 235 if (readChunkDays <= 0) {
229 throw ArgumentError.value(readChunkDays, 'readChunkDays'); 236 throw ArgumentError.value(readChunkDays, 'readChunkDays');
230 } 237 }
231 238
232 final userId = _userId; 239 final userId = _userId;
  240 + final ensureStopwatch = Stopwatch()..start();
233 await _localStore.ensureReadable(userId); 241 await _localStore.ensureReadable(userId);
  242 + _profileLog(
  243 + 'core_ensureReadable_finish userId=$userId '
  244 + 'elapsedMs=${ensureStopwatch.elapsedMilliseconds}',
  245 + );
234 final effectiveEndTime = 246 final effectiveEndTime =
235 endTime ?? DateTime.now().millisecondsSinceEpoch ~/ 1000; 247 endTime ?? DateTime.now().millisecondsSinceEpoch ~/ 1000;
236 - final earliestStartTime = DateTime.now()  
237 - .subtract(const Duration(days: defaultLookbackDays))  
238 - .millisecondsSinceEpoch ~/  
239 - 1000; 248 + final earliestStartTime = _twoMonthLookbackStart(effectiveEndTime);
240 final requestedStartTime = 249 final requestedStartTime =
241 math.max(forceStartTime ?? earliestStartTime, earliestStartTime); 250 math.max(forceStartTime ?? earliestStartTime, earliestStartTime);
242 if (effectiveEndTime < requestedStartTime) { 251 if (effectiveEndTime < requestedStartTime) {
243 throw ArgumentError.value(endTime, 'endTime'); 252 throw ArgumentError.value(endTime, 'endTime');
244 } 253 }
  254 + final authStopwatch = Stopwatch()..start();
245 final hasAuthorization = await _hasHealthReadAuthorizationSafely(); 255 final hasAuthorization = await _hasHealthReadAuthorizationSafely();
  256 + _profileLog(
  257 + 'core_authorization_finish userId=$userId '
  258 + 'hasAuthorization=$hasAuthorization '
  259 + 'elapsedMs=${authStopwatch.elapsedMilliseconds}',
  260 + );
246 final willSyncRawData = 261 final willSyncRawData =
247 hasAuthorization && _rawDataSource is OhosHealthRawDataSource; 262 hasAuthorization && _rawDataSource is OhosHealthRawDataSource;
248 final syncStartTime = willSyncRawData ? DateTime.now() : null; 263 final syncStartTime = willSyncRawData ? DateTime.now() : null;
@@ -260,23 +275,70 @@ class OHOSHealthRawDataCoreService { @@ -260,23 +275,70 @@ class OHOSHealthRawDataCoreService {
260 'endTime=$effectiveEndTime syncedRawStartTime=$syncedRawStartTime ' 275 'endTime=$effectiveEndTime syncedRawStartTime=$syncedRawStartTime '
261 'syncResults=$syncResults', 276 'syncResults=$syncResults',
262 ); 277 );
  278 + _profileLog(
  279 + 'core_sync_finish userId=$userId willSyncRawData=$willSyncRawData '
  280 + 'resultCount=${syncResults.length} storedCount='
  281 + '${syncResults.fold<int>(0, (sum, result) => sum + result.storedCount)} '
  282 + 'elapsedMs=${syncElapsed.inMilliseconds}',
  283 + );
263 284
264 final calculationStartTime = DateTime.now(); 285 final calculationStartTime = DateTime.now();
265 - final storedResult = await _calculateAndStoreSafely( 286 + _publishCalculationEvent(
  287 + type: OhosHealthRawDataPipelineEventType.calculationStarted,
  288 + flow: 'calculateAndStore',
266 userId: userId, 289 userId: userId,
267 - requestedStartTime: requestedStartTime,  
268 - effectiveEndTime: effectiveEndTime,  
269 - earliestStartTime: earliestStartTime,  
270 - syncedRawStartTime: syncedRawStartTime,  
271 - readChunkDays: readChunkDays, 290 + startTime: requestedStartTime,
  291 + endTime: effectiveEndTime,
272 ); 292 );
  293 + late final _OhosStoredCalculationResult storedResult;
  294 + try {
  295 + storedResult = await _calculateAndStoreSafely(
  296 + userId: userId,
  297 + requestedStartTime: requestedStartTime,
  298 + effectiveEndTime: effectiveEndTime,
  299 + earliestStartTime: earliestStartTime,
  300 + syncedRawStartTime: syncedRawStartTime,
  301 + readChunkDays: readChunkDays,
  302 + );
  303 + } catch (error) {
  304 + final calculationElapsed =
  305 + DateTime.now().difference(calculationStartTime);
  306 + _publishCalculationEvent(
  307 + type: OhosHealthRawDataPipelineEventType.calculationFailed,
  308 + flow: 'calculateAndStore',
  309 + userId: userId,
  310 + startTime: requestedStartTime,
  311 + endTime: effectiveEndTime,
  312 + elapsedMs: calculationElapsed.inMilliseconds,
  313 + error: error.toString(),
  314 + );
  315 + rethrow;
  316 + }
273 final calculationElapsed = DateTime.now().difference(calculationStartTime); 317 final calculationElapsed = DateTime.now().difference(calculationStartTime);
  318 + _profileLog(
  319 + 'core_calculateAndStore_finish userId=$userId '
  320 + 'hrv=${storedResult.result.hrvStressPoints.length} '
  321 + 'realtime=${storedResult.result.realtimeStressPoints.length} '
  322 + 'daily=${storedResult.result.dailyStressPoints.length} '
  323 + 'sleep=${storedResult.result.sleepResults.length} '
  324 + 'elapsedMs=${calculationElapsed.inMilliseconds}',
  325 + );
  326 + final uploadScheduleStopwatch = Stopwatch()..start();
274 _scheduleResultUpload(); 327 _scheduleResultUpload();
  328 + _profileLog(
  329 + 'core_uploadSchedule_finish userId=$userId '
  330 + 'elapsedMs=${uploadScheduleStopwatch.elapsedMilliseconds}',
  331 + );
  332 + final notificationStopwatch = Stopwatch()..start();
275 await _sendLocalNotificationsAfterCalculationSafely( 333 await _sendLocalNotificationsAfterCalculationSafely(
276 result: storedResult.result, 334 result: storedResult.result,
277 hasExistingHrv: storedResult.hasExistingHrv, 335 hasExistingHrv: storedResult.hasExistingHrv,
278 hasExistingSleep: storedResult.hasExistingSleep, 336 hasExistingSleep: storedResult.hasExistingSleep,
279 ); 337 );
  338 + _profileLog(
  339 + 'core_localNotification_finish userId=$userId '
  340 + 'elapsedMs=${notificationStopwatch.elapsedMilliseconds}',
  341 + );
280 if (_hasStoredRawData(syncResults) || 342 if (_hasStoredRawData(syncResults) ||
281 _hasCalculatedResult(storedResult.result)) { 343 _hasCalculatedResult(storedResult.result)) {
282 _showDebugTimingToast( 344 _showDebugTimingToast(
@@ -284,6 +346,23 @@ class OHOSHealthRawDataCoreService { @@ -284,6 +346,23 @@ class OHOSHealthRawDataCoreService {
284 calculationElapsed: calculationElapsed, 346 calculationElapsed: calculationElapsed,
285 ); 347 );
286 } 348 }
  349 + _profileLog(
  350 + 'core_finish userId=$userId syncElapsedMs=${syncElapsed.inMilliseconds} '
  351 + 'calculationElapsedMs=${calculationElapsed.inMilliseconds} '
  352 + 'totalElapsedMs=${totalStopwatch.elapsedMilliseconds}',
  353 + );
  354 + _publishCalculationEvent(
  355 + type: OhosHealthRawDataPipelineEventType.calculationSucceeded,
  356 + flow: 'calculateAndStore',
  357 + userId: userId,
  358 + startTime: requestedStartTime,
  359 + endTime: effectiveEndTime,
  360 + elapsedMs: calculationElapsed.inMilliseconds,
  361 + hrvCount: storedResult.result.hrvStressPoints.length,
  362 + realtimeCount: storedResult.result.realtimeStressPoints.length,
  363 + dailyCount: storedResult.result.dailyStressPoints.length,
  364 + sleepCount: storedResult.result.sleepResults.length,
  365 + );
287 return storedResult.result; 366 return storedResult.result;
288 } 367 }
289 368
@@ -295,9 +374,11 @@ class OHOSHealthRawDataCoreService { @@ -295,9 +374,11 @@ class OHOSHealthRawDataCoreService {
295 required int? syncedRawStartTime, 374 required int? syncedRawStartTime,
296 required int readChunkDays, 375 required int readChunkDays,
297 }) async { 376 }) async {
  377 + final totalStopwatch = Stopwatch()..start();
298 var hasExistingHrv = false; 378 var hasExistingHrv = false;
299 var hasExistingSleep = false; 379 var hasExistingSleep = false;
300 try { 380 try {
  381 + final contextStopwatch = Stopwatch()..start();
301 final hrvContextStart = 382 final hrvContextStart =
302 await _localStore.latestHrvSourceStartTime(userId); 383 await _localStore.latestHrvSourceStartTime(userId);
303 final realtimeContextStart = 384 final realtimeContextStart =
@@ -317,6 +398,10 @@ class OHOSHealthRawDataCoreService { @@ -317,6 +398,10 @@ class OHOSHealthRawDataCoreService {
317 final latestRawSleepDataTime = await _latestOhosRawDataTime( 398 final latestRawSleepDataTime = await _latestOhosRawDataTime(
318 OhosHealthRawDataType.sleepAnalysis, 399 OhosHealthRawDataType.sleepAnalysis,
319 ); 400 );
  401 + _profileLog(
  402 + 'calculate_contextQuery_finish userId=$userId '
  403 + 'elapsedMs=${contextStopwatch.elapsedMilliseconds}',
  404 + );
320 hasExistingHrv = latestHrvRawEndTime != null; 405 hasExistingHrv = latestHrvRawEndTime != null;
321 hasExistingSleep = latestSleepResultTime != null; 406 hasExistingSleep = latestSleepResultTime != null;
322 407
@@ -400,34 +485,73 @@ class OHOSHealthRawDataCoreService { @@ -400,34 +485,73 @@ class OHOSHealthRawDataCoreService {
400 'realtimeRecomputeStartTime=$realtimeRecomputeStartTime', 485 'realtimeRecomputeStartTime=$realtimeRecomputeStartTime',
401 ); 486 );
402 487
  488 + final hrvFetchStopwatch = Stopwatch()..start();
403 final hrvPoints = await _fetchRawDataInChunks( 489 final hrvPoints = await _fetchRawDataInChunks(
404 HealthDataUploadType.hrv.type, 490 HealthDataUploadType.hrv.type,
405 hrvStartTime, 491 hrvStartTime,
406 effectiveEndTime, 492 effectiveEndTime,
407 readChunkDays: readChunkDays, 493 readChunkDays: readChunkDays,
408 ); 494 );
  495 + _profileLog(
  496 + 'calculate_fetchRaw_finish userId=$userId '
  497 + 'name=hrv dataType=${HealthDataUploadType.hrv.type} '
  498 + 'startTime=$hrvStartTime endTime=$effectiveEndTime '
  499 + 'count=${hrvPoints.length} '
  500 + 'elapsedMs=${hrvFetchStopwatch.elapsedMilliseconds}',
  501 + );
  502 + final heartRateFetchStopwatch = Stopwatch()..start();
409 final heartRatePoints = await _fetchRawDataInChunks( 503 final heartRatePoints = await _fetchRawDataInChunks(
410 HealthDataUploadType.heartRate.type, 504 HealthDataUploadType.heartRate.type,
411 heartRateStartTime, 505 heartRateStartTime,
412 effectiveEndTime, 506 effectiveEndTime,
413 readChunkDays: readChunkDays, 507 readChunkDays: readChunkDays,
414 ); 508 );
  509 + _profileLog(
  510 + 'calculate_fetchRaw_finish userId=$userId '
  511 + 'name=heartRate dataType=${HealthDataUploadType.heartRate.type} '
  512 + 'startTime=$heartRateStartTime endTime=$effectiveEndTime '
  513 + 'count=${heartRatePoints.length} '
  514 + 'elapsedMs=${heartRateFetchStopwatch.elapsedMilliseconds}',
  515 + );
  516 + final restingHeartRateFetchStopwatch = Stopwatch()..start();
415 final restingHeartRatePoints = await _fetchRawDataInChunks( 517 final restingHeartRatePoints = await _fetchRawDataInChunks(
416 HealthDataUploadType.restingHeartRate.type, 518 HealthDataUploadType.restingHeartRate.type,
417 heartRateStartTime, 519 heartRateStartTime,
418 effectiveEndTime, 520 effectiveEndTime,
419 readChunkDays: readChunkDays, 521 readChunkDays: readChunkDays,
420 ); 522 );
  523 + _profileLog(
  524 + 'calculate_fetchRaw_finish userId=$userId '
  525 + 'name=restingHeartRate '
  526 + 'dataType=${HealthDataUploadType.restingHeartRate.type} '
  527 + 'startTime=$heartRateStartTime endTime=$effectiveEndTime '
  528 + 'count=${restingHeartRatePoints.length} '
  529 + 'elapsedMs=${restingHeartRateFetchStopwatch.elapsedMilliseconds}',
  530 + );
  531 + final sleepFetchStopwatch = Stopwatch()..start();
421 final sleepIntervals = await _fetchSleepIntervalsInChunks( 532 final sleepIntervals = await _fetchSleepIntervalsInChunks(
422 sleepStartTime, 533 sleepStartTime,
423 effectiveEndTime, 534 effectiveEndTime,
424 readChunkDays: readChunkDays, 535 readChunkDays: readChunkDays,
425 ); 536 );
  537 + _profileLog(
  538 + 'calculate_fetchRaw_finish userId=$userId name=sleep '
  539 + 'startTime=$sleepStartTime endTime=$effectiveEndTime '
  540 + 'count=${sleepIntervals.length} '
  541 + 'elapsedMs=${sleepFetchStopwatch.elapsedMilliseconds}',
  542 + );
  543 + final workoutFetchStopwatch = Stopwatch()..start();
426 final workoutIntervals = await _fetchWorkoutIntervalsInChunks( 544 final workoutIntervals = await _fetchWorkoutIntervalsInChunks(
427 heartRateStartTime, 545 heartRateStartTime,
428 effectiveEndTime, 546 effectiveEndTime,
429 readChunkDays: readChunkDays, 547 readChunkDays: readChunkDays,
430 ); 548 );
  549 + _profileLog(
  550 + 'calculate_fetchRaw_finish userId=$userId name=workout '
  551 + 'startTime=$heartRateStartTime endTime=$effectiveEndTime '
  552 + 'count=${workoutIntervals.length} '
  553 + 'elapsedMs=${workoutFetchStopwatch.elapsedMilliseconds}',
  554 + );
431 final latestRawHrvTime = _latestRawDataTime(hrvPoints); 555 final latestRawHrvTime = _latestRawDataTime(hrvPoints);
432 final latestRawHrTime = _latestRawDataTime(heartRatePoints); 556 final latestRawHrTime = _latestRawDataTime(heartRatePoints);
433 final latestRawSleepTime = _latestRawDataTime(sleepIntervals); 557 final latestRawSleepTime = _latestRawDataTime(sleepIntervals);
@@ -445,6 +569,7 @@ class OHOSHealthRawDataCoreService { @@ -445,6 +569,7 @@ class OHOSHealthRawDataCoreService {
445 'needSleep=${_needsNewResult(latestRawSleepTime, latestSleepResultTime)}', 569 'needSleep=${_needsNewResult(latestRawSleepTime, latestSleepResultTime)}',
446 ); 570 );
447 571
  572 + final isolateStopwatch = Stopwatch()..start();
448 final result = await Isolate.run( 573 final result = await Isolate.run(
449 () => HuaweiHealthRawStressCalculator(userId: userId).calculate( 574 () => HuaweiHealthRawStressCalculator(userId: userId).calculate(
450 hrvPoints: hrvPoints, 575 hrvPoints: hrvPoints,
@@ -457,12 +582,23 @@ class OHOSHealthRawDataCoreService { @@ -457,12 +582,23 @@ class OHOSHealthRawDataCoreService {
457 ), 582 ),
458 debugName: 'OHOSHealthRawStressCalculator', 583 debugName: 'OHOSHealthRawStressCalculator',
459 ); 584 );
  585 + _profileLog(
  586 + 'calculate_isolate_finish userId=$userId '
  587 + 'hrvInput=${hrvPoints.length} heartRateInput=${heartRatePoints.length} '
  588 + 'restingHeartRateInput=${restingHeartRatePoints.length} '
  589 + 'sleepInput=${sleepIntervals.length} workoutInput=${workoutIntervals.length} '
  590 + 'hrv=${result.hrvStressPoints.length} '
  591 + 'realtime=${result.realtimeStressPoints.length} '
  592 + 'daily=${result.dailyStressPoints.length} '
  593 + 'elapsedMs=${isolateStopwatch.elapsedMilliseconds}',
  594 + );
460 _logInfo( 595 _logInfo(
461 '$_calculateLogMarker result_counts userId=$userId ' 596 '$_calculateLogMarker result_counts userId=$userId '
462 'hrv=${result.hrvStressPoints.length} ' 597 'hrv=${result.hrvStressPoints.length} '
463 'realtime=${result.realtimeStressPoints.length} ' 598 'realtime=${result.realtimeStressPoints.length} '
464 'daily=${result.dailyStressPoints.length}', 599 'daily=${result.dailyStressPoints.length}',
465 ); 600 );
  601 + final filterStopwatch = Stopwatch()..start();
466 final newResult = result.copyWith( 602 final newResult = result.copyWith(
467 hrvStressPoints: _filterNewHrvStressPoints( 603 hrvStressPoints: _filterNewHrvStressPoints(
468 result.hrvStressPoints, 604 result.hrvStressPoints,
@@ -475,6 +611,14 @@ class OHOSHealthRawDataCoreService { @@ -475,6 +611,14 @@ class OHOSHealthRawDataCoreService {
475 recomputeStartTime: realtimeRecomputeStartTime, 611 recomputeStartTime: realtimeRecomputeStartTime,
476 ), 612 ),
477 ); 613 );
  614 + _profileLog(
  615 + 'calculate_filterNew_finish userId=$userId '
  616 + 'inputHrv=${result.hrvStressPoints.length} '
  617 + 'inputRealtime=${result.realtimeStressPoints.length} '
  618 + 'newHrv=${newResult.hrvStressPoints.length} '
  619 + 'newRealtime=${newResult.realtimeStressPoints.length} '
  620 + 'elapsedMs=${filterStopwatch.elapsedMilliseconds}',
  621 + );
478 _logInfo( 622 _logInfo(
479 '$_calculateLogMarker new_result_counts userId=$userId ' 623 '$_calculateLogMarker new_result_counts userId=$userId '
480 'latestHrvRawEndTime=$latestHrvRawEndTime ' 624 'latestHrvRawEndTime=$latestHrvRawEndTime '
@@ -482,27 +626,52 @@ class OHOSHealthRawDataCoreService { @@ -482,27 +626,52 @@ class OHOSHealthRawDataCoreService {
482 'hrv=${newResult.hrvStressPoints.length} ' 626 'hrv=${newResult.hrvStressPoints.length} '
483 'realtime=${newResult.realtimeStressPoints.length}', 627 'realtime=${newResult.realtimeStressPoints.length}',
484 ); 628 );
  629 + final resultStoreStopwatch = Stopwatch()..start();
485 await _localStore.upsertResult(newResult); 630 await _localStore.upsertResult(newResult);
  631 + _profileLog(
  632 + 'calculate_storeResult_finish userId=$userId '
  633 + 'hrv=${newResult.hrvStressPoints.length} '
  634 + 'realtime=${newResult.realtimeStressPoints.length} '
  635 + 'elapsedMs=${resultStoreStopwatch.elapsedMilliseconds}',
  636 + );
486 _logInfo( 637 _logInfo(
487 '$_calculateLogMarker result_stored userId=$userId ' 638 '$_calculateLogMarker result_stored userId=$userId '
488 'hrv=${newResult.hrvStressPoints.length} ' 639 'hrv=${newResult.hrvStressPoints.length} '
489 'realtime=${newResult.realtimeStressPoints.length}', 640 'realtime=${newResult.realtimeStressPoints.length}',
490 ); 641 );
491 642
  643 + final dailyStopwatch = Stopwatch()..start();
492 final dailyStressPoints = await _calculateAndStoreDailyStressPoints( 644 final dailyStressPoints = await _calculateAndStoreDailyStressPoints(
493 userId: userId, 645 userId: userId,
494 realtimePoints: newResult.realtimeStressPoints, 646 realtimePoints: newResult.realtimeStressPoints,
495 nowSeconds: effectiveEndTime, 647 nowSeconds: effectiveEndTime,
496 ); 648 );
  649 + _profileLog(
  650 + 'calculate_daily_finish userId=$userId '
  651 + 'count=${dailyStressPoints.length} '
  652 + 'elapsedMs=${dailyStopwatch.elapsedMilliseconds}',
  653 + );
  654 + final sleepStopwatch = Stopwatch()..start();
497 final sleepResults = await _calculateAndStoreSleepResults( 655 final sleepResults = await _calculateAndStoreSleepResults(
498 userId: userId, 656 userId: userId,
499 sleepIntervals: sleepIntervals, 657 sleepIntervals: sleepIntervals,
500 latestSleepResultTime: latestSleepResultTime, 658 latestSleepResultTime: latestSleepResultTime,
501 ); 659 );
  660 + _profileLog(
  661 + 'calculate_sleep_finish userId=$userId count=${sleepResults.length} '
  662 + 'elapsedMs=${sleepStopwatch.elapsedMilliseconds}',
  663 + );
502 _logInfo( 664 _logInfo(
503 'calculate_daily_sleep_stored userId=$userId ' 665 'calculate_daily_sleep_stored userId=$userId '
504 'daily=${dailyStressPoints.length} sleep=${sleepResults.length}', 666 'daily=${dailyStressPoints.length} sleep=${sleepResults.length}',
505 ); 667 );
  668 + _profileLog(
  669 + 'calculate_finish userId=$userId '
  670 + 'hrv=${newResult.hrvStressPoints.length} '
  671 + 'realtime=${newResult.realtimeStressPoints.length} '
  672 + 'daily=${dailyStressPoints.length} sleep=${sleepResults.length} '
  673 + 'elapsedMs=${totalStopwatch.elapsedMilliseconds}',
  674 + );
506 return _OhosStoredCalculationResult( 675 return _OhosStoredCalculationResult(
507 result: newResult.copyWith( 676 result: newResult.copyWith(
508 dailyStressPoints: dailyStressPoints, 677 dailyStressPoints: dailyStressPoints,
@@ -512,6 +681,10 @@ class OHOSHealthRawDataCoreService { @@ -512,6 +681,10 @@ class OHOSHealthRawDataCoreService {
512 hasExistingSleep: hasExistingSleep, 681 hasExistingSleep: hasExistingSleep,
513 ); 682 );
514 } catch (error, stackTrace) { 683 } catch (error, stackTrace) {
  684 + _profileLog(
  685 + 'calculate_failed userId=$userId '
  686 + 'elapsedMs=${totalStopwatch.elapsedMilliseconds} error=$error',
  687 + );
515 _logError( 688 _logError(
516 '$_calculateLogMarker calculate_failed userId=$userId', 689 '$_calculateLogMarker calculate_failed userId=$userId',
517 error, 690 error,
@@ -531,10 +704,26 @@ class OHOSHealthRawDataCoreService { @@ -531,10 +704,26 @@ class OHOSHealthRawDataCoreService {
531 var cursor = startTime; 704 var cursor = startTime;
532 while (cursor <= endTime) { 705 while (cursor <= endTime) {
533 final chunkEnd = math.min(cursor + chunkSeconds - 1, endTime); 706 final chunkEnd = math.min(cursor + chunkSeconds - 1, endTime);
534 - final points = await _rawDataSource.getRawData(  
535 - dataType,  
536 - cursor,  
537 - chunkEnd, 707 + final chunkStopwatch = Stopwatch()..start();
  708 + final List<HealthKitRawDataPoint> points;
  709 + try {
  710 + points = await _rawDataSource.getRawData(
  711 + dataType,
  712 + cursor,
  713 + chunkEnd,
  714 + );
  715 + } catch (error) {
  716 + _profileLog(
  717 + 'rawChunk_failed dataType=$dataType startTime=$cursor '
  718 + 'endTime=$chunkEnd elapsedMs=${chunkStopwatch.elapsedMilliseconds} '
  719 + 'error=$error',
  720 + );
  721 + rethrow;
  722 + }
  723 + _profileLog(
  724 + 'rawChunk_finish dataType=$dataType startTime=$cursor '
  725 + 'endTime=$chunkEnd count=${points.length} '
  726 + 'elapsedMs=${chunkStopwatch.elapsedMilliseconds}',
538 ); 727 );
539 for (final point in points) { 728 for (final point in points) {
540 yield point; 729 yield point;
@@ -552,12 +741,27 @@ class OHOSHealthRawDataCoreService { @@ -552,12 +741,27 @@ class OHOSHealthRawDataCoreService {
552 var cursor = startTime; 741 var cursor = startTime;
553 while (cursor <= endTime) { 742 while (cursor <= endTime) {
554 final chunkEnd = math.min(cursor + chunkSeconds - 1, endTime); 743 final chunkEnd = math.min(cursor + chunkSeconds - 1, endTime);
555 - final groups = await _rawDataSource.getRawSleepData(cursor, chunkEnd); 744 + final chunkStopwatch = Stopwatch()..start();
  745 + final List<HealthKitRawSleepDataPoint> groups;
  746 + try {
  747 + groups = await _rawDataSource.getRawSleepData(cursor, chunkEnd);
  748 + } catch (error) {
  749 + _profileLog(
  750 + 'sleepChunk_failed startTime=$cursor endTime=$chunkEnd '
  751 + 'elapsedMs=${chunkStopwatch.elapsedMilliseconds} error=$error',
  752 + );
  753 + rethrow;
  754 + }
556 final points = groups 755 final points = groups
557 .expand((group) => group.sleepDataPoints) 756 .expand((group) => group.sleepDataPoints)
558 .where( 757 .where(
559 (point) => point.endTime >= cursor && point.startTime <= chunkEnd) 758 (point) => point.endTime >= cursor && point.startTime <= chunkEnd)
560 .toList(); 759 .toList();
  760 + _profileLog(
  761 + 'sleepChunk_finish startTime=$cursor endTime=$chunkEnd '
  762 + 'groupCount=${groups.length} count=${points.length} '
  763 + 'elapsedMs=${chunkStopwatch.elapsedMilliseconds}',
  764 + );
561 for (final point in points) { 765 for (final point in points) {
562 yield point; 766 yield point;
563 } 767 }
@@ -574,9 +778,27 @@ class OHOSHealthRawDataCoreService { @@ -574,9 +778,27 @@ class OHOSHealthRawDataCoreService {
574 var cursor = startTime; 778 var cursor = startTime;
575 while (cursor <= endTime) { 779 while (cursor <= endTime) {
576 final chunkEnd = math.min(cursor + chunkSeconds - 1, endTime); 780 final chunkEnd = math.min(cursor + chunkSeconds - 1, endTime);
577 - final points = await _rawDataSource.getRawWorkoutData(cursor, chunkEnd);  
578 - for (final point in points.where(  
579 - (point) => point.endTime >= cursor && point.startTime <= chunkEnd)) { 781 + final chunkStopwatch = Stopwatch()..start();
  782 + final List<HealthKitRawWorkoutDataPoint> points;
  783 + try {
  784 + points = await _rawDataSource.getRawWorkoutData(cursor, chunkEnd);
  785 + } catch (error) {
  786 + _profileLog(
  787 + 'workoutChunk_failed startTime=$cursor endTime=$chunkEnd '
  788 + 'elapsedMs=${chunkStopwatch.elapsedMilliseconds} error=$error',
  789 + );
  790 + rethrow;
  791 + }
  792 + final filteredPoints = points
  793 + .where(
  794 + (point) => point.endTime >= cursor && point.startTime <= chunkEnd)
  795 + .toList(growable: false);
  796 + _profileLog(
  797 + 'workoutChunk_finish startTime=$cursor endTime=$chunkEnd '
  798 + 'count=${filteredPoints.length} rawCount=${points.length} '
  799 + 'elapsedMs=${chunkStopwatch.elapsedMilliseconds}',
  800 + );
  801 + for (final point in filteredPoints) {
580 yield point; 802 yield point;
581 } 803 }
582 cursor = chunkEnd + 1; 804 cursor = chunkEnd + 1;
@@ -1022,13 +1244,7 @@ class OHOSHealthRawDataCoreService { @@ -1022,13 +1244,7 @@ class OHOSHealthRawDataCoreService {
1022 1244
1023 int? _rawBackfillStartTime(int? latestDataTime) { 1245 int? _rawBackfillStartTime(int? latestDataTime) {
1024 if (latestDataTime == null) return null; 1246 if (latestDataTime == null) return null;
1025 - final backfillTime = math.max(  
1026 - 0,  
1027 - latestDataTime -  
1028 - OhosHealthRawDataSyncService.incrementalBackfillDays *  
1029 - Duration.secondsPerDay,  
1030 - );  
1031 - return _localDay(backfillTime).millisecondsSinceEpoch ~/ 1000; 1247 + return _localDay(latestDataTime).millisecondsSinceEpoch ~/ 1000;
1032 } 1248 }
1033 1249
1034 int? _boundedNullableStartTime(int? startTime, int earliestStartTime) { 1250 int? _boundedNullableStartTime(int? startTime, int earliestStartTime) {
@@ -1108,6 +1324,7 @@ class OHOSHealthRawDataCoreService { @@ -1108,6 +1324,7 @@ class OHOSHealthRawDataCoreService {
1108 final dailyStressPoints = <HealthRawDailyStressPoint>[]; 1324 final dailyStressPoints = <HealthRawDailyStressPoint>[];
1109 final emptyDates = <int>[]; 1325 final emptyDates = <int>[];
1110 for (final date in affectedDates) { 1326 for (final date in affectedDates) {
  1327 + final dayStopwatch = Stopwatch()..start();
1111 if (date != todayDate && existingDates.contains(date)) { 1328 if (date != todayDate && existingDates.contains(date)) {
1112 _logInfo( 1329 _logInfo(
1113 '$_dailyStressLogMarker refresh_existing userId=$userId date=$date', 1330 '$_dailyStressLogMarker refresh_existing userId=$userId date=$date',
@@ -1119,6 +1336,7 @@ class OHOSHealthRawDataCoreService { @@ -1119,6 +1336,7 @@ class OHOSHealthRawDataCoreService {
1119 startTime: startTime, 1336 startTime: startTime,
1120 endTime: endTime, 1337 endTime: endTime,
1121 ); 1338 );
  1339 + final queryElapsedMs = dayStopwatch.elapsedMilliseconds;
1122 _logInfo( 1340 _logInfo(
1123 '$_dailyStressLogMarker day_query userId=$userId date=$date ' 1341 '$_dailyStressLogMarker day_query userId=$userId date=$date '
1124 'startTime=$startTime endTime=$endTime ' 1342 'startTime=$startTime endTime=$endTime '
@@ -1133,18 +1351,31 @@ class OHOSHealthRawDataCoreService { @@ -1133,18 +1351,31 @@ class OHOSHealthRawDataCoreService {
1133 ).firstOrNull; 1351 ).firstOrNull;
1134 if (point == null) { 1352 if (point == null) {
1135 emptyDates.add(date); 1353 emptyDates.add(date);
  1354 + _profileLog(
  1355 + 'dailyStress_day_finish userId=$userId date=$date '
  1356 + 'dayRealtime=${dayRealtimePoints.length} hasResult=false '
  1357 + 'queryElapsedMs=$queryElapsedMs '
  1358 + 'elapsedMs=${dayStopwatch.elapsedMilliseconds}',
  1359 + );
1136 _logInfo( 1360 _logInfo(
1137 '$_dailyStressLogMarker no_result userId=$userId date=$date', 1361 '$_dailyStressLogMarker no_result userId=$userId date=$date',
1138 ); 1362 );
1139 continue; 1363 continue;
1140 } 1364 }
1141 dailyStressPoints.add(point); 1365 dailyStressPoints.add(point);
  1366 + _profileLog(
  1367 + 'dailyStress_day_finish userId=$userId date=$date '
  1368 + 'dayRealtime=${dayRealtimePoints.length} hasResult=true '
  1369 + 'queryElapsedMs=$queryElapsedMs '
  1370 + 'elapsedMs=${dayStopwatch.elapsedMilliseconds}',
  1371 + );
1142 _logInfo( 1372 _logInfo(
1143 '$_dailyStressLogMarker result userId=$userId date=$date ' 1373 '$_dailyStressLogMarker result userId=$userId date=$date '
1144 'stressValue=${point.stressValue} stressScore=${point.stressScore} ' 1374 'stressValue=${point.stressValue} stressScore=${point.stressScore} '
1145 'state=${point.state.value}', 1375 'state=${point.state.value}',
1146 ); 1376 );
1147 } 1377 }
  1378 + final dailyStoreStopwatch = Stopwatch()..start();
1148 await _localStore.upsertDailyStressPoints( 1379 await _localStore.upsertDailyStressPoints(
1149 userId: userId, 1380 userId: userId,
1150 points: dailyStressPoints, 1381 points: dailyStressPoints,
@@ -1153,6 +1384,11 @@ class OHOSHealthRawDataCoreService { @@ -1153,6 +1384,11 @@ class OHOSHealthRawDataCoreService {
1153 userId: userId, 1384 userId: userId,
1154 dates: emptyDates, 1385 dates: emptyDates,
1155 ); 1386 );
  1387 + _profileLog(
  1388 + 'dailyStress_store_finish userId=$userId '
  1389 + 'stored=${dailyStressPoints.length} deletedEmpty=${emptyDates.length} '
  1390 + 'elapsedMs=${dailyStoreStopwatch.elapsedMilliseconds}',
  1391 + );
1156 _logInfo( 1392 _logInfo(
1157 '$_dailyStressLogMarker stored userId=$userId ' 1393 '$_dailyStressLogMarker stored userId=$userId '
1158 'stored=${dailyStressPoints.length} deletedEmptyDates=$emptyDates', 1394 'stored=${dailyStressPoints.length} deletedEmptyDates=$emptyDates',
@@ -1184,6 +1420,7 @@ class OHOSHealthRawDataCoreService { @@ -1184,6 +1420,7 @@ class OHOSHealthRawDataCoreService {
1184 ); 1420 );
1185 final results = <HealthRawSleepResult>[]; 1421 final results = <HealthRawSleepResult>[];
1186 for (final day in days) { 1422 for (final day in days) {
  1423 + final dayStopwatch = Stopwatch()..start();
1187 final calculation = HealthSleepCalculator.calculateDay( 1424 final calculation = HealthSleepCalculator.calculateDay(
1188 day: day, 1425 day: day,
1189 sleepIntervals: sleepIntervals, 1426 sleepIntervals: sleepIntervals,
@@ -1192,6 +1429,12 @@ class OHOSHealthRawDataCoreService { @@ -1192,6 +1429,12 @@ class OHOSHealthRawDataCoreService {
1192 final score = calculation.score; 1429 final score = calculation.score;
1193 final state = calculation.state; 1430 final state = calculation.state;
1194 if (merged == null || score == null || state == null) { 1431 if (merged == null || score == null || state == null) {
  1432 + _profileLog(
  1433 + 'sleep_day_finish userId=$userId '
  1434 + 'date=${_dateKeyFromDateTime(day)} hasResult=false '
  1435 + 'reason=missing_score_or_state '
  1436 + 'elapsedMs=${dayStopwatch.elapsedMilliseconds}',
  1437 + );
1195 _logInfo( 1438 _logInfo(
1196 '$_sleepCalcLogMarker skip_invalid userId=$userId ' 1439 '$_sleepCalcLogMarker skip_invalid userId=$userId '
1197 'date=${_dateKeyFromDateTime(day)} ' 1440 'date=${_dateKeyFromDateTime(day)} '
@@ -1201,6 +1444,12 @@ class OHOSHealthRawDataCoreService { @@ -1201,6 +1444,12 @@ class OHOSHealthRawDataCoreService {
1201 continue; 1444 continue;
1202 } 1445 }
1203 if (!calculation.hasValidSleep) { 1446 if (!calculation.hasValidSleep) {
  1447 + _profileLog(
  1448 + 'sleep_day_finish userId=$userId '
  1449 + 'date=${_dateKeyFromDateTime(day)} hasResult=false '
  1450 + 'reason=no_valid_sleep '
  1451 + 'elapsedMs=${dayStopwatch.elapsedMilliseconds}',
  1452 + );
1204 _logInfo( 1453 _logInfo(
1205 '$_sleepCalcLogMarker skip_invalid userId=$userId ' 1454 '$_sleepCalcLogMarker skip_invalid userId=$userId '
1206 'date=${_dateKeyFromDateTime(day)} reason=no_valid_sleep ' 1455 'date=${_dateKeyFromDateTime(day)} reason=no_valid_sleep '
@@ -1210,6 +1459,12 @@ class OHOSHealthRawDataCoreService { @@ -1210,6 +1459,12 @@ class OHOSHealthRawDataCoreService {
1210 } 1459 }
1211 if (latestSleepResultTime != null && 1460 if (latestSleepResultTime != null &&
1212 merged.endTime <= latestSleepResultTime) { 1461 merged.endTime <= latestSleepResultTime) {
  1462 + _profileLog(
  1463 + 'sleep_day_finish userId=$userId '
  1464 + 'date=${_dateKeyFromDateTime(day)} hasResult=false '
  1465 + 'reason=existing mergedEnd=${merged.endTime} '
  1466 + 'elapsedMs=${dayStopwatch.elapsedMilliseconds}',
  1467 + );
1213 _logInfo( 1468 _logInfo(
1214 '$_sleepCalcLogMarker skip_existing userId=$userId ' 1469 '$_sleepCalcLogMarker skip_existing userId=$userId '
1215 'date=${_dateKeyFromDateTime(day)} mergedEnd=${merged.endTime} ' 1470 'date=${_dateKeyFromDateTime(day)} mergedEnd=${merged.endTime} '
@@ -1230,6 +1485,12 @@ class OHOSHealthRawDataCoreService { @@ -1230,6 +1485,12 @@ class OHOSHealthRawDataCoreService {
1230 uploaded: false, 1485 uploaded: false,
1231 ), 1486 ),
1232 ); 1487 );
  1488 + _profileLog(
  1489 + 'sleep_day_finish userId=$userId '
  1490 + 'date=${_dateKeyFromDateTime(day)} hasResult=true '
  1491 + 'sleepMinutes=${calculation.summary.sleepMinutes} '
  1492 + 'elapsedMs=${dayStopwatch.elapsedMilliseconds}',
  1493 + );
1233 _logInfo( 1494 _logInfo(
1234 '$_sleepCalcLogMarker result userId=$userId ' 1495 '$_sleepCalcLogMarker result userId=$userId '
1235 'date=${_dateKeyFromDateTime(day)} start=${merged.startTime} ' 1496 'date=${_dateKeyFromDateTime(day)} start=${merged.startTime} '
@@ -1237,7 +1498,12 @@ class OHOSHealthRawDataCoreService { @@ -1237,7 +1498,12 @@ class OHOSHealthRawDataCoreService {
1237 'sleepMinutes=${calculation.summary.sleepMinutes}', 1498 'sleepMinutes=${calculation.summary.sleepMinutes}',
1238 ); 1499 );
1239 } 1500 }
  1501 + final sleepStoreStopwatch = Stopwatch()..start();
1240 await _localStore.upsertSleepResults(userId: userId, results: results); 1502 await _localStore.upsertSleepResults(userId: userId, results: results);
  1503 + _profileLog(
  1504 + 'sleep_store_finish userId=$userId stored=${results.length} '
  1505 + 'elapsedMs=${sleepStoreStopwatch.elapsedMilliseconds}',
  1506 + );
1241 _logInfo( 1507 _logInfo(
1242 '$_sleepCalcLogMarker stored userId=$userId stored=${results.length}', 1508 '$_sleepCalcLogMarker stored userId=$userId stored=${results.length}',
1243 ); 1509 );
@@ -1392,6 +1658,16 @@ class OHOSHealthRawDataCoreService { @@ -1392,6 +1658,16 @@ class OHOSHealthRawDataCoreService {
1392 return DateTime(date.year, date.month, date.day); 1658 return DateTime(date.year, date.month, date.day);
1393 } 1659 }
1394 1660
  1661 + static int _twoMonthLookbackStart(int endTime) {
  1662 + final endDate = DateTime.fromMillisecondsSinceEpoch(endTime * 1000);
  1663 + return DateTime(
  1664 + endDate.year,
  1665 + endDate.month - defaultLookbackMonths,
  1666 + endDate.day,
  1667 + ).millisecondsSinceEpoch ~/
  1668 + 1000;
  1669 + }
  1670 +
1395 static int _dateKeyFromDateTime(DateTime date) { 1671 static int _dateKeyFromDateTime(DateTime date) {
1396 return date.year * 10000 + date.month * 100 + date.day; 1672 return date.year * 10000 + date.month * 100 + date.day;
1397 } 1673 }
@@ -1437,6 +1713,42 @@ class OHOSHealthRawDataCoreService { @@ -1437,6 +1713,42 @@ class OHOSHealthRawDataCoreService {
1437 // Logger may be unavailable in isolated unit tests. 1713 // Logger may be unavailable in isolated unit tests.
1438 } 1714 }
1439 } 1715 }
  1716 +
  1717 + static void _profileLog(String message) {
  1718 + if (!kDebugMode) return;
  1719 + _logInfo('$_profileLogMarker $message');
  1720 + }
  1721 +
  1722 + static void _publishCalculationEvent({
  1723 + required OhosHealthRawDataPipelineEventType type,
  1724 + required String flow,
  1725 + required int userId,
  1726 + required int startTime,
  1727 + required int endTime,
  1728 + int? elapsedMs,
  1729 + int? hrvCount,
  1730 + int? realtimeCount,
  1731 + int? dailyCount,
  1732 + int? sleepCount,
  1733 + String? error,
  1734 + }) {
  1735 + OhosHealthRawDataPipelineEvents.publish(
  1736 + OhosHealthRawDataPipelineEvent(
  1737 + type: type,
  1738 + flow: flow,
  1739 + occurredAt: DateTime.now(),
  1740 + userId: userId,
  1741 + startTime: startTime,
  1742 + endTime: endTime,
  1743 + elapsedMs: elapsedMs,
  1744 + hrvCount: hrvCount,
  1745 + realtimeCount: realtimeCount,
  1746 + dailyCount: dailyCount,
  1747 + sleepCount: sleepCount,
  1748 + error: error,
  1749 + ),
  1750 + );
  1751 + }
1440 } 1752 }
1441 1753
1442 class _OhosStoredCalculationResult { 1754 class _OhosStoredCalculationResult {
  1 +import 'dart:async';
  2 +
  3 +enum OhosHealthRawDataPipelineEventType {
  4 + syncStarted,
  5 + syncSucceeded,
  6 + syncFailed,
  7 + calculationStarted,
  8 + calculationSucceeded,
  9 + calculationFailed,
  10 +}
  11 +
  12 +class OhosHealthRawDataPipelineEvent {
  13 + const OhosHealthRawDataPipelineEvent({
  14 + required this.type,
  15 + required this.flow,
  16 + required this.occurredAt,
  17 + this.userId,
  18 + this.dataType,
  19 + this.dataTypes,
  20 + this.startTime,
  21 + this.endTime,
  22 + this.elapsedMs,
  23 + this.pageCount,
  24 + this.storedCount,
  25 + this.hrvCount,
  26 + this.realtimeCount,
  27 + this.dailyCount,
  28 + this.sleepCount,
  29 + this.error,
  30 + });
  31 +
  32 + final OhosHealthRawDataPipelineEventType type;
  33 + final String flow;
  34 + final DateTime occurredAt;
  35 + final int? userId;
  36 + final int? dataType;
  37 + final List<int>? dataTypes;
  38 + final int? startTime;
  39 + final int? endTime;
  40 + final int? elapsedMs;
  41 + final int? pageCount;
  42 + final int? storedCount;
  43 + final int? hrvCount;
  44 + final int? realtimeCount;
  45 + final int? dailyCount;
  46 + final int? sleepCount;
  47 + final String? error;
  48 +
  49 + bool get isStarted =>
  50 + type == OhosHealthRawDataPipelineEventType.syncStarted ||
  51 + type == OhosHealthRawDataPipelineEventType.calculationStarted;
  52 +
  53 + bool get isSucceeded =>
  54 + type == OhosHealthRawDataPipelineEventType.syncSucceeded ||
  55 + type == OhosHealthRawDataPipelineEventType.calculationSucceeded;
  56 +
  57 + bool get isFailed =>
  58 + type == OhosHealthRawDataPipelineEventType.syncFailed ||
  59 + type == OhosHealthRawDataPipelineEventType.calculationFailed;
  60 +}
  61 +
  62 +class OhosHealthRawDataPipelineEvents {
  63 + OhosHealthRawDataPipelineEvents._();
  64 +
  65 + static final StreamController<OhosHealthRawDataPipelineEvent> _controller =
  66 + StreamController<OhosHealthRawDataPipelineEvent>.broadcast();
  67 +
  68 + static Stream<OhosHealthRawDataPipelineEvent> get stream =>
  69 + _controller.stream;
  70 +
  71 + static void publish(OhosHealthRawDataPipelineEvent event) {
  72 + if (_controller.isClosed) return;
  73 + _controller.add(event);
  74 + }
  75 +}
@@ -108,39 +108,53 @@ class OhosHealthRawDataSqliteStore implements OhosHealthRawDataLocalStore { @@ -108,39 +108,53 @@ class OhosHealthRawDataSqliteStore implements OhosHealthRawDataLocalStore {
108 ); 108 );
109 return 0; 109 return 0;
110 } 110 }
  111 + final storage = await _prepareRawDataStorage(db, dataType);
  112 + final itemsToStore = await _filterExistingDuplicates(
  113 + db: db,
  114 + dataType: dataType,
  115 + storage: storage,
  116 + items: filteredItems,
  117 + latestTime: latestTime,
  118 + );
  119 + if (itemsToStore.isEmpty) {
  120 + _log(
  121 + 'skip_store_existing_duplicates dataType=$dataType '
  122 + 'latestTime=$latestTime incoming=${items.length} '
  123 + 'candidates=${filteredItems.length}',
  124 + );
  125 + return 0;
  126 + }
111 var storedCount = 0; 127 var storedCount = 0;
112 await db.transaction((txn) async { 128 await db.transaction((txn) async {
113 if (dataType == OhosHealthRawDataType.sleepAnalysis) { 129 if (dataType == OhosHealthRawDataType.sleepAnalysis) {
114 - for (final item in filteredItems) {  
115 - final rowId = await txn.insert( 130 + final batch = txn.batch();
  131 + for (final item in itemsToStore) {
  132 + batch.insert(
116 sleepDataTable, 133 sleepDataTable,
117 _sleepRow(item, createTime), 134 _sleepRow(item, createTime),
118 conflictAlgorithm: ConflictAlgorithm.ignore, 135 conflictAlgorithm: ConflictAlgorithm.ignore,
119 ); 136 );
120 - if (rowId > 0) storedCount += 1;  
121 } 137 }
  138 + storedCount += await _commitInsertBatch(batch);
122 return; 139 return;
123 } 140 }
124 if (dataType == OhosHealthRawDataType.workout) { 141 if (dataType == OhosHealthRawDataType.workout) {
125 - final table = _rawDataTable(dataType);  
126 - await _createRawIntervalDataTypeTable(txn, table);  
127 - for (final item in filteredItems) {  
128 - final rowId = await txn.insert(  
129 - table, 142 + final batch = txn.batch();
  143 + for (final item in itemsToStore) {
  144 + batch.insert(
  145 + storage.table,
130 _intervalRow(item, createTime), 146 _intervalRow(item, createTime),
131 conflictAlgorithm: ConflictAlgorithm.ignore, 147 conflictAlgorithm: ConflictAlgorithm.ignore,
132 ); 148 );
133 - if (rowId > 0) storedCount += 1;  
134 } 149 }
  150 + storedCount += await _commitInsertBatch(batch);
135 return; 151 return;
136 } 152 }
137 153
138 - final storedDataType = _storedHealthDataType(dataType);  
139 - final table = _rawDataTable(storedDataType);  
140 - await _createRawDataTypeTable(txn, table);  
141 - for (final item in filteredItems) {  
142 - final rowId = await txn.insert(  
143 - table, 154 + final batch = txn.batch();
  155 + for (final item in itemsToStore) {
  156 + batch.insert(
  157 + storage.table,
144 _rawRow(item, createTime), 158 _rawRow(item, createTime),
145 conflictAlgorithm: _dailyItemNeedsRefresh( 159 conflictAlgorithm: _dailyItemNeedsRefresh(
146 dataType: dataType, 160 dataType: dataType,
@@ -150,10 +164,10 @@ class OhosHealthRawDataSqliteStore implements OhosHealthRawDataLocalStore { @@ -150,10 +164,10 @@ class OhosHealthRawDataSqliteStore implements OhosHealthRawDataLocalStore {
150 ? ConflictAlgorithm.replace 164 ? ConflictAlgorithm.replace
151 : ConflictAlgorithm.ignore, 165 : ConflictAlgorithm.ignore,
152 ); 166 );
153 - if (rowId > 0) storedCount += 1;  
154 } 167 }
  168 + storedCount += await _commitInsertBatch(batch);
155 }); 169 });
156 - final skippedCount = filteredItems.length - storedCount; 170 + final skippedCount = filteredItems.length - itemsToStore.length;
157 if (skippedCount > 0) { 171 if (skippedCount > 0) {
158 _log( 172 _log(
159 'skip_store_duplicates dataType=$dataType latestTime=$latestTime ' 173 'skip_store_duplicates dataType=$dataType latestTime=$latestTime '
@@ -164,6 +178,104 @@ class OhosHealthRawDataSqliteStore implements OhosHealthRawDataLocalStore { @@ -164,6 +178,104 @@ class OhosHealthRawDataSqliteStore implements OhosHealthRawDataLocalStore {
164 return storedCount; 178 return storedCount;
165 } 179 }
166 180
  181 + Future<_RawDataStorage> _prepareRawDataStorage(
  182 + Database db,
  183 + int dataType,
  184 + ) async {
  185 + if (dataType == OhosHealthRawDataType.sleepAnalysis) {
  186 + return const _RawDataStorage(
  187 + table: sleepDataTable,
  188 + keyColumn: 'from_time',
  189 + );
  190 + }
  191 + if (dataType == OhosHealthRawDataType.workout) {
  192 + final table = _rawDataTable(dataType);
  193 + await _createRawIntervalDataTypeTable(db, table);
  194 + return _RawDataStorage(table: table, keyColumn: 'from_time');
  195 + }
  196 +
  197 + final storedDataType = _storedHealthDataType(dataType);
  198 + final table = _rawDataTable(storedDataType);
  199 + await _createRawDataTypeTable(db, table);
  200 + return _RawDataStorage(table: table, keyColumn: 'time');
  201 + }
  202 +
  203 + Future<List<OhosHealthRawDataItem>> _filterExistingDuplicates({
  204 + required Database db,
  205 + required int dataType,
  206 + required _RawDataStorage storage,
  207 + required List<OhosHealthRawDataItem> items,
  208 + required int? latestTime,
  209 + }) async {
  210 + final keyedItems = <int, OhosHealthRawDataItem>{};
  211 + final refreshKeys = <int>{};
  212 + for (final item in items) {
  213 + final keyTime = _dedupeKeyTime(dataType: dataType, item: item);
  214 + keyedItems.putIfAbsent(keyTime, () => item);
  215 + if (_dailyItemNeedsRefresh(
  216 + dataType: dataType,
  217 + item: item,
  218 + latestTime: latestTime,
  219 + )) {
  220 + refreshKeys.add(keyTime);
  221 + }
  222 + }
  223 + if (keyedItems.isEmpty) return const <OhosHealthRawDataItem>[];
  224 + if (latestTime == null) {
  225 + return keyedItems.values.toList(growable: false);
  226 + }
  227 +
  228 + final existingKeys = await _queryExistingKeys(
  229 + db: db,
  230 + table: storage.table,
  231 + keyColumn: storage.keyColumn,
  232 + keys: keyedItems.keys.where((key) => !refreshKeys.contains(key)),
  233 + );
  234 + return <OhosHealthRawDataItem>[
  235 + for (final entry in keyedItems.entries)
  236 + if (refreshKeys.contains(entry.key) ||
  237 + !existingKeys.contains(entry.key))
  238 + entry.value,
  239 + ];
  240 + }
  241 +
  242 + Future<Set<int>> _queryExistingKeys({
  243 + required Database db,
  244 + required String table,
  245 + required String keyColumn,
  246 + required Iterable<int> keys,
  247 + }) async {
  248 + final keyList = keys.toList(growable: false);
  249 + if (keyList.isEmpty) return const <int>{};
  250 + final existing = <int>{};
  251 + const chunkSize = 900;
  252 + for (var offset = 0; offset < keyList.length; offset += chunkSize) {
  253 + final chunk =
  254 + keyList.skip(offset).take(chunkSize).toList(growable: false);
  255 + final placeholders = List<String>.filled(chunk.length, '?').join(',');
  256 + final rows = await db.query(
  257 + table,
  258 + columns: <String>[keyColumn],
  259 + where: '$keyColumn IN ($placeholders)',
  260 + whereArgs: chunk,
  261 + );
  262 + for (final row in rows) {
  263 + final key = row[keyColumn];
  264 + if (key is int) {
  265 + existing.add(key);
  266 + } else if (key is num) {
  267 + existing.add(key.toInt());
  268 + }
  269 + }
  270 + }
  271 + return existing;
  272 + }
  273 +
  274 + Future<int> _commitInsertBatch(Batch batch) async {
  275 + final results = await batch.commit(noResult: false);
  276 + return results.whereType<int>().where((rowId) => rowId > 0).length;
  277 + }
  278 +
167 @override 279 @override
168 Future<List<OhosHealthRawDataItem>> queryRawData({ 280 Future<List<OhosHealthRawDataItem>> queryRawData({
169 required int dataType, 281 required int dataType,
@@ -606,3 +718,13 @@ CREATE TABLE IF NOT EXISTS $table ( @@ -606,3 +718,13 @@ CREATE TABLE IF NOT EXISTS $table (
606 return value is num ? value.toInt() : null; 718 return value is num ? value.toInt() : null;
607 } 719 }
608 } 720 }
  721 +
  722 +class _RawDataStorage {
  723 + const _RawDataStorage({
  724 + required this.table,
  725 + required this.keyColumn,
  726 + });
  727 +
  728 + final String table;
  729 + final String keyColumn;
  730 +}
  1 +import 'dart:async';
  2 +import 'dart:collection';
1 import 'dart:math' as math; 3 import 'dart:math' as math;
2 4
  5 +import 'package:dio/dio.dart';
  6 +import 'package:doublefeel_flutter/core/error/app_error.dart';
3 import 'package:doublefeel_flutter/core/logging/app_logger.dart'; 7 import 'package:doublefeel_flutter/core/logging/app_logger.dart';
4 import 'package:doublefeel_flutter/core/result/app_result.dart'; 8 import 'package:doublefeel_flutter/core/result/app_result.dart';
5 import 'package:doublefeel_flutter/data/models/enums/app_enums.dart'; 9 import 'package:doublefeel_flutter/data/models/enums/app_enums.dart';
@@ -10,6 +14,7 @@ import 'package:doublefeel_flutter/data/models/health/health_v2_models.dart'; @@ -10,6 +14,7 @@ import 'package:doublefeel_flutter/data/models/health/health_v2_models.dart';
10 import 'package:flutter/foundation.dart'; 14 import 'package:flutter/foundation.dart';
11 15
12 import 'huawei_health_data_type.dart'; 16 import 'huawei_health_data_type.dart';
  17 +import 'ohos_health_raw_data_events.dart';
13 18
14 class OhosHealthRawDataType { 19 class OhosHealthRawDataType {
15 const OhosHealthRawDataType._(); 20 const OhosHealthRawDataType._();
@@ -25,17 +30,20 @@ class OhosHealthRawDataSyncService { @@ -25,17 +30,20 @@ class OhosHealthRawDataSyncService {
25 OhosHealthRawDataLocalStore? localStore, 30 OhosHealthRawDataLocalStore? localStore,
26 DateTime Function()? nowProvider, 31 DateTime Function()? nowProvider,
27 void Function(String message)? logSink, 32 void Function(String message)? logSink,
  33 + int maxConcurrentFetches = defaultMaxConcurrentFetches,
28 }) : _remoteDataSource = 34 }) : _remoteDataSource =
29 remoteDataSource ?? const TodoOhosHealthRawDataRemoteDataSource(), 35 remoteDataSource ?? const TodoOhosHealthRawDataRemoteDataSource(),
30 _localStore = localStore ?? const TodoOhosHealthRawDataLocalStore(), 36 _localStore = localStore ?? const TodoOhosHealthRawDataLocalStore(),
31 _nowProvider = nowProvider ?? DateTime.now, 37 _nowProvider = nowProvider ?? DateTime.now,
32 - _logSink = logSink; 38 + _logSink = logSink,
  39 + _fetchLimiter = _AsyncLimiter(maxConcurrentFetches);
33 40
34 - static const int defaultLookbackDays = 183;  
35 - static const int incrementalBackfillDays = 7; 41 + static const int defaultLookbackMonths = 2;
36 static const int heartRateFetchChunkDays = 10; 42 static const int heartRateFetchChunkDays = 10;
37 static const int defaultFetchChunkDays = 30; 43 static const int defaultFetchChunkDays = 30;
  44 + static const int defaultMaxConcurrentFetches = 10;
38 static const String logMarker = '[OHOS_HEALTH_RAW_SYNC]'; 45 static const String logMarker = '[OHOS_HEALTH_RAW_SYNC]';
  46 + static const String profileLogMarker = '[OHOS_HEALTH_RAW_PROFILE]';
39 static final List<int> calculationDataTypes = List<int>.unmodifiable( 47 static final List<int> calculationDataTypes = List<int>.unmodifiable(
40 <int>[ 48 <int>[
41 for (final type in HuaweiHealthDataType.values) type.dataType, 49 for (final type in HuaweiHealthDataType.values) type.dataType,
@@ -48,6 +56,7 @@ class OhosHealthRawDataSyncService { @@ -48,6 +56,7 @@ class OhosHealthRawDataSyncService {
48 final OhosHealthRawDataLocalStore _localStore; 56 final OhosHealthRawDataLocalStore _localStore;
49 final DateTime Function() _nowProvider; 57 final DateTime Function() _nowProvider;
50 final void Function(String message)? _logSink; 58 final void Function(String message)? _logSink;
  59 + final _AsyncLimiter _fetchLimiter;
51 final Map<String, Future<OhosHealthRawDataSyncResult>> _runningSyncs = 60 final Map<String, Future<OhosHealthRawDataSyncResult>> _runningSyncs =
52 <String, Future<OhosHealthRawDataSyncResult>>{}; 61 <String, Future<OhosHealthRawDataSyncResult>>{};
53 62
@@ -56,17 +65,22 @@ class OhosHealthRawDataSyncService { @@ -56,17 +65,22 @@ class OhosHealthRawDataSyncService {
56 int? startTime, 65 int? startTime,
57 int? endTime, 66 int? endTime,
58 }) async { 67 }) async {
  68 + final totalStopwatch = Stopwatch()..start();
59 final resolvedEndTime = endTime ?? _unixSeconds(_nowProvider()); 69 final resolvedEndTime = endTime ?? _unixSeconds(_nowProvider());
60 final resolvedStartTime = await _resolveStartTime( 70 final resolvedStartTime = await _resolveStartTime(
61 dataType: dataType, 71 dataType: dataType,
62 requestedStartTime: startTime, 72 requestedStartTime: startTime,
63 endTime: resolvedEndTime, 73 endTime: resolvedEndTime,
64 ); 74 );
  75 + _profileLog(
  76 + 'syncRawData_resolved dataType=$dataType '
  77 + 'requestedStartTime=${startTime ?? ''} startTime=$resolvedStartTime '
  78 + 'endTime=$resolvedEndTime elapsedMs=${totalStopwatch.elapsedMilliseconds}',
  79 + );
65 80
66 if (resolvedEndTime < resolvedStartTime) { 81 if (resolvedEndTime < resolvedStartTime) {
67 throw ArgumentError.value(endTime, 'endTime'); 82 throw ArgumentError.value(endTime, 'endTime');
68 } 83 }
69 -  
70 final syncKey = '$dataType:$resolvedStartTime:$resolvedEndTime'; 84 final syncKey = '$dataType:$resolvedStartTime:$resolvedEndTime';
71 final running = _runningSyncs[syncKey]; 85 final running = _runningSyncs[syncKey];
72 if (running != null) { 86 if (running != null) {
@@ -74,9 +88,21 @@ class OhosHealthRawDataSyncService { @@ -74,9 +88,21 @@ class OhosHealthRawDataSyncService {
74 'sync_duplicate_join dataType=$dataType ' 88 'sync_duplicate_join dataType=$dataType '
75 'startTime=$resolvedStartTime endTime=$resolvedEndTime', 89 'startTime=$resolvedStartTime endTime=$resolvedEndTime',
76 ); 90 );
  91 + _profileLog(
  92 + 'syncRawData_duplicate_join dataType=$dataType '
  93 + 'startTime=$resolvedStartTime endTime=$resolvedEndTime',
  94 + );
77 return running; 95 return running;
78 } 96 }
79 97
  98 + _publishSyncEvent(
  99 + type: OhosHealthRawDataPipelineEventType.syncStarted,
  100 + flow: 'syncRawData',
  101 + dataType: dataType,
  102 + startTime: resolvedStartTime,
  103 + endTime: resolvedEndTime,
  104 + );
  105 +
80 final task = _syncResolvedRawData( 106 final task = _syncResolvedRawData(
81 dataType: dataType, 107 dataType: dataType,
82 startTime: resolvedStartTime, 108 startTime: resolvedStartTime,
@@ -84,12 +110,43 @@ class OhosHealthRawDataSyncService { @@ -84,12 +110,43 @@ class OhosHealthRawDataSyncService {
84 ); 110 );
85 _runningSyncs[syncKey] = task; 111 _runningSyncs[syncKey] = task;
86 try { 112 try {
87 - return await task; 113 + final result = await task;
  114 + _profileLog(
  115 + 'syncRawData_finish dataType=$dataType '
  116 + 'storedCount=${result.storedCount} pageCount=${result.pageCount} '
  117 + 'segmentCount=${result.segmentCount} '
  118 + 'elapsedMs=${totalStopwatch.elapsedMilliseconds}',
  119 + );
  120 + _publishSyncEvent(
  121 + type: OhosHealthRawDataPipelineEventType.syncSucceeded,
  122 + flow: 'syncRawData',
  123 + dataType: dataType,
  124 + startTime: resolvedStartTime,
  125 + endTime: resolvedEndTime,
  126 + elapsedMs: totalStopwatch.elapsedMilliseconds,
  127 + pageCount: result.pageCount,
  128 + storedCount: result.storedCount,
  129 + );
  130 + return result;
88 } catch (error, stackTrace) { 131 } catch (error, stackTrace) {
89 _log( 132 _log(
90 'sync_failed dataType=$dataType ' 133 'sync_failed dataType=$dataType '
91 'startTime=$resolvedStartTime endTime=$resolvedEndTime ' 134 'startTime=$resolvedStartTime endTime=$resolvedEndTime '
92 - 'error=$error stackTrace=$stackTrace', 135 + 'error=${_describeError(error)} stackTrace=$stackTrace',
  136 + );
  137 + _profileLog(
  138 + 'syncRawData_failed dataType=$dataType '
  139 + 'elapsedMs=${totalStopwatch.elapsedMilliseconds} '
  140 + 'error=${_describeError(error)}',
  141 + );
  142 + _publishSyncEvent(
  143 + type: OhosHealthRawDataPipelineEventType.syncFailed,
  144 + flow: 'syncRawData',
  145 + dataType: dataType,
  146 + startTime: resolvedStartTime,
  147 + endTime: resolvedEndTime,
  148 + elapsedMs: totalStopwatch.elapsedMilliseconds,
  149 + error: _describeError(error),
93 ); 150 );
94 rethrow; 151 rethrow;
95 } finally { 152 } finally {
@@ -108,21 +165,34 @@ class OhosHealthRawDataSyncService { @@ -108,21 +165,34 @@ class OhosHealthRawDataSyncService {
108 int? endTime, 165 int? endTime,
109 List<int>? dataTypes, 166 List<int>? dataTypes,
110 }) async { 167 }) async {
  168 + final totalStopwatch = Stopwatch()..start();
111 final resolvedEndTime = endTime ?? _unixSeconds(_nowProvider()); 169 final resolvedEndTime = endTime ?? _unixSeconds(_nowProvider());
  170 + final earliestStartTime = _twoMonthLookbackStart(resolvedEndTime);
  171 + final eventStartTime = startTime == null
  172 + ? earliestStartTime
  173 + : math.max(startTime, earliestStartTime);
112 final resolvedDataTypes = dataTypes ?? calculationDataTypes; 174 final resolvedDataTypes = dataTypes ?? calculationDataTypes;
113 _log( 175 _log(
114 'calculation_sync_start dataTypes=${resolvedDataTypes.join(',')} ' 176 'calculation_sync_start dataTypes=${resolvedDataTypes.join(',')} '
115 'startTime=${startTime ?? ''} endTime=$resolvedEndTime', 177 'startTime=${startTime ?? ''} endTime=$resolvedEndTime',
116 ); 178 );
  179 + _publishSyncEvent(
  180 + type: OhosHealthRawDataPipelineEventType.syncStarted,
  181 + flow: 'syncCalculationRawData',
  182 + dataTypes: resolvedDataTypes,
  183 + startTime: eventStartTime,
  184 + endTime: resolvedEndTime,
  185 + );
117 final List<OhosHealthRawDataSyncResult> results; 186 final List<OhosHealthRawDataSyncResult> results;
118 try { 187 try {
119 - final activityGoalFuture = _remoteDataSource.fetchActivityGoal();  
120 - final rawFetchesFuture = Future.wait( 188 + final activityGoalFuture = _fetchAndStoreActivityGoalForCalculationSync();
  189 + final rawSyncFuture = Future.wait(
121 resolvedDataTypes.map( 190 resolvedDataTypes.map(
122 - (dataType) => _fetchRawDataForSync( 191 + (dataType) => _syncRawDataForCalculation(
123 dataType: dataType, 192 dataType: dataType,
124 startTime: startTime, 193 startTime: startTime,
125 endTime: resolvedEndTime, 194 endTime: resolvedEndTime,
  195 + storeGate: activityGoalFuture,
126 ), 196 ),
127 ), 197 ),
128 eagerError: true, 198 eagerError: true,
@@ -130,29 +200,30 @@ class OhosHealthRawDataSyncService { @@ -130,29 +200,30 @@ class OhosHealthRawDataSyncService {
130 final fetched = await Future.wait<Object?>( 200 final fetched = await Future.wait<Object?>(
131 <Future<Object?>>[ 201 <Future<Object?>>[
132 activityGoalFuture, 202 activityGoalFuture,
133 - rawFetchesFuture, 203 + rawSyncFuture,
134 ], 204 ],
135 eagerError: true, 205 eagerError: true,
136 ); 206 );
137 - final activityGoal = fetched[0] as V2ActivityTarget?;  
138 - final rawFetches = fetched[1] as List<_FetchedRawDataSync>;  
139 - if (activityGoal != null) {  
140 - await _localStore.upsertActivityGoal(activityGoal);  
141 - _log(  
142 - 'calculation_sync_activity_goal_stored '  
143 - 'move=${activityGoal.move} step=${activityGoal.step} '  
144 - 'exercise=${activityGoal.exercise} stand=${activityGoal.stand}',  
145 - );  
146 - }  
147 - results = <OhosHealthRawDataSyncResult>[];  
148 - for (final fetch in rawFetches) {  
149 - results.add(await _storeFetchedRawData(fetch));  
150 - } 207 + results = fetched[1] as List<OhosHealthRawDataSyncResult>;
151 } catch (error, stackTrace) { 208 } catch (error, stackTrace) {
152 _log( 209 _log(
153 'calculation_sync_failed dataTypes=${resolvedDataTypes.join(',')} ' 210 'calculation_sync_failed dataTypes=${resolvedDataTypes.join(',')} '
154 'startTime=${startTime ?? ''} endTime=$resolvedEndTime ' 211 'startTime=${startTime ?? ''} endTime=$resolvedEndTime '
155 - 'error=$error stackTrace=$stackTrace', 212 + 'error=${_describeError(error)} stackTrace=$stackTrace',
  213 + );
  214 + _profileLog(
  215 + 'calculationSync_failed dataTypes=${resolvedDataTypes.join(',')} '
  216 + 'elapsedMs=${totalStopwatch.elapsedMilliseconds} '
  217 + 'error=${_describeError(error)}',
  218 + );
  219 + _publishSyncEvent(
  220 + type: OhosHealthRawDataPipelineEventType.syncFailed,
  221 + flow: 'syncCalculationRawData',
  222 + dataTypes: resolvedDataTypes,
  223 + startTime: eventStartTime,
  224 + endTime: resolvedEndTime,
  225 + elapsedMs: totalStopwatch.elapsedMilliseconds,
  226 + error: _describeError(error),
156 ); 227 );
157 rethrow; 228 rethrow;
158 } 229 }
@@ -168,14 +239,69 @@ class OhosHealthRawDataSyncService { @@ -168,14 +239,69 @@ class OhosHealthRawDataSyncService {
168 'calculation_sync_finish dataTypes=${resolvedDataTypes.join(',')} ' 239 'calculation_sync_finish dataTypes=${resolvedDataTypes.join(',')} '
169 'pageCount=$pageCount storedCount=$storedCount', 240 'pageCount=$pageCount storedCount=$storedCount',
170 ); 241 );
  242 + _profileLog(
  243 + 'calculationSync_finish dataTypes=${resolvedDataTypes.join(',')} '
  244 + 'pageCount=$pageCount storedCount=$storedCount '
  245 + 'elapsedMs=${totalStopwatch.elapsedMilliseconds}',
  246 + );
  247 + _publishSyncEvent(
  248 + type: OhosHealthRawDataPipelineEventType.syncSucceeded,
  249 + flow: 'syncCalculationRawData',
  250 + dataTypes: resolvedDataTypes,
  251 + startTime: eventStartTime,
  252 + endTime: resolvedEndTime,
  253 + elapsedMs: totalStopwatch.elapsedMilliseconds,
  254 + pageCount: pageCount,
  255 + storedCount: storedCount,
  256 + );
171 return results; 257 return results;
172 } 258 }
173 259
174 - Future<_FetchedRawDataSync> _fetchRawDataForSync({ 260 + Future<V2ActivityTarget?> _fetchActivityGoalForCalculationSync() async {
  261 + final stopwatch = Stopwatch()..start();
  262 + try {
  263 + final result = await _remoteDataSource.fetchActivityGoal();
  264 + _profileLog(
  265 + 'calculationSync_fetchActivityGoal_finish found=${result != null} '
  266 + 'elapsedMs=${stopwatch.elapsedMilliseconds}',
  267 + );
  268 + return result;
  269 + } catch (error) {
  270 + _profileLog(
  271 + 'calculationSync_fetchActivityGoal_failed '
  272 + 'elapsedMs=${stopwatch.elapsedMilliseconds} '
  273 + 'error=${_describeError(error)}',
  274 + );
  275 + rethrow;
  276 + }
  277 + }
  278 +
  279 + Future<V2ActivityTarget?>
  280 + _fetchAndStoreActivityGoalForCalculationSync() async {
  281 + final activityGoal = await _fetchActivityGoalForCalculationSync();
  282 + if (activityGoal != null) {
  283 + final storeGoalStopwatch = Stopwatch()..start();
  284 + await _localStore.upsertActivityGoal(activityGoal);
  285 + _profileLog(
  286 + 'calculationSync_storeActivityGoal elapsedMs='
  287 + '${storeGoalStopwatch.elapsedMilliseconds}',
  288 + );
  289 + _log(
  290 + 'calculation_sync_activity_goal_stored '
  291 + 'move=${activityGoal.move} step=${activityGoal.step} '
  292 + 'exercise=${activityGoal.exercise} stand=${activityGoal.stand}',
  293 + );
  294 + }
  295 + return activityGoal;
  296 + }
  297 +
  298 + Future<OhosHealthRawDataSyncResult> _syncRawDataForCalculation({
175 required int dataType, 299 required int dataType,
176 required int? startTime, 300 required int? startTime,
177 required int endTime, 301 required int endTime,
  302 + required Future<void> storeGate,
178 }) async { 303 }) async {
  304 + final stopwatch = Stopwatch()..start();
179 final resolvedStartTime = await _resolveStartTime( 305 final resolvedStartTime = await _resolveStartTime(
180 dataType: dataType, 306 dataType: dataType,
181 requestedStartTime: startTime, 307 requestedStartTime: startTime,
@@ -184,11 +310,33 @@ class OhosHealthRawDataSyncService { @@ -184,11 +310,33 @@ class OhosHealthRawDataSyncService {
184 if (endTime < resolvedStartTime) { 310 if (endTime < resolvedStartTime) {
185 throw ArgumentError.value(endTime, 'endTime'); 311 throw ArgumentError.value(endTime, 'endTime');
186 } 312 }
187 - return _fetchResolvedRawData(  
188 - dataType: dataType,  
189 - startTime: resolvedStartTime,  
190 - endTime: endTime, 313 + _profileLog(
  314 + 'fetchRawDataForSync_resolved dataType=$dataType '
  315 + 'requestedStartTime=${startTime ?? ''} startTime=$resolvedStartTime '
  316 + 'endTime=$endTime resolveElapsedMs=${stopwatch.elapsedMilliseconds}',
191 ); 317 );
  318 + try {
  319 + final result = await _syncResolvedRawData(
  320 + dataType: dataType,
  321 + startTime: resolvedStartTime,
  322 + endTime: endTime,
  323 + storeGate: storeGate,
  324 + );
  325 + _profileLog(
  326 + 'fetchRawDataForSync_finish dataType=$dataType '
  327 + 'segments=${result.segmentCount} pageCount=${result.pageCount} '
  328 + 'storedCount=${result.storedCount} '
  329 + 'elapsedMs=${stopwatch.elapsedMilliseconds}',
  330 + );
  331 + return result;
  332 + } catch (error) {
  333 + _profileLog(
  334 + 'fetchRawDataForSync_failed dataType=$dataType '
  335 + 'elapsedMs=${stopwatch.elapsedMilliseconds} '
  336 + 'error=${_describeError(error)}',
  337 + );
  338 + rethrow;
  339 + }
192 } 340 }
193 341
194 Future<List<OhosHealthRawDataItem>> queryRawData({ 342 Future<List<OhosHealthRawDataItem>> queryRawData({
@@ -218,7 +366,8 @@ class OhosHealthRawDataSyncService { @@ -218,7 +366,8 @@ class OhosHealthRawDataSyncService {
218 } 366 }
219 } catch (error, stackTrace) { 367 } catch (error, stackTrace) {
220 _log( 368 _log(
221 - 'activity_goal_remote_failed error=$error stackTrace=$stackTrace', 369 + 'activity_goal_remote_failed error=${_describeError(error)} '
  370 + 'stackTrace=$stackTrace',
222 ); 371 );
223 } 372 }
224 } 373 }
@@ -235,20 +384,9 @@ class OhosHealthRawDataSyncService { @@ -235,20 +384,9 @@ class OhosHealthRawDataSyncService {
235 required int dataType, 384 required int dataType,
236 required int startTime, 385 required int startTime,
237 required int endTime, 386 required int endTime,
  387 + Future<void>? storeGate,
238 }) async { 388 }) async {
239 - final fetched = await _fetchResolvedRawData(  
240 - dataType: dataType,  
241 - startTime: startTime,  
242 - endTime: endTime,  
243 - );  
244 - return _storeFetchedRawData(fetched);  
245 - }  
246 -  
247 - Future<_FetchedRawDataSync> _fetchResolvedRawData({  
248 - required int dataType,  
249 - required int startTime,  
250 - required int endTime,  
251 - }) async { 389 + final stopwatch = Stopwatch()..start();
252 final fetchRanges = _splitIntoFetchRanges( 390 final fetchRanges = _splitIntoFetchRanges(
253 dataType: dataType, 391 dataType: dataType,
254 startTime: startTime, 392 startTime: startTime,
@@ -261,9 +399,9 @@ class OhosHealthRawDataSyncService { @@ -261,9 +399,9 @@ class OhosHealthRawDataSyncService {
261 'chunkDays=$fetchChunkDays rangeCount=${fetchRanges.length}', 399 'chunkDays=$fetchChunkDays rangeCount=${fetchRanges.length}',
262 ); 400 );
263 401
264 - final segments = await Future.wait(  
265 - <Future<_FetchedRawDataSegment>>[  
266 - for (var index = 0; index < fetchRanges.length; index++) 402 + final pending = <Future<_FetchedRawDataSegmentOutcome>>[
  403 + for (var index = 0; index < fetchRanges.length; index++)
  404 + _trackedSegmentFetch(
267 _fetchRawDataSegment( 405 _fetchRawDataSegment(
268 dataType: dataType, 406 dataType: dataType,
269 range: fetchRanges[index], 407 range: fetchRanges[index],
@@ -271,16 +409,97 @@ class OhosHealthRawDataSyncService { @@ -271,16 +409,97 @@ class OhosHealthRawDataSyncService {
271 segmentCount: fetchRanges.length, 409 segmentCount: fetchRanges.length,
272 fetchChunkDays: fetchChunkDays, 410 fetchChunkDays: fetchChunkDays,
273 ), 411 ),
274 - ],  
275 - eagerError: true, 412 + ),
  413 + ];
  414 + var pageCount = 0;
  415 + var fetchedItems = 0;
  416 + var storedCount = 0;
  417 + int? earliestStoredTime;
  418 + try {
  419 + while (pending.isNotEmpty) {
  420 + final outcome = await Future.any(pending);
  421 + pending.remove(outcome.task);
  422 + final error = outcome.error;
  423 + if (error != null) {
  424 + Error.throwWithStackTrace(
  425 + error, outcome.stackTrace ?? StackTrace.current);
  426 + }
  427 + final segment = outcome.segment!;
  428 + final page = segment.page;
  429 + pageCount += 1;
  430 + fetchedItems += page.items.length;
  431 + if (page.items.isNotEmpty) {
  432 + if (storeGate != null) await storeGate;
  433 + final pageStoreStopwatch = Stopwatch()..start();
  434 + final pageStoredCount = await _localStore.upsertRawDataBatch(
  435 + dataType: dataType,
  436 + items: page.items,
  437 + );
  438 + storedCount += pageStoredCount;
  439 + if (pageStoredCount > 0) {
  440 + for (final item in page.items) {
  441 + final keyTime = _dedupeKeyTime(dataType: dataType, item: item);
  442 + earliestStoredTime = earliestStoredTime == null
  443 + ? keyTime
  444 + : math.min(earliestStoredTime, keyTime);
  445 + }
  446 + }
  447 + _profileLog(
  448 + 'page_store_finish dataType=$dataType '
  449 + 'segment=${segment.segmentIndex + 1}/${fetchRanges.length} '
  450 + 'fetchedItems=${page.items.length} storedItems=$pageStoredCount '
  451 + 'elapsedMs=${pageStoreStopwatch.elapsedMilliseconds}',
  452 + );
  453 + _log(
  454 + 'page_stored dataType=$dataType '
  455 + 'segment=${segment.segmentIndex + 1}/${fetchRanges.length} '
  456 + 'fetchedItems=${page.items.length} '
  457 + 'storedItems=$pageStoredCount totalStored=$storedCount',
  458 + );
  459 + }
  460 +
  461 + _log(
  462 + 'segment_finish dataType=$dataType '
  463 + 'segment=${segment.segmentIndex + 1}/${fetchRanges.length}',
  464 + );
  465 + }
  466 + } catch (error) {
  467 + _profileLog(
  468 + 'syncResolvedRawData_failed dataType=$dataType '
  469 + 'rangeCount=${fetchRanges.length} '
  470 + 'elapsedMs=${stopwatch.elapsedMilliseconds} '
  471 + 'error=${_describeError(error)}',
  472 + );
  473 + rethrow;
  474 + }
  475 +
  476 + _log(
  477 + 'sync_finish dataType=$dataType '
  478 + 'startTime=$startTime endTime=$endTime '
  479 + 'chunkDays=$fetchChunkDays rangeCount=${fetchRanges.length} '
  480 + 'pageCount=$pageCount '
  481 + 'storedCount=$storedCount',
276 ); 482 );
277 - segments.sort((a, b) => a.segmentIndex.compareTo(b.segmentIndex));  
278 - return _FetchedRawDataSync( 483 + _profileLog(
  484 + 'syncResolvedRawData_finish dataType=$dataType '
  485 + 'pageCount=$pageCount fetchedItems=$fetchedItems '
  486 + 'storedCount=$storedCount '
  487 + 'elapsedMs=${stopwatch.elapsedMilliseconds}',
  488 + );
  489 + _profileLog(
  490 + 'storeFetchedRawData_finish dataType=$dataType '
  491 + 'pageCount=$pageCount fetchedItems=$fetchedItems '
  492 + 'storedCount=$storedCount '
  493 + 'elapsedMs=${stopwatch.elapsedMilliseconds}',
  494 + );
  495 + return OhosHealthRawDataSyncResult(
279 dataType: dataType, 496 dataType: dataType,
280 startTime: startTime, 497 startTime: startTime,
281 endTime: endTime, 498 endTime: endTime,
282 - fetchChunkDays: fetchChunkDays,  
283 - segments: segments, 499 + segmentCount: fetchRanges.length,
  500 + pageCount: pageCount,
  501 + storedCount: storedCount,
  502 + earliestStoredTime: earliestStoredTime,
284 ); 503 );
285 } 504 }
286 505
@@ -291,6 +510,8 @@ class OhosHealthRawDataSyncService { @@ -291,6 +510,8 @@ class OhosHealthRawDataSyncService {
291 required int segmentCount, 510 required int segmentCount,
292 required int fetchChunkDays, 511 required int fetchChunkDays,
293 }) async { 512 }) async {
  513 + final stopwatch = Stopwatch()..start();
  514 + var queueWaitMs = 0;
294 _log( 515 _log(
295 'segment_start dataType=$dataType ' 516 'segment_start dataType=$dataType '
296 'segment=${segmentIndex + 1}/$segmentCount ' 517 'segment=${segmentIndex + 1}/$segmentCount '
@@ -301,16 +522,49 @@ class OhosHealthRawDataSyncService { @@ -301,16 +522,49 @@ class OhosHealthRawDataSyncService {
301 'apiEndDate=${_exclusiveEndDateKeyFromUnixSeconds(range.endTime)}', 522 'apiEndDate=${_exclusiveEndDateKeyFromUnixSeconds(range.endTime)}',
302 ); 523 );
303 524
304 - final page = await _remoteDataSource.fetchRawDataPage(  
305 - dataType: dataType,  
306 - startTime: range.startTime,  
307 - endTime: range.endTime,  
308 - ); 525 + final OhosHealthRawDataPage page;
  526 + try {
  527 + page = await _fetchLimiter.run(
  528 + () {
  529 + queueWaitMs = stopwatch.elapsedMilliseconds;
  530 + _profileLog(
  531 + 'segment_fetch_acquired dataType=$dataType '
  532 + 'segment=${segmentIndex + 1}/$segmentCount '
  533 + 'queueWaitMs=$queueWaitMs activeFetches='
  534 + '${_fetchLimiter.activeCount}',
  535 + );
  536 + return _remoteDataSource.fetchRawDataPage(
  537 + dataType: dataType,
  538 + startTime: range.startTime,
  539 + endTime: range.endTime,
  540 + );
  541 + },
  542 + );
  543 + } catch (error) {
  544 + _profileLog(
  545 + 'segment_fetch_failed dataType=$dataType '
  546 + 'segment=${segmentIndex + 1}/$segmentCount '
  547 + 'startTime=${range.startTime} endTime=${range.endTime} '
  548 + 'queueWaitMs=$queueWaitMs '
  549 + 'requestElapsedMs=${stopwatch.elapsedMilliseconds - queueWaitMs} '
  550 + 'elapsedMs=${stopwatch.elapsedMilliseconds} '
  551 + 'error=${_describeError(error)}',
  552 + );
  553 + rethrow;
  554 + }
309 _log( 555 _log(
310 'page_fetched dataType=$dataType ' 556 'page_fetched dataType=$dataType '
311 'segment=${segmentIndex + 1}/$segmentCount ' 557 'segment=${segmentIndex + 1}/$segmentCount '
312 'items=${page.items.length}', 558 'items=${page.items.length}',
313 ); 559 );
  560 + _profileLog(
  561 + 'segment_fetch_finish dataType=$dataType '
  562 + 'segment=${segmentIndex + 1}/$segmentCount '
  563 + 'startTime=${range.startTime} endTime=${range.endTime} '
  564 + 'items=${page.items.length} queueWaitMs=$queueWaitMs '
  565 + 'requestElapsedMs=${stopwatch.elapsedMilliseconds - queueWaitMs} '
  566 + 'elapsedMs=${stopwatch.elapsedMilliseconds}',
  567 + );
314 568
315 return _FetchedRawDataSegment( 569 return _FetchedRawDataSegment(
316 segmentIndex: segmentIndex, 570 segmentIndex: segmentIndex,
@@ -319,52 +573,23 @@ class OhosHealthRawDataSyncService { @@ -319,52 +573,23 @@ class OhosHealthRawDataSyncService {
319 ); 573 );
320 } 574 }
321 575
322 - Future<OhosHealthRawDataSyncResult> _storeFetchedRawData(  
323 - _FetchedRawDataSync fetched,  
324 - ) async {  
325 - var pageCount = 0;  
326 - var storedCount = 0;  
327 -  
328 - for (final segment in fetched.segments) {  
329 - final page = segment.page;  
330 - pageCount += 1;  
331 - if (page.items.isNotEmpty) {  
332 - final pageStoredCount = await _localStore.upsertRawDataBatch(  
333 - dataType: fetched.dataType,  
334 - items: page.items,  
335 - );  
336 - storedCount += pageStoredCount;  
337 - _log(  
338 - 'page_stored dataType=${fetched.dataType} '  
339 - 'segment=${segment.segmentIndex + 1}/${fetched.segmentCount} '  
340 - 'fetchedItems=${page.items.length} '  
341 - 'storedItems=$pageStoredCount totalStored=$storedCount',  
342 - );  
343 - }  
344 -  
345 - _log(  
346 - 'segment_finish dataType=${fetched.dataType} '  
347 - 'segment=${segment.segmentIndex + 1}/${fetched.segmentCount}',  
348 - );  
349 - }  
350 -  
351 - _log(  
352 - 'sync_finish dataType=${fetched.dataType} '  
353 - 'startTime=${fetched.startTime} endTime=${fetched.endTime} '  
354 - 'chunkDays=${fetched.fetchChunkDays} rangeCount=${fetched.segmentCount} '  
355 - 'pageCount=$pageCount '  
356 - 'storedCount=$storedCount',  
357 - );  
358 -  
359 - return OhosHealthRawDataSyncResult(  
360 - dataType: fetched.dataType,  
361 - startTime: fetched.startTime,  
362 - endTime: fetched.endTime,  
363 - segmentCount: fetched.segmentCount,  
364 - pageCount: pageCount,  
365 - storedCount: storedCount,  
366 - earliestStoredTime: storedCount > 0 ? fetched.earliestItemTime : null, 576 + Future<_FetchedRawDataSegmentOutcome> _trackedSegmentFetch(
  577 + Future<_FetchedRawDataSegment> future,
  578 + ) {
  579 + late final Future<_FetchedRawDataSegmentOutcome> tracked;
  580 + tracked = future.then(
  581 + (segment) => _FetchedRawDataSegmentOutcome(
  582 + task: tracked,
  583 + segment: segment,
  584 + ),
  585 + onError: (Object error, StackTrace stackTrace) =>
  586 + _FetchedRawDataSegmentOutcome(
  587 + task: tracked,
  588 + error: error,
  589 + stackTrace: stackTrace,
  590 + ),
367 ); 591 );
  592 + return tracked;
368 } 593 }
369 594
370 Future<int> _resolveStartTime({ 595 Future<int> _resolveStartTime({
@@ -373,22 +598,18 @@ class OhosHealthRawDataSyncService { @@ -373,22 +598,18 @@ class OhosHealthRawDataSyncService {
373 required int endTime, 598 required int endTime,
374 }) async { 599 }) async {
375 final latestDataTime = await _localStore.latestDataTime(dataType: dataType); 600 final latestDataTime = await _localStore.latestDataTime(dataType: dataType);
  601 + final earliestStartTime = _twoMonthLookbackStart(endTime);
376 final int baseStartTime; 602 final int baseStartTime;
377 if (latestDataTime != null) { 603 if (latestDataTime != null) {
378 - final backfillStart = math.max(  
379 - 0,  
380 - latestDataTime - incrementalBackfillDays * Duration.secondsPerDay,  
381 - );  
382 baseStartTime = requestedStartTime == null 604 baseStartTime = requestedStartTime == null
383 - ? backfillStart  
384 - : math.min(requestedStartTime, backfillStart); 605 + ? latestDataTime
  606 + : math.min(requestedStartTime, latestDataTime);
385 } else { 607 } else {
386 - baseStartTime = requestedStartTime ??  
387 - _unixSeconds(  
388 - _nowProvider().subtract(const Duration(days: defaultLookbackDays)),  
389 - ); 608 + baseStartTime = requestedStartTime ?? earliestStartTime;
390 } 609 }
391 - final resolvedStartTime = _startOfLocalDay(baseStartTime); 610 + final resolvedStartTime = _startOfLocalDay(
  611 + math.max(baseStartTime, earliestStartTime),
  612 + );
392 return resolvedStartTime > endTime ? endTime : resolvedStartTime; 613 return resolvedStartTime > endTime ? endTime : resolvedStartTime;
393 } 614 }
394 615
@@ -401,6 +622,12 @@ class OhosHealthRawDataSyncService { @@ -401,6 +622,12 @@ class OhosHealthRawDataSyncService {
401 return _unixSeconds(DateTime(dateTime.year, dateTime.month, dateTime.day)); 622 return _unixSeconds(DateTime(dateTime.year, dateTime.month, dateTime.day));
402 } 623 }
403 624
  625 + int _twoMonthLookbackStart(int endTime) {
  626 + final endDate = DateTime.fromMillisecondsSinceEpoch(endTime * 1000);
  627 + return _unixSeconds(DateTime(
  628 + endDate.year, endDate.month - defaultLookbackMonths, endDate.day));
  629 + }
  630 +
404 List<OhosHealthRawDataFetchRange> _splitIntoFetchRanges({ 631 List<OhosHealthRawDataFetchRange> _splitIntoFetchRanges({
405 required int dataType, 632 required int dataType,
406 required int startTime, 633 required int startTime,
@@ -450,6 +677,40 @@ class OhosHealthRawDataSyncService { @@ -450,6 +677,40 @@ class OhosHealthRawDataSyncService {
450 // AppLogger may not be initialized in isolated test/bootstrap contexts. 677 // AppLogger may not be initialized in isolated test/bootstrap contexts.
451 } 678 }
452 } 679 }
  680 +
  681 + void _profileLog(String message) {
  682 + if (!kDebugMode) return;
  683 + _log('$profileLogMarker $message');
  684 + }
  685 +
  686 + void _publishSyncEvent({
  687 + required OhosHealthRawDataPipelineEventType type,
  688 + required String flow,
  689 + int? dataType,
  690 + List<int>? dataTypes,
  691 + int? startTime,
  692 + int? endTime,
  693 + int? elapsedMs,
  694 + int? pageCount,
  695 + int? storedCount,
  696 + String? error,
  697 + }) {
  698 + OhosHealthRawDataPipelineEvents.publish(
  699 + OhosHealthRawDataPipelineEvent(
  700 + type: type,
  701 + flow: flow,
  702 + occurredAt: _nowProvider(),
  703 + dataType: dataType,
  704 + dataTypes: dataTypes == null ? null : List<int>.unmodifiable(dataTypes),
  705 + startTime: startTime,
  706 + endTime: endTime,
  707 + elapsedMs: elapsedMs,
  708 + pageCount: pageCount,
  709 + storedCount: storedCount,
  710 + error: error,
  711 + ),
  712 + );
  713 + }
453 } 714 }
454 715
455 abstract class OhosHealthRawDataRemoteDataSource { 716 abstract class OhosHealthRawDataRemoteDataSource {
@@ -528,7 +789,7 @@ class OhosHarmonyHealthRawDataRemoteDataSource @@ -528,7 +789,7 @@ class OhosHarmonyHealthRawDataRemoteDataSource
528 ), 789 ),
529 ), 790 ),
530 AppFailure<HmSleepData>(:final error) => throw StateError( 791 AppFailure<HmSleepData>(:final error) => throw StateError(
531 - 'OHOS sleep raw data fetch failed: $error', 792 + 'OHOS sleep raw data fetch failed: ${_describeAppError(error)}',
532 ), 793 ),
533 }; 794 };
534 } 795 }
@@ -541,7 +802,7 @@ class OhosHarmonyHealthRawDataRemoteDataSource @@ -541,7 +802,7 @@ class OhosHarmonyHealthRawDataRemoteDataSource
541 ), 802 ),
542 ), 803 ),
543 AppFailure<HmWorkoutData>(:final error) => throw StateError( 804 AppFailure<HmWorkoutData>(:final error) => throw StateError(
544 - 'OHOS workout raw data fetch failed: $error', 805 + 'OHOS workout raw data fetch failed: ${_describeAppError(error)}',
545 ), 806 ),
546 }; 807 };
547 } 808 }
@@ -560,7 +821,7 @@ class OhosHarmonyHealthRawDataRemoteDataSource @@ -560,7 +821,7 @@ class OhosHarmonyHealthRawDataRemoteDataSource
560 ), 821 ),
561 ), 822 ),
562 AppFailure<HmHealthData>(:final error) => throw StateError( 823 AppFailure<HmHealthData>(:final error) => throw StateError(
563 - 'OHOS health raw data fetch failed: $error', 824 + 'OHOS health raw data fetch failed: ${_describeAppError(error)}',
564 ), 825 ),
565 }; 826 };
566 } 827 }
@@ -571,7 +832,7 @@ class OhosHarmonyHealthRawDataRemoteDataSource @@ -571,7 +832,7 @@ class OhosHarmonyHealthRawDataRemoteDataSource
571 return switch (result) { 832 return switch (result) {
572 AppSuccess<V2ActivityTarget>(:final data) => data, 833 AppSuccess<V2ActivityTarget>(:final data) => data,
573 AppFailure<V2ActivityTarget>(:final error) => throw StateError( 834 AppFailure<V2ActivityTarget>(:final error) => throw StateError(
574 - 'OHOS activity goal fetch failed: $error', 835 + 'OHOS activity goal fetch failed: ${_describeAppError(error)}',
575 ), 836 ),
576 }; 837 };
577 } 838 }
@@ -726,35 +987,6 @@ class OhosHealthRawDataFetchRange { @@ -726,35 +987,6 @@ class OhosHealthRawDataFetchRange {
726 final int endTime; 987 final int endTime;
727 } 988 }
728 989
729 -class _FetchedRawDataSync {  
730 - const _FetchedRawDataSync({  
731 - required this.dataType,  
732 - required this.startTime,  
733 - required this.endTime,  
734 - required this.fetchChunkDays,  
735 - required this.segments,  
736 - });  
737 -  
738 - final int dataType;  
739 - final int startTime;  
740 - final int endTime;  
741 - final int fetchChunkDays;  
742 - final List<_FetchedRawDataSegment> segments;  
743 -  
744 - int get segmentCount => segments.length;  
745 -  
746 - int? get earliestItemTime {  
747 - int? earliest;  
748 - for (final segment in segments) {  
749 - for (final item in segment.page.items) {  
750 - final keyTime = _dedupeKeyTime(dataType: dataType, item: item);  
751 - earliest = earliest == null ? keyTime : math.min(earliest, keyTime);  
752 - }  
753 - }  
754 - return earliest;  
755 - }  
756 -}  
757 -  
758 class _FetchedRawDataSegment { 990 class _FetchedRawDataSegment {
759 const _FetchedRawDataSegment({ 991 const _FetchedRawDataSegment({
760 required this.segmentIndex, 992 required this.segmentIndex,
@@ -767,6 +999,20 @@ class _FetchedRawDataSegment { @@ -767,6 +999,20 @@ class _FetchedRawDataSegment {
767 final OhosHealthRawDataPage page; 999 final OhosHealthRawDataPage page;
768 } 1000 }
769 1001
  1002 +class _FetchedRawDataSegmentOutcome {
  1003 + const _FetchedRawDataSegmentOutcome({
  1004 + required this.task,
  1005 + this.segment,
  1006 + this.error,
  1007 + this.stackTrace,
  1008 + });
  1009 +
  1010 + final Future<_FetchedRawDataSegmentOutcome> task;
  1011 + final _FetchedRawDataSegment? segment;
  1012 + final Object? error;
  1013 + final StackTrace? stackTrace;
  1014 +}
  1015 +
770 class OhosHealthRawDataSyncResult { 1016 class OhosHealthRawDataSyncResult {
771 const OhosHealthRawDataSyncResult({ 1017 const OhosHealthRawDataSyncResult({
772 required this.dataType, 1018 required this.dataType,
@@ -832,3 +1078,79 @@ int _exclusiveEndDateKeyFromUnixSeconds(int seconds) { @@ -832,3 +1078,79 @@ int _exclusiveEndDateKeyFromUnixSeconds(int seconds) {
832 int _dateKeyFromDateTime(DateTime dateTime) { 1078 int _dateKeyFromDateTime(DateTime dateTime) {
833 return dateTime.year * 10000 + dateTime.month * 100 + dateTime.day; 1079 return dateTime.year * 10000 + dateTime.month * 100 + dateTime.day;
834 } 1080 }
  1081 +
  1082 +String _describeError(Object error) {
  1083 + if (error is AppError) return _describeAppError(error);
  1084 + return error.toString();
  1085 +}
  1086 +
  1087 +String _describeAppError(AppError error) {
  1088 + return switch (error) {
  1089 + AppNetworkError() => _describeAppNetworkError(error),
  1090 + AppHttpError(
  1091 + :final statusCode,
  1092 + :final businessCode,
  1093 + :final businessMessage,
  1094 + :final cause,
  1095 + ) =>
  1096 + 'AppHttpError(statusCode=$statusCode, businessCode=$businessCode, '
  1097 + 'businessMessage=$businessMessage, cause=$cause)',
  1098 + AppCancelledError() => 'AppCancelledError',
  1099 + AppUnknownError(:final cause) => 'AppUnknownError(cause=$cause)',
  1100 + };
  1101 +}
  1102 +
  1103 +String _describeAppNetworkError(AppNetworkError error) {
  1104 + final cause = error.cause;
  1105 + if (cause is DioException) {
  1106 + return 'AppNetworkError('
  1107 + 'dioType=${cause.type}, '
  1108 + 'message=${cause.message}, '
  1109 + 'uri=${cause.requestOptions.uri}, '
  1110 + 'method=${cause.requestOptions.method}, '
  1111 + 'statusCode=${cause.response?.statusCode}'
  1112 + ')';
  1113 + }
  1114 + return 'AppNetworkError(cause=$cause)';
  1115 +}
  1116 +
  1117 +class _AsyncLimiter {
  1118 + _AsyncLimiter(this._maxConcurrent) {
  1119 + if (_maxConcurrent <= 0) {
  1120 + throw ArgumentError.value(_maxConcurrent, 'maxConcurrentFetches');
  1121 + }
  1122 + }
  1123 +
  1124 + final int _maxConcurrent;
  1125 + final Queue<Completer<void>> _waiters = Queue<Completer<void>>();
  1126 + int _activeCount = 0;
  1127 +
  1128 + int get activeCount => _activeCount;
  1129 +
  1130 + Future<T> run<T>(Future<T> Function() action) async {
  1131 + await _acquire();
  1132 + try {
  1133 + return await action();
  1134 + } finally {
  1135 + _release();
  1136 + }
  1137 + }
  1138 +
  1139 + Future<void> _acquire() {
  1140 + if (_activeCount < _maxConcurrent) {
  1141 + _activeCount += 1;
  1142 + return Future<void>.value();
  1143 + }
  1144 + final waiter = Completer<void>();
  1145 + _waiters.add(waiter);
  1146 + return waiter.future;
  1147 + }
  1148 +
  1149 + void _release() {
  1150 + if (_waiters.isNotEmpty) {
  1151 + _waiters.removeFirst().complete();
  1152 + return;
  1153 + }
  1154 + _activeCount -= 1;
  1155 + }
  1156 +}
  1 +import 'dart:io';
  2 +
  3 +import 'package:flutter/foundation.dart';
  4 +import 'package:path_provider/path_provider.dart';
  5 +import 'package:sqflite/sqflite.dart';
  6 +
  7 +import '../../../logging/app_logger.dart';
  8 +
  9 +class OhosSqliteWriteBenchmark {
  10 + const OhosSqliteWriteBenchmark();
  11 +
  12 + static const logMarker = '[OHOS_SQLITE_BENCH]';
  13 +
  14 + Future<String> run() async {
  15 + final totalStopwatch = Stopwatch()..start();
  16 + final tempDir = await getTemporaryDirectory();
  17 + final path =
  18 + '${tempDir.path}/ohos_sqlite_write_bench_${DateTime.now().millisecondsSinceEpoch}.sqlite';
  19 + final db = await openDatabase(path);
  20 + final results = <_BenchmarkResult>[];
  21 + try {
  22 + _log('start path=$path');
  23 + results.add(await _runBatchInsert(
  24 + db: db,
  25 + table: 'batch_false_500',
  26 + rowCount: 500,
  27 + noResult: false,
  28 + ));
  29 + results.add(await _runBatchInsert(
  30 + db: db,
  31 + table: 'batch_false_1000',
  32 + rowCount: 1000,
  33 + noResult: false,
  34 + ));
  35 + results.add(await _runBatchInsert(
  36 + db: db,
  37 + table: 'batch_false_2000',
  38 + rowCount: 2000,
  39 + noResult: false,
  40 + ));
  41 + results.add(await _runBatchInsert(
  42 + db: db,
  43 + table: 'batch_true_500',
  44 + rowCount: 500,
  45 + noResult: true,
  46 + ));
  47 + results.add(await _runBatchInsert(
  48 + db: db,
  49 + table: 'batch_true_1000',
  50 + rowCount: 1000,
  51 + noResult: true,
  52 + ));
  53 + results.add(await _runBatchInsert(
  54 + db: db,
  55 + table: 'batch_true_2000',
  56 + rowCount: 2000,
  57 + noResult: true,
  58 + ));
  59 + results.add(await _runChunkedBatchInsert(
  60 + db: db,
  61 + table: 'chunked_true_2000',
  62 + rowCount: 2000,
  63 + chunkSize: 500,
  64 + ));
  65 + results.add(await _runExistingKeyQuery(
  66 + db: db,
  67 + table: 'existing_key_query_2000',
  68 + rowCount: 2000,
  69 + chunkSize: 900,
  70 + ));
  71 + } finally {
  72 + await db.close();
  73 + try {
  74 + await File(path).delete();
  75 + } catch (_) {
  76 + // Best-effort cleanup for a debug-only benchmark database.
  77 + }
  78 + }
  79 +
  80 + final lines = <String>[
  81 + 'OHOS SQLite 写入性能测试完成,总耗时 ${totalStopwatch.elapsedMilliseconds}ms',
  82 + for (final result in results) result.summary,
  83 + ];
  84 + final report = lines.join('\n');
  85 + _log('finish totalMs=${totalStopwatch.elapsedMilliseconds}');
  86 + return report;
  87 + }
  88 +
  89 + Future<_BenchmarkResult> _runBatchInsert({
  90 + required Database db,
  91 + required String table,
  92 + required int rowCount,
  93 + required bool noResult,
  94 + }) async {
  95 + await _createRawTable(db, table);
  96 + final stopwatch = Stopwatch()..start();
  97 + await db.transaction((txn) async {
  98 + final batch = txn.batch();
  99 + for (var index = 0; index < rowCount; index += 1) {
  100 + batch.insert(
  101 + table,
  102 + _row(index),
  103 + conflictAlgorithm: ConflictAlgorithm.ignore,
  104 + );
  105 + }
  106 + await batch.commit(noResult: noResult);
  107 + });
  108 + return _finish(
  109 + name: 'batch(noResult:$noResult)',
  110 + rowCount: rowCount,
  111 + elapsedMs: stopwatch.elapsedMilliseconds,
  112 + );
  113 + }
  114 +
  115 + Future<_BenchmarkResult> _runChunkedBatchInsert({
  116 + required Database db,
  117 + required String table,
  118 + required int rowCount,
  119 + required int chunkSize,
  120 + }) async {
  121 + await _createRawTable(db, table);
  122 + final stopwatch = Stopwatch()..start();
  123 + for (var start = 0; start < rowCount; start += chunkSize) {
  124 + final end = (start + chunkSize).clamp(0, rowCount);
  125 + await db.transaction((txn) async {
  126 + final batch = txn.batch();
  127 + for (var index = start; index < end; index += 1) {
  128 + batch.insert(
  129 + table,
  130 + _row(index),
  131 + conflictAlgorithm: ConflictAlgorithm.ignore,
  132 + );
  133 + }
  134 + await batch.commit(noResult: true);
  135 + });
  136 + }
  137 + return _finish(
  138 + name: 'chunkedBatch(noResult:true,chunk:$chunkSize)',
  139 + rowCount: rowCount,
  140 + elapsedMs: stopwatch.elapsedMilliseconds,
  141 + );
  142 + }
  143 +
  144 + Future<_BenchmarkResult> _runExistingKeyQuery({
  145 + required Database db,
  146 + required String table,
  147 + required int rowCount,
  148 + required int chunkSize,
  149 + }) async {
  150 + await _createRawTable(db, table);
  151 + await db.transaction((txn) async {
  152 + final batch = txn.batch();
  153 + for (var index = 0; index < rowCount; index += 1) {
  154 + batch.insert(table, _row(index),
  155 + conflictAlgorithm: ConflictAlgorithm.ignore);
  156 + }
  157 + await batch.commit(noResult: true);
  158 + });
  159 +
  160 + final keys = List<int>.generate(rowCount, (index) => 1780000000 + index);
  161 + final stopwatch = Stopwatch()..start();
  162 + var found = 0;
  163 + for (var offset = 0; offset < keys.length; offset += chunkSize) {
  164 + final chunk = keys.skip(offset).take(chunkSize).toList(growable: false);
  165 + final placeholders = List<String>.filled(chunk.length, '?').join(',');
  166 + final rows = await db.query(
  167 + table,
  168 + columns: const <String>['time'],
  169 + where: 'time IN ($placeholders)',
  170 + whereArgs: chunk,
  171 + );
  172 + found += rows.length;
  173 + }
  174 + final elapsedMs = stopwatch.elapsedMilliseconds;
  175 + _log(
  176 + 'result name=existingKeyQuery rows=$rowCount found=$found '
  177 + 'chunkSize=$chunkSize elapsedMs=$elapsedMs',
  178 + );
  179 + return _BenchmarkResult(
  180 + name: 'existingKeyQuery(chunk:$chunkSize)',
  181 + rowCount: rowCount,
  182 + elapsedMs: elapsedMs,
  183 + );
  184 + }
  185 +
  186 + Future<void> _createRawTable(Database db, String table) async {
  187 + await db.execute('''
  188 +CREATE TABLE IF NOT EXISTS $table (
  189 + id INTEGER PRIMARY KEY AUTOINCREMENT,
  190 + time INTEGER NOT NULL,
  191 + value REAL,
  192 + is_asleep INTEGER,
  193 + date_key INTEGER NOT NULL DEFAULT 0,
  194 + create_time INTEGER NOT NULL,
  195 + UNIQUE (time)
  196 +)
  197 +''');
  198 + await db.execute(
  199 + 'CREATE INDEX IF NOT EXISTS idx_${table}_time ON $table(time)');
  200 + }
  201 +
  202 + Map<String, Object?> _row(int index) {
  203 + final time = 1780000000 + index;
  204 + return <String, Object?>{
  205 + 'time': time,
  206 + 'value': 60 + index % 80,
  207 + 'is_asleep': index % 5 == 0 ? 1 : 0,
  208 + 'date_key': 20260801 + index ~/ 1000,
  209 + 'create_time': 1780000000,
  210 + };
  211 + }
  212 +
  213 + _BenchmarkResult _finish({
  214 + required String name,
  215 + required int rowCount,
  216 + required int elapsedMs,
  217 + }) {
  218 + final rowsPerSecond =
  219 + elapsedMs == 0 ? rowCount * 1000.0 : rowCount * 1000 / elapsedMs;
  220 + _log(
  221 + 'result name=$name rows=$rowCount elapsedMs=$elapsedMs '
  222 + 'rowsPerSecond=${rowsPerSecond.toStringAsFixed(1)}',
  223 + );
  224 + return _BenchmarkResult(
  225 + name: name,
  226 + rowCount: rowCount,
  227 + elapsedMs: elapsedMs,
  228 + rowsPerSecond: rowsPerSecond,
  229 + );
  230 + }
  231 +
  232 + void _log(String message) {
  233 + final tagged = '$logMarker $message';
  234 + debugPrint(tagged);
  235 + try {
  236 + AppLogger.i(tagged);
  237 + } catch (_) {
  238 + // AppLogger may not be initialized in isolated debug/bootstrap contexts.
  239 + }
  240 + }
  241 +}
  242 +
  243 +class _BenchmarkResult {
  244 + const _BenchmarkResult({
  245 + required this.name,
  246 + required this.rowCount,
  247 + required this.elapsedMs,
  248 + this.rowsPerSecond,
  249 + });
  250 +
  251 + final String name;
  252 + final int rowCount;
  253 + final int elapsedMs;
  254 + final double? rowsPerSecond;
  255 +
  256 + String get summary {
  257 + final speed = rowsPerSecond;
  258 + if (speed == null) return '$name: $rowCount 条,${elapsedMs}ms';
  259 + return '$name: $rowCount 条,${elapsedMs}ms,${speed.toStringAsFixed(1)} 条/s';
  260 + }
  261 +}
@@ -6,7 +6,7 @@ packages: @@ -6,7 +6,7 @@ packages:
6 description: 6 description:
7 name: _fe_analyzer_shared 7 name: _fe_analyzer_shared
8 sha256: da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f 8 sha256: da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f
9 - url: "https://pub.dev" 9 + url: "https://pub.flutter-io.cn"
10 source: hosted 10 source: hosted
11 version: "85.0.0" 11 version: "85.0.0"
12 analyzer: 12 analyzer:
@@ -14,7 +14,7 @@ packages: @@ -14,7 +14,7 @@ packages:
14 description: 14 description:
15 name: analyzer 15 name: analyzer
16 sha256: "974859dc0ff5f37bc4313244b3218c791810d03ab3470a579580279ba971a48d" 16 sha256: "974859dc0ff5f37bc4313244b3218c791810d03ab3470a579580279ba971a48d"
17 - url: "https://pub.dev" 17 + url: "https://pub.flutter-io.cn"
18 source: hosted 18 source: hosted
19 version: "7.7.1" 19 version: "7.7.1"
20 archive: 20 archive:
@@ -22,7 +22,7 @@ packages: @@ -22,7 +22,7 @@ packages:
22 description: 22 description:
23 name: archive 23 name: archive
24 sha256: ace891da0862b0e4cabbb064ee3fd87b2728b898949fdb366d83fe98342c9f19 24 sha256: ace891da0862b0e4cabbb064ee3fd87b2728b898949fdb366d83fe98342c9f19
25 - url: "https://pub.dev" 25 + url: "https://pub.flutter-io.cn"
26 source: hosted 26 source: hosted
27 version: "4.2.0" 27 version: "4.2.0"
28 args: 28 args:
@@ -30,63 +30,79 @@ packages: @@ -30,63 +30,79 @@ packages:
30 description: 30 description:
31 name: args 31 name: args
32 sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 32 sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
33 - url: "https://pub.dev" 33 + url: "https://pub.flutter-io.cn"
34 source: hosted 34 source: hosted
35 version: "2.7.0" 35 version: "2.7.0"
36 async: 36 async:
37 dependency: transitive 37 dependency: transitive
38 description: 38 description:
39 name: async 39 name: async
40 - sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37  
41 - url: "https://pub.dev" 40 + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
  41 + url: "https://pub.flutter-io.cn"
42 source: hosted 42 source: hosted
43 - version: "2.13.1" 43 + version: "2.11.0"
44 boolean_selector: 44 boolean_selector:
45 dependency: transitive 45 dependency: transitive
46 description: 46 description:
47 name: boolean_selector 47 name: boolean_selector
48 - sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"  
49 - url: "https://pub.dev" 48 + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
  49 + url: "https://pub.flutter-io.cn"
50 source: hosted 50 source: hosted
51 - version: "2.1.2" 51 + version: "2.1.1"
52 build: 52 build:
53 dependency: transitive 53 dependency: transitive
54 description: 54 description:
55 name: build 55 name: build
56 - sha256: "825fed4d63050252a0b6e74f2d75844c4a85b664814be6993bd3493fb5239779"  
57 - url: "https://pub.dev" 56 + sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0
  57 + url: "https://pub.flutter-io.cn"
58 source: hosted 58 source: hosted
59 - version: "4.0.1" 59 + version: "2.4.2"
60 build_config: 60 build_config:
61 dependency: transitive 61 dependency: transitive
62 description: 62 description:
63 name: build_config 63 name: build_config
64 - sha256: "4f64382b97504dc2fcdf487d5aae33418e08b4703fc21249e4db6d804a4d0187"  
65 - url: "https://pub.dev" 64 + sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33"
  65 + url: "https://pub.flutter-io.cn"
66 source: hosted 66 source: hosted
67 - version: "1.2.0" 67 + version: "1.1.2"
68 build_daemon: 68 build_daemon:
69 dependency: transitive 69 dependency: transitive
70 description: 70 description:
71 name: build_daemon 71 name: build_daemon
72 - sha256: fd754058c342243718d5171a95f352cfc9fcf0cba8cfa26df67cb13a5836db78  
73 - url: "https://pub.dev" 72 + sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa"
  73 + url: "https://pub.flutter-io.cn"
74 source: hosted 74 source: hosted
75 - version: "4.1.2" 75 + version: "4.0.4"
  76 + build_resolvers:
  77 + dependency: transitive
  78 + description:
  79 + name: build_resolvers
  80 + sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0
  81 + url: "https://pub.flutter-io.cn"
  82 + source: hosted
  83 + version: "2.4.4"
76 build_runner: 84 build_runner:
77 dependency: "direct dev" 85 dependency: "direct dev"
78 description: 86 description:
79 name: build_runner 87 name: build_runner
80 - sha256: "4e54dbeefdc70691ba80b3bce3976af63b5425c8c07dface348dfee664a0edc1"  
81 - url: "https://pub.dev" 88 + sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99"
  89 + url: "https://pub.flutter-io.cn"
  90 + source: hosted
  91 + version: "2.4.15"
  92 + build_runner_core:
  93 + dependency: transitive
  94 + description:
  95 + name: build_runner_core
  96 + sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021"
  97 + url: "https://pub.flutter-io.cn"
82 source: hosted 98 source: hosted
83 - version: "2.9.0" 99 + version: "8.0.0"
84 built_collection: 100 built_collection:
85 dependency: transitive 101 dependency: transitive
86 description: 102 description:
87 name: built_collection 103 name: built_collection
88 sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" 104 sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100"
89 - url: "https://pub.dev" 105 + url: "https://pub.flutter-io.cn"
90 source: hosted 106 source: hosted
91 version: "5.1.1" 107 version: "5.1.1"
92 built_value: 108 built_value:
@@ -94,7 +110,7 @@ packages: @@ -94,7 +110,7 @@ packages:
94 description: 110 description:
95 name: built_value 111 name: built_value
96 sha256: f87ea98192116f7093cb214551ce1929caae0681fdba282b3d8b4462adee7bb7 112 sha256: f87ea98192116f7093cb214551ce1929caae0681fdba282b3d8b4462adee7bb7
97 - url: "https://pub.dev" 113 + url: "https://pub.flutter-io.cn"
98 source: hosted 114 source: hosted
99 version: "8.13.0" 115 version: "8.13.0"
100 cached_network_image: 116 cached_network_image:
@@ -102,7 +118,7 @@ packages: @@ -102,7 +118,7 @@ packages:
102 description: 118 description:
103 name: cached_network_image 119 name: cached_network_image
104 sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916" 120 sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916"
105 - url: "https://pub.dev" 121 + url: "https://pub.flutter-io.cn"
106 source: hosted 122 source: hosted
107 version: "3.4.1" 123 version: "3.4.1"
108 cached_network_image_platform_interface: 124 cached_network_image_platform_interface:
@@ -110,7 +126,7 @@ packages: @@ -110,7 +126,7 @@ packages:
110 description: 126 description:
111 name: cached_network_image_platform_interface 127 name: cached_network_image_platform_interface
112 sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829" 128 sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829"
113 - url: "https://pub.dev" 129 + url: "https://pub.flutter-io.cn"
114 source: hosted 130 source: hosted
115 version: "4.1.1" 131 version: "4.1.1"
116 cached_network_image_web: 132 cached_network_image_web:
@@ -118,103 +134,95 @@ packages: @@ -118,103 +134,95 @@ packages:
118 description: 134 description:
119 name: cached_network_image_web 135 name: cached_network_image_web
120 sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062" 136 sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062"
121 - url: "https://pub.dev" 137 + url: "https://pub.flutter-io.cn"
122 source: hosted 138 source: hosted
123 version: "1.3.1" 139 version: "1.3.1"
124 characters: 140 characters:
125 dependency: transitive 141 dependency: transitive
126 description: 142 description:
127 name: characters 143 name: characters
128 - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803  
129 - url: "https://pub.dev" 144 + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
  145 + url: "https://pub.flutter-io.cn"
130 source: hosted 146 source: hosted
131 - version: "1.4.0" 147 + version: "1.3.0"
132 checked_yaml: 148 checked_yaml:
133 dependency: transitive 149 dependency: transitive
134 description: 150 description:
135 name: checked_yaml 151 name: checked_yaml
136 - sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f"  
137 - url: "https://pub.dev" 152 + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
  153 + url: "https://pub.flutter-io.cn"
138 source: hosted 154 source: hosted
139 - version: "2.0.4" 155 + version: "2.0.3"
140 clock: 156 clock:
141 dependency: transitive 157 dependency: transitive
142 description: 158 description:
143 name: clock 159 name: clock
144 - sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b  
145 - url: "https://pub.dev"  
146 - source: hosted  
147 - version: "1.1.2"  
148 - code_assets:  
149 - dependency: transitive  
150 - description:  
151 - name: code_assets  
152 - sha256: cfd4f5f575a49c5f10ca856e9846073f1e6c3ee94912377eea5f6cefc5272941  
153 - url: "https://pub.dev" 160 + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
  161 + url: "https://pub.flutter-io.cn"
154 source: hosted 162 source: hosted
155 - version: "2.0.0" 163 + version: "1.1.1"
156 code_builder: 164 code_builder:
157 dependency: transitive 165 dependency: transitive
158 description: 166 description:
159 name: code_builder 167 name: code_builder
160 - sha256: aa5932e94c6c39c2f9ec4e5e06dfdd11a9430a61f6c41b6ba75b28ce0c481baf  
161 - url: "https://pub.dev" 168 + sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e"
  169 + url: "https://pub.flutter-io.cn"
162 source: hosted 170 source: hosted
163 - version: "4.12.0" 171 + version: "4.10.1"
164 collection: 172 collection:
165 dependency: transitive 173 dependency: transitive
166 description: 174 description:
167 name: collection 175 name: collection
168 - sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"  
169 - url: "https://pub.dev" 176 + sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf
  177 + url: "https://pub.flutter-io.cn"
170 source: hosted 178 source: hosted
171 - version: "1.19.1" 179 + version: "1.19.0"
172 convert: 180 convert:
173 dependency: transitive 181 dependency: transitive
174 description: 182 description:
175 name: convert 183 name: convert
176 sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 184 sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
177 - url: "https://pub.dev" 185 + url: "https://pub.flutter-io.cn"
178 source: hosted 186 source: hosted
179 version: "3.1.2" 187 version: "3.1.2"
180 cross_file: 188 cross_file:
181 dependency: transitive 189 dependency: transitive
182 description: 190 description:
183 name: cross_file 191 name: cross_file
184 - sha256: f141ea4f277af142a0356955707f6556f37b03947d39d55585981a06ca437bd6  
185 - url: "https://pub.dev" 192 + sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670"
  193 + url: "https://pub.flutter-io.cn"
186 source: hosted 194 source: hosted
187 - version: "0.3.5+5" 195 + version: "0.3.4+2"
188 crypto: 196 crypto:
189 dependency: "direct main" 197 dependency: "direct main"
190 description: 198 description:
191 name: crypto 199 name: crypto
192 sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf 200 sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
193 - url: "https://pub.dev" 201 + url: "https://pub.flutter-io.cn"
194 source: hosted 202 source: hosted
195 version: "3.0.7" 203 version: "3.0.7"
196 cupertino_icons: 204 cupertino_icons:
197 dependency: "direct main" 205 dependency: "direct main"
198 description: 206 description:
199 name: cupertino_icons 207 name: cupertino_icons
200 - sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd"  
201 - url: "https://pub.dev" 208 + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
  209 + url: "https://pub.flutter-io.cn"
202 source: hosted 210 source: hosted
203 - version: "1.0.9" 211 + version: "1.0.8"
204 dart_style: 212 dart_style:
205 dependency: transitive 213 dependency: transitive
206 description: 214 description:
207 name: dart_style 215 name: dart_style
208 - sha256: "8a0e5fba27e8ee025d2ffb4ee820b4e6e2cf5e4246a6b1a477eb66866947e0bb"  
209 - url: "https://pub.dev" 216 + sha256: "27eb0ae77836989a3bc541ce55595e8ceee0992807f14511552a898ddd0d88ac"
  217 + url: "https://pub.flutter-io.cn"
210 source: hosted 218 source: hosted
211 - version: "3.1.1" 219 + version: "3.0.1"
212 dio: 220 dio:
213 dependency: "direct main" 221 dependency: "direct main"
214 description: 222 description:
215 name: dio 223 name: dio
216 sha256: "852ec3b48cc431ac04fff978413c541502b67ffc3e26921e74e3d994694192c1" 224 sha256: "852ec3b48cc431ac04fff978413c541502b67ffc3e26921e74e3d994694192c1"
217 - url: "https://pub.dev" 225 + url: "https://pub.flutter-io.cn"
218 source: hosted 226 source: hosted
219 version: "5.11.1" 227 version: "5.11.1"
220 dio_web_adapter: 228 dio_web_adapter:
@@ -222,7 +230,7 @@ packages: @@ -222,7 +230,7 @@ packages:
222 description: 230 description:
223 name: dio_web_adapter 231 name: dio_web_adapter
224 sha256: "3a1b2cd7be71086f38504956e3ebcd2837288d231ff454bafa78021244102bfc" 232 sha256: "3a1b2cd7be71086f38504956e3ebcd2837288d231ff454bafa78021244102bfc"
225 - url: "https://pub.dev" 233 + url: "https://pub.flutter-io.cn"
226 source: hosted 234 source: hosted
227 version: "2.2.2" 235 version: "2.2.2"
228 equatable: 236 equatable:
@@ -230,71 +238,71 @@ packages: @@ -230,71 +238,71 @@ packages:
230 description: 238 description:
231 name: equatable 239 name: equatable
232 sha256: "3bce007a596ff8b3119c45d68aaef631272537c03d30e5d4534dd24bf4c5eaa2" 240 sha256: "3bce007a596ff8b3119c45d68aaef631272537c03d30e5d4534dd24bf4c5eaa2"
233 - url: "https://pub.dev" 241 + url: "https://pub.flutter-io.cn"
234 source: hosted 242 source: hosted
235 version: "2.1.0" 243 version: "2.1.0"
236 fake_async: 244 fake_async:
237 dependency: transitive 245 dependency: transitive
238 description: 246 description:
239 name: fake_async 247 name: fake_async
240 - sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"  
241 - url: "https://pub.dev" 248 + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
  249 + url: "https://pub.flutter-io.cn"
242 source: hosted 250 source: hosted
243 - version: "1.3.3" 251 + version: "1.3.1"
244 ffi: 252 ffi:
245 dependency: transitive 253 dependency: transitive
246 description: 254 description:
247 name: ffi 255 name: ffi
248 - sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45"  
249 - url: "https://pub.dev" 256 + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
  257 + url: "https://pub.flutter-io.cn"
250 source: hosted 258 source: hosted
251 - version: "2.2.0" 259 + version: "2.1.3"
252 file: 260 file:
253 dependency: transitive 261 dependency: transitive
254 description: 262 description:
255 name: file 263 name: file
256 sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 264 sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
257 - url: "https://pub.dev" 265 + url: "https://pub.flutter-io.cn"
258 source: hosted 266 source: hosted
259 version: "7.0.1" 267 version: "7.0.1"
260 file_selector_linux: 268 file_selector_linux:
261 dependency: transitive 269 dependency: transitive
262 description: 270 description:
263 name: file_selector_linux 271 name: file_selector_linux
264 - sha256: da76400e7872ce7637ffdce12749ec24169c25f6195c28372208e65a24bcd2ab  
265 - url: "https://pub.dev" 272 + sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33"
  273 + url: "https://pub.flutter-io.cn"
266 source: hosted 274 source: hosted
267 - version: "0.9.4+1" 275 + version: "0.9.3+2"
268 file_selector_macos: 276 file_selector_macos:
269 dependency: transitive 277 dependency: transitive
270 description: 278 description:
271 name: file_selector_macos 279 name: file_selector_macos
272 - sha256: d57c62362766b5e7ae739448650b66c6aab7a68ba7ecc65e04018652645ae0f4  
273 - url: "https://pub.dev" 280 + sha256: "8c9250b2bd2d8d4268e39c82543bacbaca0fda7d29e0728c3c4bbb7c820fd711"
  281 + url: "https://pub.flutter-io.cn"
274 source: hosted 282 source: hosted
275 - version: "0.9.5+1" 283 + version: "0.9.4+3"
276 file_selector_platform_interface: 284 file_selector_platform_interface:
277 dependency: transitive 285 dependency: transitive
278 description: 286 description:
279 name: file_selector_platform_interface 287 name: file_selector_platform_interface
280 - sha256: "35e0bd61ebcdb91a3505813b055b09b79dfdc7d0aee9c09a7ba59ae4bb13dc85"  
281 - url: "https://pub.dev" 288 + sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b
  289 + url: "https://pub.flutter-io.cn"
282 source: hosted 290 source: hosted
283 - version: "2.7.0" 291 + version: "2.6.2"
284 file_selector_windows: 292 file_selector_windows:
285 dependency: transitive 293 dependency: transitive
286 description: 294 description:
287 name: file_selector_windows 295 name: file_selector_windows
288 - sha256: fbefc5fb92c6d3cbe8d284a2cd971b593bb07d2cd6da8557b81a862250b4acec  
289 - url: "https://pub.dev" 296 + sha256: "320fcfb6f33caa90f0b58380489fc5ac05d99ee94b61aa96ec2bff0ba81d3c2b"
  297 + url: "https://pub.flutter-io.cn"
290 source: hosted 298 source: hosted
291 - version: "0.9.3+6" 299 + version: "0.9.3+4"
292 fixnum: 300 fixnum:
293 dependency: transitive 301 dependency: transitive
294 description: 302 description:
295 name: fixnum 303 name: fixnum
296 sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be 304 sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
297 - url: "https://pub.dev" 305 + url: "https://pub.flutter-io.cn"
298 source: hosted 306 source: hosted
299 version: "1.1.1" 307 version: "1.1.1"
300 fl_chart: 308 fl_chart:
@@ -302,7 +310,7 @@ packages: @@ -302,7 +310,7 @@ packages:
302 description: 310 description:
303 name: fl_chart 311 name: fl_chart
304 sha256: "5276944c6ffc975ae796569a826c38a62d2abcf264e26b88fa6f482e107f4237" 312 sha256: "5276944c6ffc975ae796569a826c38a62d2abcf264e26b88fa6f482e107f4237"
305 - url: "https://pub.dev" 313 + url: "https://pub.flutter-io.cn"
306 source: hosted 314 source: hosted
307 version: "0.70.2" 315 version: "0.70.2"
308 flutter: 316 flutter:
@@ -314,16 +322,16 @@ packages: @@ -314,16 +322,16 @@ packages:
314 dependency: transitive 322 dependency: transitive
315 description: 323 description:
316 name: flutter_cache_manager 324 name: flutter_cache_manager
317 - sha256: "1de7849213b4c73c85aca7e0ac687a9a5d82ccdb594366b9dcc26cb6a2189cd2"  
318 - url: "https://pub.dev" 325 + sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386"
  326 + url: "https://pub.flutter-io.cn"
319 source: hosted 327 source: hosted
320 - version: "3.4.2" 328 + version: "3.4.1"
321 flutter_lints: 329 flutter_lints:
322 dependency: "direct dev" 330 dependency: "direct dev"
323 description: 331 description:
324 name: flutter_lints 332 name: flutter_lints
325 sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" 333 sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1"
326 - url: "https://pub.dev" 334 + url: "https://pub.flutter-io.cn"
327 source: hosted 335 source: hosted
328 version: "5.0.0" 336 version: "5.0.0"
329 flutter_localizations: 337 flutter_localizations:
@@ -335,10 +343,10 @@ packages: @@ -335,10 +343,10 @@ packages:
335 dependency: transitive 343 dependency: transitive
336 description: 344 description:
337 name: flutter_plugin_android_lifecycle 345 name: flutter_plugin_android_lifecycle
338 - sha256: "3854fe5e3bff0b113c658f260b90c95dea17c92db0f2addeac2e343dd9969785"  
339 - url: "https://pub.dev" 346 + sha256: "6382ce712ff69b0f719640ce957559dde459e55ecd433c767e06d139ddf16cab"
  347 + url: "https://pub.flutter-io.cn"
340 source: hosted 348 source: hosted
341 - version: "2.0.35" 349 + version: "2.0.29"
342 flutter_test: 350 flutter_test:
343 dependency: "direct dev" 351 dependency: "direct dev"
344 description: flutter 352 description: flutter
@@ -349,7 +357,7 @@ packages: @@ -349,7 +357,7 @@ packages:
349 description: 357 description:
350 name: flutter_timezone 358 name: flutter_timezone
351 sha256: "869677426fde92dbe170fb7d2d4929f2a8343c2f5f62f08b0bb64f908630b073" 359 sha256: "869677426fde92dbe170fb7d2d4929f2a8343c2f5f62f08b0bb64f908630b073"
352 - url: "https://pub.dev" 360 + url: "https://pub.flutter-io.cn"
353 source: hosted 361 source: hosted
354 version: "5.1.0" 362 version: "5.1.0"
355 flutter_web_plugins: 363 flutter_web_plugins:
@@ -366,12 +374,20 @@ packages: @@ -366,12 +374,20 @@ packages:
366 url: "https://gitcode.com/CPF-Flutter/flutter_fluttertoast.git" 374 url: "https://gitcode.com/CPF-Flutter/flutter_fluttertoast.git"
367 source: git 375 source: git
368 version: "9.0.0" 376 version: "9.0.0"
  377 + frontend_server_client:
  378 + dependency: transitive
  379 + description:
  380 + name: frontend_server_client
  381 + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
  382 + url: "https://pub.flutter-io.cn"
  383 + source: hosted
  384 + version: "4.0.0"
369 get: 385 get:
370 dependency: "direct main" 386 dependency: "direct main"
371 description: 387 description:
372 name: get 388 name: get
373 sha256: "5ed34a7925b85336e15d472cc4cfe7d9ebf4ab8e8b9f688585bf6b50f4c3d79a" 389 sha256: "5ed34a7925b85336e15d472cc4cfe7d9ebf4ab8e8b9f688585bf6b50f4c3d79a"
374 - url: "https://pub.dev" 390 + url: "https://pub.flutter-io.cn"
375 source: hosted 391 source: hosted
376 version: "4.7.3" 392 version: "4.7.3"
377 glob: 393 glob:
@@ -379,7 +395,7 @@ packages: @@ -379,7 +395,7 @@ packages:
379 description: 395 description:
380 name: glob 396 name: glob
381 sha256: "218aeb56050c714f62a3182775320dfa04602b55074873e24e31bbd39bda96fb" 397 sha256: "218aeb56050c714f62a3182775320dfa04602b55074873e24e31bbd39bda96fb"
382 - url: "https://pub.dev" 398 + url: "https://pub.flutter-io.cn"
383 source: hosted 399 source: hosted
384 version: "2.2.0" 400 version: "2.2.0"
385 graphs: 401 graphs:
@@ -387,23 +403,15 @@ packages: @@ -387,23 +403,15 @@ packages:
387 description: 403 description:
388 name: graphs 404 name: graphs
389 sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" 405 sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0"
390 - url: "https://pub.dev" 406 + url: "https://pub.flutter-io.cn"
391 source: hosted 407 source: hosted
392 version: "2.3.2" 408 version: "2.3.2"
393 - hooks:  
394 - dependency: transitive  
395 - description:  
396 - name: hooks  
397 - sha256: eaac480a35ec0814146c2c48d96aaa829e0e44a7662c88ae84c9edf4bc35651f  
398 - url: "https://pub.dev"  
399 - source: hosted  
400 - version: "2.2.0"  
401 http: 409 http:
402 dependency: transitive 410 dependency: transitive
403 description: 411 description:
404 name: http 412 name: http
405 sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" 413 sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
406 - url: "https://pub.dev" 414 + url: "https://pub.flutter-io.cn"
407 source: hosted 415 source: hosted
408 version: "1.6.0" 416 version: "1.6.0"
409 http_multi_server: 417 http_multi_server:
@@ -411,7 +419,7 @@ packages: @@ -411,7 +419,7 @@ packages:
411 description: 419 description:
412 name: http_multi_server 420 name: http_multi_server
413 sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 421 sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8
414 - url: "https://pub.dev" 422 + url: "https://pub.flutter-io.cn"
415 source: hosted 423 source: hosted
416 version: "3.2.2" 424 version: "3.2.2"
417 http_parser: 425 http_parser:
@@ -419,7 +427,7 @@ packages: @@ -419,7 +427,7 @@ packages:
419 description: 427 description:
420 name: http_parser 428 name: http_parser
421 sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" 429 sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
422 - url: "https://pub.dev" 430 + url: "https://pub.flutter-io.cn"
423 source: hosted 431 source: hosted
424 version: "4.1.2" 432 version: "4.1.2"
425 image_cropper: 433 image_cropper:
@@ -435,7 +443,7 @@ packages: @@ -435,7 +443,7 @@ packages:
435 dependency: transitive 443 dependency: transitive
436 description: 444 description:
437 path: image_cropper_for_web 445 path: image_cropper_for_web
438 - ref: b1b45b1a5333571095569d53d01720c505fff983 446 + ref: "9.1.0-ohos-1.0.0-beta.1"
439 resolved-ref: b1b45b1a5333571095569d53d01720c505fff983 447 resolved-ref: b1b45b1a5333571095569d53d01720c505fff983
440 url: "https://gitcode.com/CPF-Flutter/fluttertpc_image_cropper.git" 448 url: "https://gitcode.com/CPF-Flutter/fluttertpc_image_cropper.git"
441 source: git 449 source: git
@@ -444,7 +452,7 @@ packages: @@ -444,7 +452,7 @@ packages:
444 dependency: transitive 452 dependency: transitive
445 description: 453 description:
446 path: image_cropper_platform_interface 454 path: image_cropper_platform_interface
447 - ref: b1b45b1a5333571095569d53d01720c505fff983 455 + ref: "9.1.0-ohos-1.0.0-beta.1"
448 resolved-ref: b1b45b1a5333571095569d53d01720c505fff983 456 resolved-ref: b1b45b1a5333571095569d53d01720c505fff983
449 url: "https://gitcode.com/CPF-Flutter/fluttertpc_image_cropper.git" 457 url: "https://gitcode.com/CPF-Flutter/fluttertpc_image_cropper.git"
450 source: git 458 source: git
@@ -462,42 +470,42 @@ packages: @@ -462,42 +470,42 @@ packages:
462 dependency: transitive 470 dependency: transitive
463 description: 471 description:
464 name: image_picker_android 472 name: image_picker_android
465 - sha256: d5b3e1774af29c9ab00103afb0d4614070f924d2e0057ac867ec98800114793f  
466 - url: "https://pub.dev" 473 + sha256: e83b2b05141469c5e19d77e1dfa11096b6b1567d09065b2265d7c6904560050c
  474 + url: "https://pub.flutter-io.cn"
467 source: hosted 475 source: hosted
468 - version: "0.8.13+17" 476 + version: "0.8.13"
469 image_picker_for_web: 477 image_picker_for_web:
470 dependency: transitive 478 dependency: transitive
471 description: 479 description:
472 name: image_picker_for_web 480 name: image_picker_for_web
473 - sha256: "66257a3191ab360d23a55c8241c91a6e329d31e94efa7be9cf7a212e65850214"  
474 - url: "https://pub.dev" 481 + sha256: "40c2a6a0da15556dc0f8e38a3246064a971a9f512386c3339b89f76db87269b6"
  482 + url: "https://pub.flutter-io.cn"
475 source: hosted 483 source: hosted
476 - version: "3.1.1" 484 + version: "3.1.0"
477 image_picker_ios: 485 image_picker_ios:
478 dependency: transitive 486 dependency: transitive
479 description: 487 description:
480 name: image_picker_ios 488 name: image_picker_ios
481 - sha256: ee3885b6fcd71958fbc79770dd194c63371439d536d69c47b279171a486482ae  
482 - url: "https://pub.dev" 489 + sha256: eb06fe30bab4c4497bad449b66448f50edcc695f1c59408e78aa3a8059eb8f0e
  490 + url: "https://pub.flutter-io.cn"
483 source: hosted 491 source: hosted
484 - version: "0.8.13+7" 492 + version: "0.8.13"
485 image_picker_linux: 493 image_picker_linux:
486 dependency: transitive 494 dependency: transitive
487 description: 495 description:
488 name: image_picker_linux 496 name: image_picker_linux
489 sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4" 497 sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4"
490 - url: "https://pub.dev" 498 + url: "https://pub.flutter-io.cn"
491 source: hosted 499 source: hosted
492 version: "0.2.2" 500 version: "0.2.2"
493 image_picker_macos: 501 image_picker_macos:
494 dependency: transitive 502 dependency: transitive
495 description: 503 description:
496 name: image_picker_macos 504 name: image_picker_macos
497 - sha256: "86f0f15a309de7e1a552c12df9ce5b59fe927e71385329355aec4776c6a8ec91"  
498 - url: "https://pub.dev" 505 + sha256: d58cd9d67793d52beefd6585b12050af0a7663c0c2a6ece0fb110a35d6955e04
  506 + url: "https://pub.flutter-io.cn"
499 source: hosted 507 source: hosted
500 - version: "0.2.2+1" 508 + version: "0.2.2"
501 image_picker_ohos: 509 image_picker_ohos:
502 dependency: transitive 510 dependency: transitive
503 description: 511 description:
@@ -511,96 +519,80 @@ packages: @@ -511,96 +519,80 @@ packages:
511 dependency: transitive 519 dependency: transitive
512 description: 520 description:
513 name: image_picker_platform_interface 521 name: image_picker_platform_interface
514 - sha256: "567e056716333a1647c64bb6bd873cff7622233a5c3f694be28a583d4715690c"  
515 - url: "https://pub.dev" 522 + sha256: "9f143b0dba3e459553209e20cc425c9801af48e6dfa4f01a0fcf927be3f41665"
  523 + url: "https://pub.flutter-io.cn"
516 source: hosted 524 source: hosted
517 - version: "2.11.1" 525 + version: "2.11.0"
518 image_picker_windows: 526 image_picker_windows:
519 dependency: transitive 527 dependency: transitive
520 description: 528 description:
521 name: image_picker_windows 529 name: image_picker_windows
522 sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae 530 sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae
523 - url: "https://pub.dev" 531 + url: "https://pub.flutter-io.cn"
524 source: hosted 532 source: hosted
525 version: "0.2.2" 533 version: "0.2.2"
526 intl: 534 intl:
527 dependency: "direct main" 535 dependency: "direct main"
528 description: 536 description:
529 name: intl 537 name: intl
530 - sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"  
531 - url: "https://pub.dev" 538 + sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
  539 + url: "https://pub.flutter-io.cn"
532 source: hosted 540 source: hosted
533 - version: "0.20.2" 541 + version: "0.19.0"
534 io: 542 io:
535 dependency: transitive 543 dependency: transitive
536 description: 544 description:
537 name: io 545 name: io
538 sha256: "2635216ca6a737e60de577ffa1a48a0bec76ca8a62917cfc1bb88c14c570646f" 546 sha256: "2635216ca6a737e60de577ffa1a48a0bec76ca8a62917cfc1bb88c14c570646f"
539 - url: "https://pub.dev" 547 + url: "https://pub.flutter-io.cn"
540 source: hosted 548 source: hosted
541 version: "1.1.0" 549 version: "1.1.0"
542 - jni:  
543 - dependency: transitive  
544 - description:  
545 - name: jni  
546 - sha256: f038e58b4dc2c9037f50e233175086337e0b305e356d28211bf55f21c504cbd3  
547 - url: "https://pub.dev"  
548 - source: hosted  
549 - version: "1.0.3"  
550 - jni_flutter: 550 + js:
551 dependency: transitive 551 dependency: transitive
552 description: 552 description:
553 - name: jni_flutter  
554 - sha256: b2310cdd4c18c65c081ab141a41efa94aa26c65431803703ece51996f174f351  
555 - url: "https://pub.dev" 553 + name: js
  554 + sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf
  555 + url: "https://pub.flutter-io.cn"
556 source: hosted 556 source: hosted
557 - version: "1.0.3"  
558 - jni_util:  
559 - dependency: transitive  
560 - description:  
561 - name: jni_util  
562 - sha256: "1ba86da04a5f2bf18fde2edb235587e70c5b0fc5bd4ba955f46b00942c3fc35f"  
563 - url: "https://pub.dev"  
564 - source: hosted  
565 - version: "1.0.0" 557 + version: "0.7.1"
566 json_annotation: 558 json_annotation:
567 dependency: transitive 559 dependency: transitive
568 description: 560 description:
569 name: json_annotation 561 name: json_annotation
570 - sha256: "2a743920d81b7910627f68ee2c9ac1fc0bfee32b9fc3403587d7c6791ca12f80"  
571 - url: "https://pub.dev" 562 + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
  563 + url: "https://pub.flutter-io.cn"
572 source: hosted 564 source: hosted
573 - version: "4.12.0" 565 + version: "4.9.0"
574 leak_tracker: 566 leak_tracker:
575 dependency: transitive 567 dependency: transitive
576 description: 568 description:
577 name: leak_tracker 569 name: leak_tracker
578 - sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"  
579 - url: "https://pub.dev" 570 + sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06"
  571 + url: "https://pub.flutter-io.cn"
580 source: hosted 572 source: hosted
581 - version: "11.0.2" 573 + version: "10.0.7"
582 leak_tracker_flutter_testing: 574 leak_tracker_flutter_testing:
583 dependency: transitive 575 dependency: transitive
584 description: 576 description:
585 name: leak_tracker_flutter_testing 577 name: leak_tracker_flutter_testing
586 - sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"  
587 - url: "https://pub.dev" 578 + sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379"
  579 + url: "https://pub.flutter-io.cn"
588 source: hosted 580 source: hosted
589 - version: "3.0.10" 581 + version: "3.0.8"
590 leak_tracker_testing: 582 leak_tracker_testing:
591 dependency: transitive 583 dependency: transitive
592 description: 584 description:
593 name: leak_tracker_testing 585 name: leak_tracker_testing
594 - sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"  
595 - url: "https://pub.dev" 586 + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
  587 + url: "https://pub.flutter-io.cn"
596 source: hosted 588 source: hosted
597 - version: "3.0.2" 589 + version: "3.0.1"
598 lints: 590 lints:
599 dependency: transitive 591 dependency: transitive
600 description: 592 description:
601 name: lints 593 name: lints
602 sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 594 sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7
603 - url: "https://pub.dev" 595 + url: "https://pub.flutter-io.cn"
604 source: hosted 596 source: hosted
605 version: "5.1.1" 597 version: "5.1.1"
606 logger: 598 logger:
@@ -608,7 +600,7 @@ packages: @@ -608,7 +600,7 @@ packages:
608 description: 600 description:
609 name: logger 601 name: logger
610 sha256: "25aee487596a6257655a1e091ec2ae66bc30e7af663592cc3a27e6591e05035c" 602 sha256: "25aee487596a6257655a1e091ec2ae66bc30e7af663592cc3a27e6591e05035c"
611 - url: "https://pub.dev" 603 + url: "https://pub.flutter-io.cn"
612 source: hosted 604 source: hosted
613 version: "2.7.0" 605 version: "2.7.0"
614 logging: 606 logging:
@@ -616,7 +608,7 @@ packages: @@ -616,7 +608,7 @@ packages:
616 description: 608 description:
617 name: logging 609 name: logging
618 sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 610 sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
619 - url: "https://pub.dev" 611 + url: "https://pub.flutter-io.cn"
620 source: hosted 612 source: hosted
621 version: "1.3.0" 613 version: "1.3.0"
622 lottie: 614 lottie:
@@ -624,23 +616,23 @@ packages: @@ -624,23 +616,23 @@ packages:
624 description: 616 description:
625 name: lottie 617 name: lottie
626 sha256: c5fa04a80a620066c15cf19cc44773e19e9b38e989ff23ea32e5903ef1015950 618 sha256: c5fa04a80a620066c15cf19cc44773e19e9b38e989ff23ea32e5903ef1015950
627 - url: "https://pub.dev" 619 + url: "https://pub.flutter-io.cn"
628 source: hosted 620 source: hosted
629 version: "3.3.1" 621 version: "3.3.1"
630 matcher: 622 matcher:
631 dependency: transitive 623 dependency: transitive
632 description: 624 description:
633 name: matcher 625 name: matcher
634 - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2  
635 - url: "https://pub.dev" 626 + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
  627 + url: "https://pub.flutter-io.cn"
636 source: hosted 628 source: hosted
637 - version: "0.12.17" 629 + version: "0.12.16+1"
638 material_color_utilities: 630 material_color_utilities:
639 dependency: transitive 631 dependency: transitive
640 description: 632 description:
641 name: material_color_utilities 633 name: material_color_utilities
642 sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec 634 sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
643 - url: "https://pub.dev" 635 + url: "https://pub.flutter-io.cn"
644 source: hosted 636 source: hosted
645 version: "0.11.1" 637 version: "0.11.1"
646 meta: 638 meta:
@@ -648,7 +640,7 @@ packages: @@ -648,7 +640,7 @@ packages:
648 description: 640 description:
649 name: meta 641 name: meta
650 sha256: "307249ce4ff29d58a18e97f6345f539382eb9c9c29ecda628900f31de0443dd9" 642 sha256: "307249ce4ff29d58a18e97f6345f539382eb9c9c29ecda628900f31de0443dd9"
651 - url: "https://pub.dev" 643 + url: "https://pub.flutter-io.cn"
652 source: hosted 644 source: hosted
653 version: "1.19.0" 645 version: "1.19.0"
654 mime: 646 mime:
@@ -656,23 +648,15 @@ packages: @@ -656,23 +648,15 @@ packages:
656 description: 648 description:
657 name: mime 649 name: mime
658 sha256: bd47de35f07e27267e69c8c8b22edf9473bfee170a60d60fcc93730c5144b7f6 650 sha256: bd47de35f07e27267e69c8c8b22edf9473bfee170a60d60fcc93730c5144b7f6
659 - url: "https://pub.dev" 651 + url: "https://pub.flutter-io.cn"
660 source: hosted 652 source: hosted
661 version: "2.1.0" 653 version: "2.1.0"
662 - objective_c:  
663 - dependency: transitive  
664 - description:  
665 - name: objective_c  
666 - sha256: ad56fd53a78ff6b1472fa59ff2a4e8b8ccabafc586fc263a1dfad0b99b5553e3  
667 - url: "https://pub.dev"  
668 - source: hosted  
669 - version: "9.6.0"  
670 octo_image: 654 octo_image:
671 dependency: transitive 655 dependency: transitive
672 description: 656 description:
673 name: octo_image 657 name: octo_image
674 sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd" 658 sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd"
675 - url: "https://pub.dev" 659 + url: "https://pub.flutter-io.cn"
676 source: hosted 660 source: hosted
677 version: "2.1.0" 661 version: "2.1.0"
678 package_config: 662 package_config:
@@ -680,17 +664,17 @@ packages: @@ -680,17 +664,17 @@ packages:
680 description: 664 description:
681 name: package_config 665 name: package_config
682 sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc 666 sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc
683 - url: "https://pub.dev" 667 + url: "https://pub.flutter-io.cn"
684 source: hosted 668 source: hosted
685 version: "2.2.0" 669 version: "2.2.0"
686 path: 670 path:
687 dependency: transitive 671 dependency: transitive
688 description: 672 description:
689 name: path 673 name: path
690 - sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"  
691 - url: "https://pub.dev" 674 + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
  675 + url: "https://pub.flutter-io.cn"
692 source: hosted 676 source: hosted
693 - version: "1.9.1" 677 + version: "1.9.0"
694 path_provider: 678 path_provider:
695 dependency: "direct main" 679 dependency: "direct main"
696 description: 680 description:
@@ -704,26 +688,26 @@ packages: @@ -704,26 +688,26 @@ packages:
704 dependency: transitive 688 dependency: transitive
705 description: 689 description:
706 name: path_provider_android 690 name: path_provider_android
707 - sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd"  
708 - url: "https://pub.dev" 691 + sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9
  692 + url: "https://pub.flutter-io.cn"
709 source: hosted 693 source: hosted
710 - version: "2.3.1" 694 + version: "2.2.17"
711 path_provider_foundation: 695 path_provider_foundation:
712 dependency: transitive 696 dependency: transitive
713 description: 697 description:
714 name: path_provider_foundation 698 name: path_provider_foundation
715 - sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699"  
716 - url: "https://pub.dev" 699 + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942"
  700 + url: "https://pub.flutter-io.cn"
717 source: hosted 701 source: hosted
718 - version: "2.6.0" 702 + version: "2.4.1"
719 path_provider_linux: 703 path_provider_linux:
720 dependency: transitive 704 dependency: transitive
721 description: 705 description:
722 name: path_provider_linux 706 name: path_provider_linux
723 - sha256: "58c2005f147315b11e9b4a7bc889cd5203e250cba8e3f012dae259b4972b5c16"  
724 - url: "https://pub.dev" 707 + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
  708 + url: "https://pub.flutter-io.cn"
725 source: hosted 709 source: hosted
726 - version: "2.2.2" 710 + version: "2.2.1"
727 path_provider_ohos: 711 path_provider_ohos:
728 dependency: transitive 712 dependency: transitive
729 description: 713 description:
@@ -737,16 +721,16 @@ packages: @@ -737,16 +721,16 @@ packages:
737 dependency: transitive 721 dependency: transitive
738 description: 722 description:
739 name: path_provider_platform_interface 723 name: path_provider_platform_interface
740 - sha256: "484838772624c3a4b94f1e44a3e19897fee738f2d5c4ce448443b0417f7c9dda"  
741 - url: "https://pub.dev" 724 + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
  725 + url: "https://pub.flutter-io.cn"
742 source: hosted 726 source: hosted
743 - version: "2.1.3" 727 + version: "2.1.2"
744 path_provider_windows: 728 path_provider_windows:
745 dependency: transitive 729 dependency: transitive
746 description: 730 description:
747 name: path_provider_windows 731 name: path_provider_windows
748 sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 732 sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
749 - url: "https://pub.dev" 733 + url: "https://pub.flutter-io.cn"
750 source: hosted 734 source: hosted
751 version: "2.3.0" 735 version: "2.3.0"
752 permission_handler: 736 permission_handler:
@@ -754,7 +738,7 @@ packages: @@ -754,7 +738,7 @@ packages:
754 description: 738 description:
755 name: permission_handler 739 name: permission_handler
756 sha256: "59adad729136f01ea9e35a48f5d1395e25cba6cea552249ddbe9cf950f5d7849" 740 sha256: "59adad729136f01ea9e35a48f5d1395e25cba6cea552249ddbe9cf950f5d7849"
757 - url: "https://pub.dev" 741 + url: "https://pub.flutter-io.cn"
758 source: hosted 742 source: hosted
759 version: "11.4.0" 743 version: "11.4.0"
760 permission_handler_android: 744 permission_handler_android:
@@ -762,7 +746,7 @@ packages: @@ -762,7 +746,7 @@ packages:
762 description: 746 description:
763 name: permission_handler_android 747 name: permission_handler_android
764 sha256: d3971dcdd76182a0c198c096b5db2f0884b0d4196723d21a866fc4cdea057ebc 748 sha256: d3971dcdd76182a0c198c096b5db2f0884b0d4196723d21a866fc4cdea057ebc
765 - url: "https://pub.dev" 749 + url: "https://pub.flutter-io.cn"
766 source: hosted 750 source: hosted
767 version: "12.1.0" 751 version: "12.1.0"
768 permission_handler_apple: 752 permission_handler_apple:
@@ -770,7 +754,7 @@ packages: @@ -770,7 +754,7 @@ packages:
770 description: 754 description:
771 name: permission_handler_apple 755 name: permission_handler_apple
772 sha256: f49cb15a064ea9d974fc7fbb302099353b7b170d07284e86e264561579e5bcf8 756 sha256: f49cb15a064ea9d974fc7fbb302099353b7b170d07284e86e264561579e5bcf8
773 - url: "https://pub.dev" 757 + url: "https://pub.flutter-io.cn"
774 source: hosted 758 source: hosted
775 version: "9.6.1" 759 version: "9.6.1"
776 permission_handler_html: 760 permission_handler_html:
@@ -778,7 +762,7 @@ packages: @@ -778,7 +762,7 @@ packages:
778 description: 762 description:
779 name: permission_handler_html 763 name: permission_handler_html
780 sha256: "6ea98b3f17f60d3b527f2647ed2ab4dc0f6bfe25b22cb1c363f5d8f62252f6ac" 764 sha256: "6ea98b3f17f60d3b527f2647ed2ab4dc0f6bfe25b22cb1c363f5d8f62252f6ac"
781 - url: "https://pub.dev" 765 + url: "https://pub.flutter-io.cn"
782 source: hosted 766 source: hosted
783 version: "0.1.4+1" 767 version: "0.1.4+1"
784 permission_handler_ohos: 768 permission_handler_ohos:
@@ -795,7 +779,7 @@ packages: @@ -795,7 +779,7 @@ packages:
795 description: 779 description:
796 name: permission_handler_platform_interface 780 name: permission_handler_platform_interface
797 sha256: a5c8a97ecf5616112a5b16d4b8e9ec0e5ae90ef63ac69c0d7b8ae240be760b23 781 sha256: a5c8a97ecf5616112a5b16d4b8e9ec0e5ae90ef63ac69c0d7b8ae240be760b23
798 - url: "https://pub.dev" 782 + url: "https://pub.flutter-io.cn"
799 source: hosted 783 source: hosted
800 version: "4.4.0" 784 version: "4.4.0"
801 permission_handler_windows: 785 permission_handler_windows:
@@ -803,7 +787,7 @@ packages: @@ -803,7 +787,7 @@ packages:
803 description: 787 description:
804 name: permission_handler_windows 788 name: permission_handler_windows
805 sha256: caeae01858a0a7d2df67a445ac98e1ad95e55a0e77c73044f4e9b1c8c2289cbd 789 sha256: caeae01858a0a7d2df67a445ac98e1ad95e55a0e77c73044f4e9b1c8c2289cbd
806 - url: "https://pub.dev" 790 + url: "https://pub.flutter-io.cn"
807 source: hosted 791 source: hosted
808 version: "0.2.2" 792 version: "0.2.2"
809 pigeon: 793 pigeon:
@@ -820,7 +804,7 @@ packages: @@ -820,7 +804,7 @@ packages:
820 description: 804 description:
821 name: platform 805 name: platform
822 sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" 806 sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
823 - url: "https://pub.dev" 807 + url: "https://pub.flutter-io.cn"
824 source: hosted 808 source: hosted
825 version: "3.1.6" 809 version: "3.1.6"
826 plugin_platform_interface: 810 plugin_platform_interface:
@@ -828,7 +812,7 @@ packages: @@ -828,7 +812,7 @@ packages:
828 description: 812 description:
829 name: plugin_platform_interface 813 name: plugin_platform_interface
830 sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" 814 sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
831 - url: "https://pub.dev" 815 + url: "https://pub.flutter-io.cn"
832 source: hosted 816 source: hosted
833 version: "2.1.8" 817 version: "2.1.8"
834 pool: 818 pool:
@@ -836,7 +820,7 @@ packages: @@ -836,7 +820,7 @@ packages:
836 description: 820 description:
837 name: pool 821 name: pool
838 sha256: "4177f68c237ea2128d1bee66ac17b2ce05ba3dbaafcbdd54c5d40a39d0b6b11c" 822 sha256: "4177f68c237ea2128d1bee66ac17b2ce05ba3dbaafcbdd54c5d40a39d0b6b11c"
839 - url: "https://pub.dev" 823 + url: "https://pub.flutter-io.cn"
840 source: hosted 824 source: hosted
841 version: "1.5.3" 825 version: "1.5.3"
842 posix: 826 posix:
@@ -844,7 +828,7 @@ packages: @@ -844,7 +828,7 @@ packages:
844 description: 828 description:
845 name: posix 829 name: posix
846 sha256: bc1bad54ad2b735816e31f8d4600cfde6c7839975085ddfbca48b6c9f7c4044e 830 sha256: bc1bad54ad2b735816e31f8d4600cfde6c7839975085ddfbca48b6c9f7c4044e
847 - url: "https://pub.dev" 831 + url: "https://pub.flutter-io.cn"
848 source: hosted 832 source: hosted
849 version: "6.5.2" 833 version: "6.5.2"
850 pretty_dio_logger: 834 pretty_dio_logger:
@@ -852,7 +836,7 @@ packages: @@ -852,7 +836,7 @@ packages:
852 description: 836 description:
853 name: pretty_dio_logger 837 name: pretty_dio_logger
854 sha256: "36f2101299786d567869493e2f5731de61ce130faa14679473b26905a92b6407" 838 sha256: "36f2101299786d567869493e2f5731de61ce130faa14679473b26905a92b6407"
855 - url: "https://pub.dev" 839 + url: "https://pub.flutter-io.cn"
856 source: hosted 840 source: hosted
857 version: "1.4.0" 841 version: "1.4.0"
858 pub_semver: 842 pub_semver:
@@ -860,31 +844,23 @@ packages: @@ -860,31 +844,23 @@ packages:
860 description: 844 description:
861 name: pub_semver 845 name: pub_semver
862 sha256: "261236774e8b1d69cfc6b9eabbc96c40f25e7a2d6b171f3385d4f65d5734fb24" 846 sha256: "261236774e8b1d69cfc6b9eabbc96c40f25e7a2d6b171f3385d4f65d5734fb24"
863 - url: "https://pub.dev" 847 + url: "https://pub.flutter-io.cn"
864 source: hosted 848 source: hosted
865 version: "2.2.1" 849 version: "2.2.1"
866 pubspec_parse: 850 pubspec_parse:
867 dependency: transitive 851 dependency: transitive
868 description: 852 description:
869 name: pubspec_parse 853 name: pubspec_parse
870 - sha256: c38b81cbf34450b67e0265d73433569d12e34782e30ed769c9cc99c9d5f2e796  
871 - url: "https://pub.dev"  
872 - source: hosted  
873 - version: "1.6.0"  
874 - record_use:  
875 - dependency: transitive  
876 - description:  
877 - name: record_use  
878 - sha256: "1cb8564af8d43b464294411db9217f5ec04891c6f22ee2c32d73ae05e88a6bd2"  
879 - url: "https://pub.dev" 854 + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082"
  855 + url: "https://pub.flutter-io.cn"
880 source: hosted 856 source: hosted
881 - version: "1.1.1" 857 + version: "1.5.0"
882 rxdart: 858 rxdart:
883 dependency: transitive 859 dependency: transitive
884 description: 860 description:
885 name: rxdart 861 name: rxdart
886 sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" 862 sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962"
887 - url: "https://pub.dev" 863 + url: "https://pub.flutter-io.cn"
888 source: hosted 864 source: hosted
889 version: "0.28.0" 865 version: "0.28.0"
890 share_plus: 866 share_plus:
@@ -900,7 +876,7 @@ packages: @@ -900,7 +876,7 @@ packages:
900 dependency: transitive 876 dependency: transitive
901 description: 877 description:
902 path: "packages/share_plus/share_plus_platform_interface" 878 path: "packages/share_plus/share_plus_platform_interface"
903 - ref: "55de300a8627c55cd45ac86e6a26bcae8e0ca4cf" 879 + ref: "br_share_plus-v10.1.1_ohos"
904 resolved-ref: "55de300a8627c55cd45ac86e6a26bcae8e0ca4cf" 880 resolved-ref: "55de300a8627c55cd45ac86e6a26bcae8e0ca4cf"
905 url: "https://gitcode.com/CPF-Flutter/flutter_plus_plugins.git" 881 url: "https://gitcode.com/CPF-Flutter/flutter_plus_plugins.git"
906 source: git 882 source: git
@@ -918,24 +894,24 @@ packages: @@ -918,24 +894,24 @@ packages:
918 dependency: transitive 894 dependency: transitive
919 description: 895 description:
920 name: shared_preferences_android 896 name: shared_preferences_android
921 - sha256: e8d4762b1e2e8578fc4d0fd548cebf24afd24f49719c08974df92834565e2c53  
922 - url: "https://pub.dev" 897 + sha256: "5bcf0772a761b04f8c6bf814721713de6f3e5d9d89caf8d3fe031b02a342379e"
  898 + url: "https://pub.flutter-io.cn"
923 source: hosted 899 source: hosted
924 - version: "2.4.23" 900 + version: "2.4.11"
925 shared_preferences_foundation: 901 shared_preferences_foundation:
926 dependency: transitive 902 dependency: transitive
927 description: 903 description:
928 name: shared_preferences_foundation 904 name: shared_preferences_foundation
929 - sha256: "2ec3934efa51e46117f23031cc141b8fc878e8525b94ec1ea4f7f586cf1b47ea"  
930 - url: "https://pub.dev" 905 + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03"
  906 + url: "https://pub.flutter-io.cn"
931 source: hosted 907 source: hosted
932 - version: "2.5.7" 908 + version: "2.5.4"
933 shared_preferences_linux: 909 shared_preferences_linux:
934 dependency: transitive 910 dependency: transitive
935 description: 911 description:
936 name: shared_preferences_linux 912 name: shared_preferences_linux
937 sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" 913 sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
938 - url: "https://pub.dev" 914 + url: "https://pub.flutter-io.cn"
939 source: hosted 915 source: hosted
940 version: "2.4.1" 916 version: "2.4.1"
941 shared_preferences_ohos: 917 shared_preferences_ohos:
@@ -951,16 +927,16 @@ packages: @@ -951,16 +927,16 @@ packages:
951 dependency: transitive 927 dependency: transitive
952 description: 928 description:
953 name: shared_preferences_platform_interface 929 name: shared_preferences_platform_interface
954 - sha256: "649dc798a33931919ea356c4305c2d1f81619ea6e92244070b520187b5140ef9"  
955 - url: "https://pub.dev" 930 + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
  931 + url: "https://pub.flutter-io.cn"
956 source: hosted 932 source: hosted
957 - version: "2.4.2" 933 + version: "2.4.1"
958 shared_preferences_web: 934 shared_preferences_web:
959 dependency: transitive 935 dependency: transitive
960 description: 936 description:
961 name: shared_preferences_web 937 name: shared_preferences_web
962 sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 938 sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
963 - url: "https://pub.dev" 939 + url: "https://pub.flutter-io.cn"
964 source: hosted 940 source: hosted
965 version: "2.4.3" 941 version: "2.4.3"
966 shared_preferences_windows: 942 shared_preferences_windows:
@@ -968,7 +944,7 @@ packages: @@ -968,7 +944,7 @@ packages:
968 description: 944 description:
969 name: shared_preferences_windows 945 name: shared_preferences_windows
970 sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" 946 sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
971 - url: "https://pub.dev" 947 + url: "https://pub.flutter-io.cn"
972 source: hosted 948 source: hosted
973 version: "2.4.1" 949 version: "2.4.1"
974 shelf: 950 shelf:
@@ -976,7 +952,7 @@ packages: @@ -976,7 +952,7 @@ packages:
976 description: 952 description:
977 name: shelf 953 name: shelf
978 sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 954 sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
979 - url: "https://pub.dev" 955 + url: "https://pub.flutter-io.cn"
980 source: hosted 956 source: hosted
981 version: "1.4.2" 957 version: "1.4.2"
982 shelf_web_socket: 958 shelf_web_socket:
@@ -984,7 +960,7 @@ packages: @@ -984,7 +960,7 @@ packages:
984 description: 960 description:
985 name: shelf_web_socket 961 name: shelf_web_socket
986 sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" 962 sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925"
987 - url: "https://pub.dev" 963 + url: "https://pub.flutter-io.cn"
988 source: hosted 964 source: hosted
989 version: "3.0.0" 965 version: "3.0.0"
990 simple_gesture_detector: 966 simple_gesture_detector:
@@ -992,7 +968,7 @@ packages: @@ -992,7 +968,7 @@ packages:
992 description: 968 description:
993 name: simple_gesture_detector 969 name: simple_gesture_detector
994 sha256: ba2cd5af24ff20a0b8d609cec3f40e5b0744d2a71804a2616ae086b9c19d19a3 970 sha256: ba2cd5af24ff20a0b8d609cec3f40e5b0744d2a71804a2616ae086b9c19d19a3
995 - url: "https://pub.dev" 971 + url: "https://pub.flutter-io.cn"
996 source: hosted 972 source: hosted
997 version: "0.2.1" 973 version: "0.2.1"
998 sky_engine: 974 sky_engine:
@@ -1004,10 +980,10 @@ packages: @@ -1004,10 +980,10 @@ packages:
1004 dependency: transitive 980 dependency: transitive
1005 description: 981 description:
1006 name: source_span 982 name: source_span
1007 - sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"  
1008 - url: "https://pub.dev" 983 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
  984 + url: "https://pub.flutter-io.cn"
1009 source: hosted 985 source: hosted
1010 - version: "1.10.2" 986 + version: "1.10.0"
1011 sqflite: 987 sqflite:
1012 dependency: "direct main" 988 dependency: "direct main"
1013 description: 989 description:
@@ -1021,31 +997,31 @@ packages: @@ -1021,31 +997,31 @@ packages:
1021 dependency: transitive 997 dependency: transitive
1022 description: 998 description:
1023 name: sqflite_android 999 name: sqflite_android
1024 - sha256: "881e28efdcc9950fd8e9bb42713dcf1103e62a2e7168f23c9338d82db13dec40"  
1025 - url: "https://pub.dev" 1000 + sha256: "78f489aab276260cdd26676d2169446c7ecd3484bbd5fead4ca14f3ed4dd9ee3"
  1001 + url: "https://pub.flutter-io.cn"
1026 source: hosted 1002 source: hosted
1027 - version: "2.4.2+3" 1003 + version: "2.4.0"
1028 sqflite_common: 1004 sqflite_common:
1029 dependency: transitive 1005 dependency: transitive
1030 description: 1006 description:
1031 name: sqflite_common 1007 name: sqflite_common
1032 - sha256: "1581ffbf7a0e333b380d6a30737d78516b826cb35beb7fb0bf8a3ea0c678b465"  
1033 - url: "https://pub.dev" 1008 + sha256: "761b9740ecbd4d3e66b8916d784e581861fd3c3553eda85e167bc49fdb68f709"
  1009 + url: "https://pub.flutter-io.cn"
1034 source: hosted 1010 source: hosted
1035 - version: "2.5.8" 1011 + version: "2.5.4+6"
1036 sqflite_darwin: 1012 sqflite_darwin:
1037 dependency: transitive 1013 dependency: transitive
1038 description: 1014 description:
1039 name: sqflite_darwin 1015 name: sqflite_darwin
1040 - sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3"  
1041 - url: "https://pub.dev" 1016 + sha256: "22adfd9a2c7d634041e96d6241e6e1c8138ca6817018afc5d443fef91dcefa9c"
  1017 + url: "https://pub.flutter-io.cn"
1042 source: hosted 1018 source: hosted
1043 - version: "2.4.2" 1019 + version: "2.4.1+1"
1044 sqflite_ohos: 1020 sqflite_ohos:
1045 dependency: transitive 1021 dependency: transitive
1046 description: 1022 description:
1047 path: sqflite_ohos 1023 path: sqflite_ohos
1048 - ref: "8cc162bdd90adad2e8c8054b52fc14c1e86000ee" 1024 + ref: "2.4.2-ohos-1.0.0-beta.2"
1049 resolved-ref: "8cc162bdd90adad2e8c8054b52fc14c1e86000ee" 1025 resolved-ref: "8cc162bdd90adad2e8c8054b52fc14c1e86000ee"
1050 url: "https://gitcode.com/CPF-Flutter/flutter_sqflite.git" 1026 url: "https://gitcode.com/CPF-Flutter/flutter_sqflite.git"
1051 source: git 1027 source: git
@@ -1055,159 +1031,167 @@ packages: @@ -1055,159 +1031,167 @@ packages:
1055 description: 1031 description:
1056 name: sqflite_platform_interface 1032 name: sqflite_platform_interface
1057 sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" 1033 sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920"
1058 - url: "https://pub.dev" 1034 + url: "https://pub.flutter-io.cn"
1059 source: hosted 1035 source: hosted
1060 version: "2.4.0" 1036 version: "2.4.0"
1061 stack_trace: 1037 stack_trace:
1062 dependency: transitive 1038 dependency: transitive
1063 description: 1039 description:
1064 name: stack_trace 1040 name: stack_trace
1065 - sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"  
1066 - url: "https://pub.dev" 1041 + sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377"
  1042 + url: "https://pub.flutter-io.cn"
1067 source: hosted 1043 source: hosted
1068 - version: "1.12.1" 1044 + version: "1.12.0"
1069 stream_channel: 1045 stream_channel:
1070 dependency: transitive 1046 dependency: transitive
1071 description: 1047 description:
1072 name: stream_channel 1048 name: stream_channel
1073 - sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"  
1074 - url: "https://pub.dev" 1049 + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
  1050 + url: "https://pub.flutter-io.cn"
1075 source: hosted 1051 source: hosted
1076 - version: "2.1.4" 1052 + version: "2.1.2"
1077 stream_transform: 1053 stream_transform:
1078 dependency: transitive 1054 dependency: transitive
1079 description: 1055 description:
1080 name: stream_transform 1056 name: stream_transform
1081 sha256: a00e5f18bffc764f923e7dec1038527f7fe7a1791361a7117f0358193f13d53a 1057 sha256: a00e5f18bffc764f923e7dec1038527f7fe7a1791361a7117f0358193f13d53a
1082 - url: "https://pub.dev" 1058 + url: "https://pub.flutter-io.cn"
1083 source: hosted 1059 source: hosted
1084 version: "2.1.2" 1060 version: "2.1.2"
1085 string_scanner: 1061 string_scanner:
1086 dependency: transitive 1062 dependency: transitive
1087 description: 1063 description:
1088 name: string_scanner 1064 name: string_scanner
1089 - sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"  
1090 - url: "https://pub.dev" 1065 + sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3"
  1066 + url: "https://pub.flutter-io.cn"
1091 source: hosted 1067 source: hosted
1092 - version: "1.4.1" 1068 + version: "1.3.0"
1093 synchronized: 1069 synchronized:
1094 dependency: transitive 1070 dependency: transitive
1095 description: 1071 description:
1096 name: synchronized 1072 name: synchronized
1097 - sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0  
1098 - url: "https://pub.dev" 1073 + sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225"
  1074 + url: "https://pub.flutter-io.cn"
1099 source: hosted 1075 source: hosted
1100 - version: "3.4.0" 1076 + version: "3.3.0+3"
1101 table_calendar: 1077 table_calendar:
1102 dependency: "direct main" 1078 dependency: "direct main"
1103 description: 1079 description:
1104 name: table_calendar 1080 name: table_calendar
1105 - sha256: f276347cad425ef837a41e8d9ad43f3ee7d59227aa4c36d7430607a5a18fa3b3  
1106 - url: "https://pub.dev" 1081 + sha256: b2896b7c86adf3a4d9c911d860120fe3dbe03c85db43b22fd61f14ee78cdbb63
  1082 + url: "https://pub.flutter-io.cn"
1107 source: hosted 1083 source: hosted
1108 - version: "3.2.1" 1084 + version: "3.1.3"
1109 term_glyph: 1085 term_glyph:
1110 dependency: transitive 1086 dependency: transitive
1111 description: 1087 description:
1112 name: term_glyph 1088 name: term_glyph
1113 - sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"  
1114 - url: "https://pub.dev" 1089 + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
  1090 + url: "https://pub.flutter-io.cn"
1115 source: hosted 1091 source: hosted
1116 - version: "1.2.2" 1092 + version: "1.2.1"
1117 test_api: 1093 test_api:
1118 dependency: transitive 1094 dependency: transitive
1119 description: 1095 description:
1120 name: test_api 1096 name: test_api
1121 - sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55  
1122 - url: "https://pub.dev" 1097 + sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c"
  1098 + url: "https://pub.flutter-io.cn"
1123 source: hosted 1099 source: hosted
1124 - version: "0.7.7" 1100 + version: "0.7.3"
1125 thinking_analytics: 1101 thinking_analytics:
1126 dependency: "direct main" 1102 dependency: "direct main"
1127 description: 1103 description:
1128 name: thinking_analytics 1104 name: thinking_analytics
1129 sha256: b01cac0b5482e71c1d75c44c77d27f427662cc65a77b7bc3c8b49617d7a01e02 1105 sha256: b01cac0b5482e71c1d75c44c77d27f427662cc65a77b7bc3c8b49617d7a01e02
1130 - url: "https://pub.dev" 1106 + url: "https://pub.flutter-io.cn"
1131 source: hosted 1107 source: hosted
1132 version: "3.3.3" 1108 version: "3.3.3"
  1109 + timing:
  1110 + dependency: transitive
  1111 + description:
  1112 + name: timing
  1113 + sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe"
  1114 + url: "https://pub.flutter-io.cn"
  1115 + source: hosted
  1116 + version: "1.0.2"
1133 typed_data: 1117 typed_data:
1134 dependency: transitive 1118 dependency: transitive
1135 description: 1119 description:
1136 name: typed_data 1120 name: typed_data
1137 sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 1121 sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
1138 - url: "https://pub.dev" 1122 + url: "https://pub.flutter-io.cn"
1139 source: hosted 1123 source: hosted
1140 version: "1.4.0" 1124 version: "1.4.0"
1141 url_launcher_linux: 1125 url_launcher_linux:
1142 dependency: transitive 1126 dependency: transitive
1143 description: 1127 description:
1144 name: url_launcher_linux 1128 name: url_launcher_linux
1145 - sha256: "10f86fef4c2c43563fa6c211ff9cf757adf4d3ab762c56bd430664a947d70cd0"  
1146 - url: "https://pub.dev" 1129 + sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935"
  1130 + url: "https://pub.flutter-io.cn"
1147 source: hosted 1131 source: hosted
1148 - version: "3.2.3" 1132 + version: "3.2.1"
1149 url_launcher_platform_interface: 1133 url_launcher_platform_interface:
1150 dependency: transitive 1134 dependency: transitive
1151 description: 1135 description:
1152 name: url_launcher_platform_interface 1136 name: url_launcher_platform_interface
1153 sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" 1137 sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
1154 - url: "https://pub.dev" 1138 + url: "https://pub.flutter-io.cn"
1155 source: hosted 1139 source: hosted
1156 version: "2.3.2" 1140 version: "2.3.2"
1157 url_launcher_web: 1141 url_launcher_web:
1158 dependency: transitive 1142 dependency: transitive
1159 description: 1143 description:
1160 name: url_launcher_web 1144 name: url_launcher_web
1161 - sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34"  
1162 - url: "https://pub.dev" 1145 + sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2"
  1146 + url: "https://pub.flutter-io.cn"
1163 source: hosted 1147 source: hosted
1164 - version: "2.4.3" 1148 + version: "2.4.1"
1165 url_launcher_windows: 1149 url_launcher_windows:
1166 dependency: transitive 1150 dependency: transitive
1167 description: 1151 description:
1168 name: url_launcher_windows 1152 name: url_launcher_windows
1169 - sha256: "6c5ad3f22cd4c38e089b81963b3cd7bb83b111b2df5dce008bb066162f42e429"  
1170 - url: "https://pub.dev" 1153 + sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77"
  1154 + url: "https://pub.flutter-io.cn"
1171 source: hosted 1155 source: hosted
1172 - version: "3.1.6" 1156 + version: "3.1.4"
1173 uuid: 1157 uuid:
1174 dependency: transitive 1158 dependency: transitive
1175 description: 1159 description:
1176 name: uuid 1160 name: uuid
1177 sha256: "9b129329f58692f6e6578329498a8fe9fbe98f090beb764ffbb8ee2eadd01dcd" 1161 sha256: "9b129329f58692f6e6578329498a8fe9fbe98f090beb764ffbb8ee2eadd01dcd"
1178 - url: "https://pub.dev" 1162 + url: "https://pub.flutter-io.cn"
1179 source: hosted 1163 source: hosted
1180 version: "4.6.0" 1164 version: "4.6.0"
1181 vector_math: 1165 vector_math:
1182 dependency: transitive 1166 dependency: transitive
1183 description: 1167 description:
1184 name: vector_math 1168 name: vector_math
1185 - sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b  
1186 - url: "https://pub.dev" 1169 + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
  1170 + url: "https://pub.flutter-io.cn"
1187 source: hosted 1171 source: hosted
1188 - version: "2.2.0" 1172 + version: "2.1.4"
1189 video_thumbnail: 1173 video_thumbnail:
1190 dependency: "direct main" 1174 dependency: "direct main"
1191 description: 1175 description:
1192 name: video_thumbnail 1176 name: video_thumbnail
1193 sha256: "181a0c205b353918954a881f53a3441476b9e301641688a581e0c13f00dc588b" 1177 sha256: "181a0c205b353918954a881f53a3441476b9e301641688a581e0c13f00dc588b"
1194 - url: "https://pub.dev" 1178 + url: "https://pub.flutter-io.cn"
1195 source: hosted 1179 source: hosted
1196 version: "0.5.6" 1180 version: "0.5.6"
1197 vm_service: 1181 vm_service:
1198 dependency: transitive 1182 dependency: transitive
1199 description: 1183 description:
1200 name: vm_service 1184 name: vm_service
1201 - sha256: "5f37239c4851efcef929cea7824e76df7f2f0970aef85d66bbc430afa40e72f0"  
1202 - url: "https://pub.dev" 1185 + sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b
  1186 + url: "https://pub.flutter-io.cn"
1203 source: hosted 1187 source: hosted
1204 - version: "15.3.0" 1188 + version: "14.3.0"
1205 watcher: 1189 watcher:
1206 dependency: transitive 1190 dependency: transitive
1207 description: 1191 description:
1208 name: watcher 1192 name: watcher
1209 sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" 1193 sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635"
1210 - url: "https://pub.dev" 1194 + url: "https://pub.flutter-io.cn"
1211 source: hosted 1195 source: hosted
1212 version: "1.2.1" 1196 version: "1.2.1"
1213 web: 1197 web:
@@ -1215,7 +1199,7 @@ packages: @@ -1215,7 +1199,7 @@ packages:
1215 description: 1199 description:
1216 name: web 1200 name: web
1217 sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" 1201 sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
1218 - url: "https://pub.dev" 1202 + url: "https://pub.flutter-io.cn"
1219 source: hosted 1203 source: hosted
1220 version: "1.1.1" 1204 version: "1.1.1"
1221 web_socket: 1205 web_socket:
@@ -1223,7 +1207,7 @@ packages: @@ -1223,7 +1207,7 @@ packages:
1223 description: 1207 description:
1224 name: web_socket 1208 name: web_socket
1225 sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" 1209 sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c"
1226 - url: "https://pub.dev" 1210 + url: "https://pub.flutter-io.cn"
1227 source: hosted 1211 source: hosted
1228 version: "1.0.1" 1212 version: "1.0.1"
1229 web_socket_channel: 1213 web_socket_channel:
@@ -1231,7 +1215,7 @@ packages: @@ -1231,7 +1215,7 @@ packages:
1231 description: 1215 description:
1232 name: web_socket_channel 1216 name: web_socket_channel
1233 sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 1217 sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
1234 - url: "https://pub.dev" 1218 + url: "https://pub.flutter-io.cn"
1235 source: hosted 1219 source: hosted
1236 version: "3.0.3" 1220 version: "3.0.3"
1237 webview_flutter: 1221 webview_flutter:
@@ -1247,8 +1231,8 @@ packages: @@ -1247,8 +1231,8 @@ packages:
1247 dependency: transitive 1231 dependency: transitive
1248 description: 1232 description:
1249 path: "packages/webview_flutter/webview_flutter_android" 1233 path: "packages/webview_flutter/webview_flutter_android"
1250 - ref: "5a9d0055772c7973712c0eaaf2de0e5f0749f8b9"  
1251 - resolved-ref: "5a9d0055772c7973712c0eaaf2de0e5f0749f8b9" 1234 + ref: "br_webview_flutter-v4.13.0_ohos"
  1235 + resolved-ref: "3f44aceb6b076a6ec58f3570435f1907411511fe"
1252 url: "https://gitcode.com/openharmony-tpc/flutter_packages.git" 1236 url: "https://gitcode.com/openharmony-tpc/flutter_packages.git"
1253 source: git 1237 source: git
1254 version: "4.7.0" 1238 version: "4.7.0"
@@ -1256,8 +1240,8 @@ packages: @@ -1256,8 +1240,8 @@ packages:
1256 dependency: transitive 1240 dependency: transitive
1257 description: 1241 description:
1258 path: "packages/webview_flutter/webview_flutter_ohos" 1242 path: "packages/webview_flutter/webview_flutter_ohos"
1259 - ref: "5a9d0055772c7973712c0eaaf2de0e5f0749f8b9"  
1260 - resolved-ref: "5a9d0055772c7973712c0eaaf2de0e5f0749f8b9" 1243 + ref: "br_webview_flutter-v4.13.0_ohos"
  1244 + resolved-ref: "3f44aceb6b076a6ec58f3570435f1907411511fe"
1261 url: "https://gitcode.com/openharmony-tpc/flutter_packages.git" 1245 url: "https://gitcode.com/openharmony-tpc/flutter_packages.git"
1262 source: git 1246 source: git
1263 version: "4.7.0" 1247 version: "4.7.0"
@@ -1265,8 +1249,8 @@ packages: @@ -1265,8 +1249,8 @@ packages:
1265 dependency: transitive 1249 dependency: transitive
1266 description: 1250 description:
1267 path: "packages/webview_flutter/webview_flutter_platform_interface" 1251 path: "packages/webview_flutter/webview_flutter_platform_interface"
1268 - ref: "5a9d0055772c7973712c0eaaf2de0e5f0749f8b9"  
1269 - resolved-ref: "5a9d0055772c7973712c0eaaf2de0e5f0749f8b9" 1252 + ref: "br_webview_flutter-v4.13.0_ohos"
  1253 + resolved-ref: "3f44aceb6b076a6ec58f3570435f1907411511fe"
1270 url: "https://gitcode.com/openharmony-tpc/flutter_packages.git" 1254 url: "https://gitcode.com/openharmony-tpc/flutter_packages.git"
1271 source: git 1255 source: git
1272 version: "2.13.1" 1256 version: "2.13.1"
@@ -1274,8 +1258,8 @@ packages: @@ -1274,8 +1258,8 @@ packages:
1274 dependency: transitive 1258 dependency: transitive
1275 description: 1259 description:
1276 path: "packages/webview_flutter/webview_flutter_wkwebview" 1260 path: "packages/webview_flutter/webview_flutter_wkwebview"
1277 - ref: "5a9d0055772c7973712c0eaaf2de0e5f0749f8b9"  
1278 - resolved-ref: "5a9d0055772c7973712c0eaaf2de0e5f0749f8b9" 1261 + ref: "br_webview_flutter-v4.13.0_ohos"
  1262 + resolved-ref: "3f44aceb6b076a6ec58f3570435f1907411511fe"
1279 url: "https://gitcode.com/openharmony-tpc/flutter_packages.git" 1263 url: "https://gitcode.com/openharmony-tpc/flutter_packages.git"
1280 source: git 1264 source: git
1281 version: "3.22.0" 1265 version: "3.22.0"
@@ -1283,16 +1267,16 @@ packages: @@ -1283,16 +1267,16 @@ packages:
1283 dependency: transitive 1267 dependency: transitive
1284 description: 1268 description:
1285 name: win32 1269 name: win32
1286 - sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e  
1287 - url: "https://pub.dev" 1270 + sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e
  1271 + url: "https://pub.flutter-io.cn"
1288 source: hosted 1272 source: hosted
1289 - version: "5.15.0" 1273 + version: "5.10.1"
1290 xdg_directories: 1274 xdg_directories:
1291 dependency: transitive 1275 dependency: transitive
1292 description: 1276 description:
1293 name: xdg_directories 1277 name: xdg_directories
1294 sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" 1278 sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
1295 - url: "https://pub.dev" 1279 + url: "https://pub.flutter-io.cn"
1296 source: hosted 1280 source: hosted
1297 version: "1.1.0" 1281 version: "1.1.0"
1298 yaml: 1282 yaml:
@@ -1300,9 +1284,9 @@ packages: @@ -1300,9 +1284,9 @@ packages:
1300 description: 1284 description:
1301 name: yaml 1285 name: yaml
1302 sha256: f67cdd8e07d3c6329146aaef1ba043542b3134c12489f553ca9a7435d1068aea 1286 sha256: f67cdd8e07d3c6329146aaef1ba043542b3134c12489f553ca9a7435d1068aea
1303 - url: "https://pub.dev" 1287 + url: "https://pub.flutter-io.cn"
1304 source: hosted 1288 source: hosted
1305 version: "3.1.4" 1289 version: "3.1.4"
1306 sdks: 1290 sdks:
1307 - dart: ">=3.10.3 <4.0.0"  
1308 - flutter: ">=3.38.4" 1291 + dart: ">=3.6.2 <4.0.0"
  1292 + flutter: ">=3.27.0"
@@ -2,6 +2,7 @@ import 'package:doublefeel_flutter/core/services/raw_data_service/health_raw_dat @@ -2,6 +2,7 @@ import 'package:doublefeel_flutter/core/services/raw_data_service/health_raw_dat
2 import 'package:doublefeel_flutter/core/services/raw_data_service/health_raw_models.dart'; 2 import 'package:doublefeel_flutter/core/services/raw_data_service/health_raw_models.dart';
3 import 'package:doublefeel_flutter/core/services/raw_data_service/platform_ios/apple_health_raw_data_core_service.dart'; 3 import 'package:doublefeel_flutter/core/services/raw_data_service/platform_ios/apple_health_raw_data_core_service.dart';
4 import 'package:doublefeel_flutter/core/services/raw_data_service/platform_ohos/ohos_health_raw_data_core_service.dart'; 4 import 'package:doublefeel_flutter/core/services/raw_data_service/platform_ohos/ohos_health_raw_data_core_service.dart';
  5 +import 'package:doublefeel_flutter/core/services/raw_data_service/platform_ohos/ohos_health_raw_data_events.dart';
5 import 'package:doublefeel_flutter/core/services/raw_data_service/platform_ohos/ohos_health_raw_data_sync_service.dart'; 6 import 'package:doublefeel_flutter/core/services/raw_data_service/platform_ohos/ohos_health_raw_data_sync_service.dart';
6 import 'package:doublefeel_flutter/data/models/enums/app_enums.dart'; 7 import 'package:doublefeel_flutter/data/models/enums/app_enums.dart';
7 import 'package:doublefeel_flutter/data/models/health/health_v2_models.dart'; 8 import 'package:doublefeel_flutter/data/models/health/health_v2_models.dart';
@@ -249,7 +250,7 @@ void main() { @@ -249,7 +250,7 @@ void main() {
249 store.dailyStressPoints.map((point) => point.date), contains(20260830)); 250 store.dailyStressPoints.map((point) => point.date), contains(20260830));
250 }); 251 });
251 252
252 - test('OHOS core backfills missing daily stress from local raw data anchor', 253 + test('OHOS core calculates from latest local raw data day without backfill',
253 () async { 254 () async {
254 final backfillDay = DateTime(2026, 8, 30); 255 final backfillDay = DateTime(2026, 8, 30);
255 final backfillBase = 256 final backfillBase =
@@ -308,20 +309,63 @@ void main() { @@ -308,20 +309,63 @@ void main() {
308 call.dataType == HealthDataUploadType.hrv.type || 309 call.dataType == HealthDataUploadType.hrv.type ||
309 call.dataType == HealthDataUploadType.heartRate.type) 310 call.dataType == HealthDataUploadType.heartRate.type)
310 .map((call) => _dateKey(call.startTime)), 311 .map((call) => _dateKey(call.startTime)),
311 - everyElement(lessThanOrEqualTo(20260827)), 312 + everyElement(20260903),
312 ); 313 );
313 expect( 314 expect(
314 store.hrvStressPoints.any((point) => point.rawEndTime == backfillBase), 315 store.hrvStressPoints.any((point) => point.rawEndTime == backfillBase),
315 - isTrue, 316 + isFalse,
316 ); 317 );
317 expect( 318 expect(
318 store.realtimeStressPoints.any( 319 store.realtimeStressPoints.any(
319 (point) => _dateKey(point.rawEndTime) == 20260830, 320 (point) => _dateKey(point.rawEndTime) == 20260830,
320 ), 321 ),
321 - isTrue, 322 + isFalse,
322 ); 323 );
323 expect(result.dailyStressPoints.map((point) => point.date), 324 expect(result.dailyStressPoints.map((point) => point.date),
324 - contains(20260830)); 325 + isNot(contains(20260830)));
  326 + });
  327 +
  328 + test('OHOS core publishes calculation start and success events', () async {
  329 + final events = <OhosHealthRawDataPipelineEvent>[];
  330 + final subscription = OhosHealthRawDataPipelineEvents.stream.listen(
  331 + events.add,
  332 + );
  333 + addTearDown(subscription.cancel);
  334 + final base = DateTime.now()
  335 + .subtract(const Duration(days: 2))
  336 + .millisecondsSinceEpoch ~/
  337 + 1000;
  338 + final service = OHOSHealthRawDataCoreService(
  339 + rawDataSource: _FakeHealthRawDataSource(
  340 + pointsByDataType: {
  341 + HealthDataUploadType.hrv.type: [_point(1, base, 60)],
  342 + HealthDataUploadType.heartRate.type: [_point(2, base, 80)],
  343 + },
  344 + ),
  345 + localStore: _FakeHealthRawStressLocalStore(),
  346 + userIdProvider: () => 42,
  347 + uploadResultsAfterCalculation: false,
  348 + healthReadAuthorizationChecker: () async => false,
  349 + );
  350 +
  351 + await service.syncAndStore(
  352 + startTime: base - Duration.secondsPerHour,
  353 + endTime: base + Duration.secondsPerHour,
  354 + readChunkDays: 1,
  355 + );
  356 + await Future<void>.delayed(Duration.zero);
  357 +
  358 + final calculationEvents =
  359 + events.where((event) => event.flow == 'calculateAndStore').toList();
  360 + expect(
  361 + calculationEvents.map((event) => event.type),
  362 + containsAllInOrder([
  363 + OhosHealthRawDataPipelineEventType.calculationStarted,
  364 + OhosHealthRawDataPipelineEventType.calculationSucceeded,
  365 + ]),
  366 + );
  367 + expect(calculationEvents.last.userId, 42);
  368 + expect(calculationEvents.last.hrvCount, 1);
325 }); 369 });
326 370
327 test('OHOS core shows debug timing toast after sync and calculation', 371 test('OHOS core shows debug timing toast after sync and calculation',
@@ -2,6 +2,7 @@ import 'dart:async'; @@ -2,6 +2,7 @@ import 'dart:async';
2 2
3 import 'package:doublefeel_flutter/core/result/app_result.dart'; 3 import 'package:doublefeel_flutter/core/result/app_result.dart';
4 import 'package:doublefeel_flutter/core/services/raw_data_service/platform_ohos/huawei_health_data_type.dart'; 4 import 'package:doublefeel_flutter/core/services/raw_data_service/platform_ohos/huawei_health_data_type.dart';
  5 +import 'package:doublefeel_flutter/core/services/raw_data_service/platform_ohos/ohos_health_raw_data_events.dart';
5 import 'package:doublefeel_flutter/core/services/raw_data_service/platform_ohos/ohos_health_raw_data_sync_service.dart'; 6 import 'package:doublefeel_flutter/core/services/raw_data_service/platform_ohos/ohos_health_raw_data_sync_service.dart';
6 import 'package:doublefeel_flutter/data/models/harmony/hm_health_data.dart'; 7 import 'package:doublefeel_flutter/data/models/harmony/hm_health_data.dart';
7 import 'package:doublefeel_flutter/data/models/harmony/hm_sleep_data.dart'; 8 import 'package:doublefeel_flutter/data/models/harmony/hm_sleep_data.dart';
@@ -47,7 +48,7 @@ void main() { @@ -47,7 +48,7 @@ void main() {
47 expect(local.storedBatches.map((e) => e.length), [2]); 48 expect(local.storedBatches.map((e) => e.length), [2]);
48 }); 49 });
49 50
50 - test('syncRawData backfills recent days when local data already exists', 51 + test('syncRawData starts from latest local data day without backfill',
51 () async { 52 () async {
52 final latest = _unixSeconds(DateTime(2026, 9, 3, 10)); 53 final latest = _unixSeconds(DateTime(2026, 9, 3, 10));
53 final remote = _FakeOhosHealthRawDataRemoteDataSource([ 54 final remote = _FakeOhosHealthRawDataRemoteDataSource([
@@ -72,7 +73,7 @@ void main() { @@ -72,7 +73,7 @@ void main() {
72 endTime: _unixSeconds(DateTime(2026, 9, 3, 11)), 73 endTime: _unixSeconds(DateTime(2026, 9, 3, 11)),
73 ); 74 );
74 75
75 - expect(_dateKey(remote.calls.single.startTime), 20260827); 76 + expect(_dateKey(remote.calls.single.startTime), 20260903);
76 expect(result.storedCount, 1); 77 expect(result.storedCount, 1);
77 expect(result.earliestStoredTime, 1788019232); 78 expect(result.earliestStoredTime, 1788019232);
78 }); 79 });
@@ -111,12 +112,12 @@ void main() { @@ -111,12 +112,12 @@ void main() {
111 endTime: _unixSeconds(DateTime(2026, 9, 4)), 112 endTime: _unixSeconds(DateTime(2026, 9, 4)),
112 ); 113 );
113 114
114 - expect(_dateKey(remote.calls[0].startTime), 20260827);  
115 - expect(_dateKey(remote.calls[1].startTime), 20260826);  
116 - expect(_dateKey(remote.calls[2].startTime), 20260825); 115 + expect(_dateKey(remote.calls[0].startTime), 20260903);
  116 + expect(_dateKey(remote.calls[1].startTime), 20260902);
  117 + expect(_dateKey(remote.calls[2].startTime), 20260901);
117 }); 118 });
118 119
119 - test('syncRawData falls back to half-year lookback when local data is empty', 120 + test('syncRawData falls back to two-month lookback when local data is empty',
120 () async { 121 () async {
121 final now = DateTime(2026, 8, 13, 12); 122 final now = DateTime(2026, 8, 13, 12);
122 final remote = _FakeOhosHealthRawDataRemoteDataSource([ 123 final remote = _FakeOhosHealthRawDataRemoteDataSource([
@@ -131,13 +132,7 @@ void main() { @@ -131,13 +132,7 @@ void main() {
131 132
132 final result = await service.syncRawData(dataType: 7); 133 final result = await service.syncRawData(dataType: 7);
133 134
134 - final fallback = now.subtract(  
135 - const Duration(  
136 - days: OhosHealthRawDataSyncService.defaultLookbackDays,  
137 - ),  
138 - );  
139 - final expectedStart =  
140 - _unixSeconds(DateTime(fallback.year, fallback.month, fallback.day)); 135 + final expectedStart = _unixSeconds(DateTime(2026, 6, 13));
141 final expectedEnd = now.millisecondsSinceEpoch ~/ 1000; 136 final expectedEnd = now.millisecondsSinceEpoch ~/ 1000;
142 137
143 expect(result.startTime, expectedStart); 138 expect(result.startTime, expectedStart);
@@ -151,6 +146,76 @@ void main() { @@ -151,6 +146,76 @@ void main() {
151 expect(local.storedBatches, isEmpty); 146 expect(local.storedBatches, isEmpty);
152 }); 147 });
153 148
  149 + test('syncRawData clamps requested and latest anchors to two months',
  150 + () async {
  151 + final endTime = _unixSeconds(DateTime(2026, 9, 4, 12));
  152 + final remote = _FakeOhosHealthRawDataRemoteDataSource([
  153 + const OhosHealthRawDataPage(items: <OhosHealthRawDataItem>[]),
  154 + const OhosHealthRawDataPage(items: <OhosHealthRawDataItem>[]),
  155 + const OhosHealthRawDataPage(items: <OhosHealthRawDataItem>[]),
  156 + ]);
  157 + final local = _FakeOhosHealthRawDataLocalStore(
  158 + latestDataTime: _unixSeconds(DateTime(2026, 1, 1, 8)),
  159 + );
  160 + final service = OhosHealthRawDataSyncService(
  161 + remoteDataSource: remote,
  162 + localStore: local,
  163 + );
  164 +
  165 + final result = await service.syncRawData(
  166 + dataType: 7,
  167 + startTime: _unixSeconds(DateTime(2026, 1, 1)),
  168 + endTime: endTime,
  169 + );
  170 +
  171 + final expectedStart = _unixSeconds(DateTime(2026, 7, 4));
  172 + expect(result.startTime, expectedStart);
  173 + expect(remote.calls.first.startTime, expectedStart);
  174 + expect(remote.calls.last.endTime, endTime);
  175 + });
  176 +
  177 + test('syncRawData publishes start and success events', () async {
  178 + final events = <OhosHealthRawDataPipelineEvent>[];
  179 + final subscription = OhosHealthRawDataPipelineEvents.stream.listen(
  180 + events.add,
  181 + );
  182 + addTearDown(subscription.cancel);
  183 + final remote = _FakeOhosHealthRawDataRemoteDataSource([
  184 + const OhosHealthRawDataPage(
  185 + items: [
  186 + OhosHealthRawDataItem(
  187 + dataType: 1,
  188 + dataTime: 1001,
  189 + payload: {'time': 1001, 'value': 45},
  190 + ),
  191 + ],
  192 + ),
  193 + ]);
  194 + final service = OhosHealthRawDataSyncService(
  195 + remoteDataSource: remote,
  196 + localStore: _FakeOhosHealthRawDataLocalStore(),
  197 + nowProvider: () => DateTime(2026, 9, 4),
  198 + );
  199 +
  200 + await service.syncRawData(
  201 + dataType: 1,
  202 + startTime: 1000,
  203 + endTime: 2000,
  204 + );
  205 + await Future<void>.delayed(Duration.zero);
  206 +
  207 + expect(
  208 + events.map((event) => event.type),
  209 + containsAllInOrder([
  210 + OhosHealthRawDataPipelineEventType.syncStarted,
  211 + OhosHealthRawDataPipelineEventType.syncSucceeded,
  212 + ]),
  213 + );
  214 + expect(events.last.flow, 'syncRawData');
  215 + expect(events.last.dataType, 1);
  216 + expect(events.last.storedCount, 1);
  217 + });
  218 +
154 test('syncRawData splits non-heart-rate ranges every 30 days and logs marker', 219 test('syncRawData splits non-heart-rate ranges every 30 days and logs marker',
155 () async { 220 () async {
156 final start = _unixSeconds(DateTime(2026, 1, 30, 10)); 221 final start = _unixSeconds(DateTime(2026, 1, 30, 10));
@@ -310,6 +375,38 @@ void main() { @@ -310,6 +375,38 @@ void main() {
310 expect(result.pageCount, 3); 375 expect(result.pageCount, 3);
311 }); 376 });
312 377
  378 + test('syncRawData limits segment fetch concurrency to ten', () async {
  379 + final start = _unixSeconds(DateTime(2026, 1));
  380 + final end = _unixSeconds(DateTime(2026, 4, 30));
  381 + final gate = Completer<void>();
  382 + final remote = _FakeOhosHealthRawDataRemoteDataSource(
  383 + const <OhosHealthRawDataPage>[],
  384 + gate: gate,
  385 + );
  386 + final service = OhosHealthRawDataSyncService(
  387 + remoteDataSource: remote,
  388 + localStore: _FakeOhosHealthRawDataLocalStore(),
  389 + );
  390 +
  391 + final sync = service.syncRawData(
  392 + dataType: HuaweiHealthDataType.heartRate.dataType,
  393 + startTime: start,
  394 + endTime: end,
  395 + );
  396 + for (var i = 0; i < 20 && remote.calls.length < 10; i += 1) {
  397 + await Future<void>.delayed(const Duration(milliseconds: 1));
  398 + }
  399 +
  400 + expect(remote.calls, hasLength(10));
  401 +
  402 + gate.complete();
  403 + final result = await sync;
  404 +
  405 + expect(result.segmentCount, 12);
  406 + expect(result.pageCount, 12);
  407 + expect(remote.calls, hasLength(12));
  408 + });
  409 +
313 test('syncRawData joins duplicate in-flight syncs', () async { 410 test('syncRawData joins duplicate in-flight syncs', () async {
314 final gate = Completer<void>(); 411 final gate = Completer<void>();
315 final remote = _FakeOhosHealthRawDataRemoteDataSource( 412 final remote = _FakeOhosHealthRawDataRemoteDataSource(