Commit 0f66e87bf17202eaf78b3560d0dc52dadb238846

Authored by 权海
1 parent fbaf35cc

feat(ui):鸿蒙数据接入

@@ -14,7 +14,6 @@ import '../../../../core/services/raw_data_service/health_raw_models.dart'; @@ -14,7 +14,6 @@ import '../../../../core/services/raw_data_service/health_raw_models.dart';
14 import '../../../../core/services/raw_data_service/platform_ios/apple_health_raw_local_notification.dart'; 14 import '../../../../core/services/raw_data_service/platform_ios/apple_health_raw_local_notification.dart';
15 import '../../../../core/services/raw_data_service/platform_ohos/ohos_harmony_health_raw_data_sync_service_factory.dart'; 15 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_health_raw_data_sync_service.dart'; 16 import '../../../../core/services/raw_data_service/platform_ohos/ohos_health_raw_data_sync_service.dart';
17 -import '../../../../data/models/enums/app_enums.dart';  
18 import '../../../../data/local/user_preferences_storage.dart'; 17 import '../../../../data/local/user_preferences_storage.dart';
19 import '../../../../l10n/l10n_extensions.dart'; 18 import '../../../../l10n/l10n_extensions.dart';
20 import '../../../../core/platform/pigeon_api_facade.dart'; 19 import '../../../../core/platform/pigeon_api_facade.dart';
@@ -361,29 +360,16 @@ class DeveloperOptionsController extends GetxController { @@ -361,29 +360,16 @@ class DeveloperOptionsController extends GetxController {
361 final endTime = now.millisecondsSinceEpoch ~/ 1000; 360 final endTime = now.millisecondsSinceEpoch ~/ 1000;
362 final startTime = 361 final startTime =
363 now.subtract(const Duration(days: 7)).millisecondsSinceEpoch ~/ 1000; 362 now.subtract(const Duration(days: 7)).millisecondsSinceEpoch ~/ 1000;
364 - final dataTypes = <int>[  
365 - HealthDataUploadType.hrv.type,  
366 - HealthDataUploadType.heartRate.type,  
367 - HealthDataUploadType.restingHeartRate.type,  
368 - OhosHealthRawDataType.sleepAnalysis,  
369 - ];  
370 -  
371 await LoadingService.instance.run(() async { 363 await LoadingService.instance.run(() async {
372 - final results = <OhosHealthRawDataSyncResult>[];  
373 - for (final dataType in dataTypes) {  
374 - AppLogger.i(  
375 - '${OhosHealthRawDataSyncService.logMarker} '  
376 - 'developer_options_pull_start dataType=$dataType '  
377 - 'startTime=$startTime endTime=$endTime',  
378 - );  
379 - results.add(  
380 - await syncService.syncRawData(  
381 - dataType: dataType,  
382 - startTime: startTime,  
383 - endTime: endTime,  
384 - ),  
385 - );  
386 - } 364 + AppLogger.i(
  365 + '${OhosHealthRawDataSyncService.logMarker} '
  366 + 'developer_options_pull_calculation_start '
  367 + 'startTime=$startTime endTime=$endTime',
  368 + );
  369 + final results = await syncService.syncCalculationRawData(
  370 + startTime: startTime,
  371 + endTime: endTime,
  372 + );
387 373
388 final storedCount = results.fold<int>( 374 final storedCount = results.fold<int>(
389 0, 375 0,
@@ -2,6 +2,7 @@ import 'package:doublefeel_flutter/core/error/http_error_handling_policy.dart'; @@ -2,6 +2,7 @@ import 'package:doublefeel_flutter/core/error/http_error_handling_policy.dart';
2 import 'package:doublefeel_flutter/core/services/raw_data_service/platform_ohos/huawei_health_data_type.dart'; 2 import 'package:doublefeel_flutter/core/services/raw_data_service/platform_ohos/huawei_health_data_type.dart';
3 import 'package:doublefeel_flutter/data/models/harmony/hm_health_data.dart'; 3 import 'package:doublefeel_flutter/data/models/harmony/hm_health_data.dart';
4 import 'package:doublefeel_flutter/data/models/harmony/hm_sleep_data.dart'; 4 import 'package:doublefeel_flutter/data/models/harmony/hm_sleep_data.dart';
  5 +import 'package:doublefeel_flutter/data/models/harmony/hm_workout_data.dart';
5 import 'package:doublefeel_flutter/data/models/harmony/privacy_records.dart'; 6 import 'package:doublefeel_flutter/data/models/harmony/privacy_records.dart';
6 7
7 import '../../result/app_result.dart'; 8 import '../../result/app_result.dart';
@@ -83,4 +84,25 @@ class HarmonyApi { @@ -83,4 +84,25 @@ class HarmonyApi {
83 }, 84 },
84 ); 85 );
85 } 86 }
  87 +
  88 + /// startTime: 开始时间,Unix 秒
  89 + /// endTime: 结束时间,Unix 秒
  90 + Future<AppResult<HmWorkoutData>> getWorkoutData(int startTime, int endTime) {
  91 + return safeCall(
  92 + call: () async {
  93 + final queryParameters = <String, dynamic>{
  94 + 'start_time': startTime,
  95 + 'end_time': endTime,
  96 + };
  97 +
  98 + final response = await _dioClient.dio.get(
  99 + ApiPaths.hmActivityTime,
  100 + queryParameters: queryParameters,
  101 + );
  102 + return HmWorkoutData.fromJson(
  103 + response.data as Map<String, dynamic>,
  104 + );
  105 + },
  106 + );
  107 + }
86 } 108 }
@@ -95,6 +95,7 @@ abstract final class ApiPaths { @@ -95,6 +95,7 @@ abstract final class ApiPaths {
95 static const hmAuth = '/client/doublefeel/huawei_hm/auth/'; 95 static const hmAuth = '/client/doublefeel/huawei_hm/auth/';
96 static const hmHealthData = '/client/doublefeel/huawei_hm/health_data/'; 96 static const hmHealthData = '/client/doublefeel/huawei_hm/health_data/';
97 static const hmSleepData = '/client/doublefeel/huawei_hm/sleep_data/'; 97 static const hmSleepData = '/client/doublefeel/huawei_hm/sleep_data/';
98 - static const hmPrivacyRecords = '/client/doublefeel/huawei_hm/privacy_records/';  
99 - 98 + static const hmActivityTime = '/client/doublefeel/huawei_hm/activity_time/';
  99 + static const hmPrivacyRecords =
  100 + '/client/doublefeel/huawei_hm/privacy_records/';
100 } 101 }
1 import 'package:flutter/foundation.dart'; 1 import 'package:flutter/foundation.dart';
2 2
3 import '../../platform/pigeon_api_facade.dart'; 3 import '../../platform/pigeon_api_facade.dart';
  4 +import 'platform_ohos/huawei_health_data_type.dart';
4 import 'platform_ohos/ohos_harmony_health_raw_data_sync_service_factory.dart'; 5 import 'platform_ohos/ohos_harmony_health_raw_data_sync_service_factory.dart';
5 import 'platform_ohos/ohos_health_raw_data_sync_service.dart'; 6 import 'platform_ohos/ohos_health_raw_data_sync_service.dart';
6 7
@@ -138,12 +139,39 @@ class OhosHealthRawDataSource implements HealthRawDataSource { @@ -138,12 +139,39 @@ class OhosHealthRawDataSource implements HealthRawDataSource {
138 OhosHealthRawDataSource({ 139 OhosHealthRawDataSource({
139 OhosHealthRawDataSyncService? syncService, 140 OhosHealthRawDataSyncService? syncService,
140 int Function()? userIdProvider, 141 int Function()? userIdProvider,
141 - }) : _syncService = syncService ?? 142 + bool syncBeforeRead = false,
  143 + }) : _syncService = syncService ??
142 createDefaultOhosHealthRawDataSyncService( 144 createDefaultOhosHealthRawDataSyncService(
143 userIdProvider: userIdProvider, 145 userIdProvider: userIdProvider,
144 - ); 146 + ),
  147 + _syncBeforeRead = syncBeforeRead;
145 148
146 final OhosHealthRawDataSyncService _syncService; 149 final OhosHealthRawDataSyncService _syncService;
  150 + final bool _syncBeforeRead;
  151 +
  152 + Future<List<OhosHealthRawDataSyncResult>> syncCalculationRawData({
  153 + int? startTime,
  154 + int? endTime,
  155 + List<int>? dataTypes,
  156 + }) {
  157 + return _syncService.syncCalculationRawData(
  158 + startTime: startTime,
  159 + endTime: endTime,
  160 + dataTypes: dataTypes,
  161 + );
  162 + }
  163 +
  164 + Future<OhosHealthRawDataSyncResult> syncRawData({
  165 + required int dataType,
  166 + int? startTime,
  167 + int? endTime,
  168 + }) {
  169 + return _syncService.syncRawData(
  170 + dataType: dataType,
  171 + startTime: startTime,
  172 + endTime: endTime,
  173 + );
  174 + }
147 175
148 @override 176 @override
149 Future<bool> hasHealthData() async { 177 Future<bool> hasHealthData() async {
@@ -157,13 +185,29 @@ class OhosHealthRawDataSource implements HealthRawDataSource { @@ -157,13 +185,29 @@ class OhosHealthRawDataSource implements HealthRawDataSource {
157 int startTime, 185 int startTime,
158 int endTime, 186 int endTime,
159 ) async { 187 ) async {
160 - await _syncService.syncRawData( 188 + if (_syncBeforeRead) {
  189 + await _syncService.syncRawData(
  190 + dataType: dataType,
  191 + startTime: startTime,
  192 + endTime: endTime,
  193 + );
  194 + }
  195 + final items = await _syncService.queryRawData(
161 dataType: dataType, 196 dataType: dataType,
162 startTime: startTime, 197 startTime: startTime,
163 endTime: endTime, 198 endTime: endTime,
164 ); 199 );
165 - // TODO: Read synced OHOS raw points from local database.  
166 - return const <HealthKitRawDataPoint>[]; 200 + return items
  201 + .map(
  202 + (item) => HealthKitRawDataPoint(
  203 + dataType: dataType,
  204 + startTime: item.dataTime,
  205 + endTime: item.dataTime,
  206 + value: _doublePayload(item.payload, 'value'),
  207 + isMotionLike: _boolPayload(item.payload, 'is_motion_like'),
  208 + ),
  209 + )
  210 + .toList(growable: false);
167 } 211 }
168 212
169 @override 213 @override
@@ -171,13 +215,24 @@ class OhosHealthRawDataSource implements HealthRawDataSource { @@ -171,13 +215,24 @@ class OhosHealthRawDataSource implements HealthRawDataSource {
171 int startTime, 215 int startTime,
172 int endTime, 216 int endTime,
173 ) async { 217 ) async {
174 - await _syncService.syncRawData( 218 + if (_syncBeforeRead) {
  219 + await _syncService.syncRawData(
  220 + dataType: OhosHealthRawDataType.sleepAnalysis,
  221 + startTime: startTime,
  222 + endTime: endTime,
  223 + );
  224 + }
  225 + final items = await _syncService.queryRawData(
175 dataType: OhosHealthRawDataType.sleepAnalysis, 226 dataType: OhosHealthRawDataType.sleepAnalysis,
176 startTime: startTime, 227 startTime: startTime,
177 endTime: endTime, 228 endTime: endTime,
178 ); 229 );
179 - // TODO: Read synced OHOS sleep groups from local database.  
180 - return const <HealthKitRawSleepDataPoint>[]; 230 + return [
  231 + HealthKitRawSleepDataPoint(
  232 + dataType: OhosHealthRawDataType.sleepAnalysis,
  233 + sleepDataPoints: items.map(_sleepPointFromItem).toList(growable: false),
  234 + ),
  235 + ];
181 } 236 }
182 237
183 @override 238 @override
@@ -185,13 +240,62 @@ class OhosHealthRawDataSource implements HealthRawDataSource { @@ -185,13 +240,62 @@ class OhosHealthRawDataSource implements HealthRawDataSource {
185 int startTime, 240 int startTime,
186 int endTime, 241 int endTime,
187 ) async { 242 ) async {
188 - await _syncService.syncRawData( 243 + if (_syncBeforeRead) {
  244 + await Future.wait(
  245 + <int>[
  246 + OhosHealthRawDataType.activitySummary,
  247 + HuaweiHealthDataType.exerciseDuration.dataType,
  248 + HuaweiHealthDataType.standingDuration.dataType,
  249 + HuaweiHealthDataType.stepCount.dataType,
  250 + ].map(
  251 + (dataType) => _syncService.syncRawData(
  252 + dataType: dataType,
  253 + startTime: startTime,
  254 + endTime: endTime,
  255 + ),
  256 + ),
  257 + eagerError: true,
  258 + );
  259 + }
  260 + final activity = await _syncService.queryRawData(
189 dataType: OhosHealthRawDataType.activitySummary, 261 dataType: OhosHealthRawDataType.activitySummary,
190 startTime: startTime, 262 startTime: startTime,
191 endTime: endTime, 263 endTime: endTime,
192 ); 264 );
193 - // TODO: Read synced OHOS activity summaries from local database.  
194 - return const <HealthKitRawActivityDataPoint>[]; 265 + final exercise = await _syncService.queryRawData(
  266 + dataType: HuaweiHealthDataType.exerciseDuration.dataType,
  267 + startTime: startTime,
  268 + endTime: endTime,
  269 + );
  270 + final standing = await _syncService.queryRawData(
  271 + dataType: HuaweiHealthDataType.standingDuration.dataType,
  272 + startTime: startTime,
  273 + endTime: endTime,
  274 + );
  275 + final byTime = <int, HealthKitRawActivityDataPoint>{};
  276 + for (final item in activity) {
  277 + final point = byTime.putIfAbsent(
  278 + item.dataTime,
  279 + () => HealthKitRawActivityDataPoint(endTime: item.dataTime),
  280 + );
  281 + point.activeEnergyBurned = _doublePayload(item.payload, 'value');
  282 + }
  283 + for (final item in exercise) {
  284 + final point = byTime.putIfAbsent(
  285 + item.dataTime,
  286 + () => HealthKitRawActivityDataPoint(endTime: item.dataTime),
  287 + );
  288 + point.appleExerciseTime = _doublePayload(item.payload, 'value');
  289 + }
  290 + for (final item in standing) {
  291 + final point = byTime.putIfAbsent(
  292 + item.dataTime,
  293 + () => HealthKitRawActivityDataPoint(endTime: item.dataTime),
  294 + );
  295 + point.appleStandHours = _doublePayload(item.payload, 'value');
  296 + }
  297 + return byTime.values.toList(growable: false)
  298 + ..sort((a, b) => a.endTime.compareTo(b.endTime));
195 } 299 }
196 300
197 @override 301 @override
@@ -199,12 +303,67 @@ class OhosHealthRawDataSource implements HealthRawDataSource { @@ -199,12 +303,67 @@ class OhosHealthRawDataSource implements HealthRawDataSource {
199 int startTime, 303 int startTime,
200 int endTime, 304 int endTime,
201 ) async { 305 ) async {
202 - await _syncService.syncRawData( 306 + if (_syncBeforeRead) {
  307 + await _syncService.syncRawData(
  308 + dataType: OhosHealthRawDataType.workout,
  309 + startTime: startTime,
  310 + endTime: endTime,
  311 + );
  312 + }
  313 + final items = await _syncService.queryRawData(
203 dataType: OhosHealthRawDataType.workout, 314 dataType: OhosHealthRawDataType.workout,
204 startTime: startTime, 315 startTime: startTime,
205 endTime: endTime, 316 endTime: endTime,
206 ); 317 );
207 - // TODO: Read synced OHOS workout intervals from local database.  
208 - return const <HealthKitRawWorkoutDataPoint>[]; 318 + return items.map(_workoutPointFromItem).toList(growable: false);
  319 + }
  320 +
  321 + HealthKitRawDataPoint _sleepPointFromItem(OhosHealthRawDataItem item) {
  322 + return HealthKitRawDataPoint(
  323 + dataType: _appleSleepType(item.dataType),
  324 + startTime: _intPayload(item.payload, 'from_time') ?? item.dataTime,
  325 + endTime: _intPayload(item.payload, 'to_time') ?? item.dataTime,
  326 + value: null,
  327 + );
  328 + }
  329 +
  330 + HealthKitRawWorkoutDataPoint _workoutPointFromItem(
  331 + OhosHealthRawDataItem item,
  332 + ) {
  333 + return HealthKitRawWorkoutDataPoint(
  334 + workoutType: _intPayload(item.payload, 'activity_type') ?? 0,
  335 + startTime: _intPayload(item.payload, 'from_time') ?? item.dataTime,
  336 + endTime: _intPayload(item.payload, 'to_time') ?? item.dataTime,
  337 + );
  338 + }
  339 +
  340 + int _appleSleepType(int ohosSleepType) {
  341 + return switch (ohosSleepType) {
  342 + 1 => 4, // deep
  343 + 2 => 3, // light/core
  344 + 3 => 5, // REM
  345 + 4 => 2, // awake
  346 + 5 => 1, // nap/asleep
  347 + 6 => 0, // in bed
  348 + 7 => 1, // manual sleep
  349 + _ => 1,
  350 + };
  351 + }
  352 +
  353 + int? _intPayload(Map<String, Object?> payload, String key) {
  354 + final value = payload[key];
  355 + return value is num ? value.toInt() : null;
  356 + }
  357 +
  358 + double? _doublePayload(Map<String, Object?> payload, String key) {
  359 + final value = payload[key];
  360 + return value is num ? value.toDouble() : null;
  361 + }
  362 +
  363 + bool? _boolPayload(Map<String, Object?> payload, String key) {
  364 + final value = payload[key];
  365 + if (value is bool) return value;
  366 + if (value is num) return value != 0;
  367 + return null;
209 } 368 }
210 } 369 }
@@ -2402,11 +2402,12 @@ CREATE TABLE IF NOT EXISTS $sleepResultsTable ( @@ -2402,11 +2402,12 @@ CREATE TABLE IF NOT EXISTS $sleepResultsTable (
2402 whereArgs: [row['raw_end_time']], 2402 whereArgs: [row['raw_end_time']],
2403 limit: 1, 2403 limit: 1,
2404 ); 2404 );
2405 - if (existing.isEmpty) {  
2406 - await db.insert(table, row); 2405 + final existingRow = existing.isEmpty
  2406 + ? await _insertOrQueryRawResultOnConflict(db, table, row)
  2407 + : existing.first;
  2408 + if (existingRow == null) {
2407 return; 2409 return;
2408 } 2410 }
2409 - final existingRow = existing.first;  
2410 if (_matchesStoredValues( 2411 if (_matchesStoredValues(
2411 existingRow, row, _rawResultStoredValueKeys(row))) { 2412 existingRow, row, _rawResultStoredValueKeys(row))) {
2412 await db.update( 2413 await db.update(
@@ -2452,6 +2453,26 @@ CREATE TABLE IF NOT EXISTS $sleepResultsTable ( @@ -2452,6 +2453,26 @@ CREATE TABLE IF NOT EXISTS $sleepResultsTable (
2452 ); 2453 );
2453 } 2454 }
2454 2455
  2456 + Future<Map<String, Object?>?> _insertOrQueryRawResultOnConflict(
  2457 + DatabaseExecutor db,
  2458 + String table,
  2459 + Map<String, Object?> row,
  2460 + ) async {
  2461 + try {
  2462 + await db.insert(table, row);
  2463 + return null;
  2464 + } on DatabaseException catch (error) {
  2465 + if (!error.isUniqueConstraintError()) rethrow;
  2466 + }
  2467 + final rows = await db.query(
  2468 + table,
  2469 + where: 'raw_end_time = ?',
  2470 + whereArgs: [row['raw_end_time']],
  2471 + limit: 1,
  2472 + );
  2473 + return rows.isEmpty ? null : rows.first;
  2474 + }
  2475 +
2455 Future<void> _upsertDailyStressResettingUploaded( 2476 Future<void> _upsertDailyStressResettingUploaded(
2456 DatabaseExecutor db, 2477 DatabaseExecutor db,
2457 Map<String, Object?> row, 2478 Map<String, Object?> row,
@@ -2467,10 +2488,35 @@ CREATE TABLE IF NOT EXISTS $sleepResultsTable ( @@ -2467,10 +2488,35 @@ CREATE TABLE IF NOT EXISTS $sleepResultsTable (
2467 limit: 1, 2488 limit: 1,
2468 ); 2489 );
2469 if (existing.isEmpty) { 2490 if (existing.isEmpty) {
2470 - await db.insert(dailyStressResultsTable, row); 2491 + final existingRow = await _insertOrQueryOnConflict(
  2492 + db,
  2493 + dailyStressResultsTable,
  2494 + row,
  2495 + column: 'date',
  2496 + );
  2497 + if (existingRow == null) return;
  2498 + await _updateDailyStressResettingUploaded(
  2499 + db,
  2500 + row,
  2501 + existingRow,
  2502 + isToday: isToday,
  2503 + );
2471 return; 2504 return;
2472 } 2505 }
2473 - final existingRow = existing.first; 2506 + await _updateDailyStressResettingUploaded(
  2507 + db,
  2508 + row,
  2509 + existing.first,
  2510 + isToday: isToday,
  2511 + );
  2512 + }
  2513 +
  2514 + Future<void> _updateDailyStressResettingUploaded(
  2515 + DatabaseExecutor db,
  2516 + Map<String, Object?> row,
  2517 + Map<String, Object?> existingRow, {
  2518 + required bool isToday,
  2519 + }) async {
2474 final valueChanged = !_matchesStoredValues( 2520 final valueChanged = !_matchesStoredValues(
2475 existingRow, 2521 existingRow,
2476 row, 2522 row,
@@ -2503,10 +2549,24 @@ CREATE TABLE IF NOT EXISTS $sleepResultsTable ( @@ -2503,10 +2549,24 @@ CREATE TABLE IF NOT EXISTS $sleepResultsTable (
2503 limit: 1, 2549 limit: 1,
2504 ); 2550 );
2505 if (existing.isEmpty) { 2551 if (existing.isEmpty) {
2506 - await db.insert(sleepResultsTable, row); 2552 + final existingRow = await _insertOrQueryOnConflict(
  2553 + db,
  2554 + sleepResultsTable,
  2555 + row,
  2556 + column: 'date',
  2557 + );
  2558 + if (existingRow == null) return;
  2559 + await _updateSleepResultResettingUploaded(db, row, existingRow);
2507 return; 2560 return;
2508 } 2561 }
2509 - final existingRow = existing.first; 2562 + await _updateSleepResultResettingUploaded(db, row, existing.first);
  2563 + }
  2564 +
  2565 + Future<void> _updateSleepResultResettingUploaded(
  2566 + DatabaseExecutor db,
  2567 + Map<String, Object?> row,
  2568 + Map<String, Object?> existingRow,
  2569 + ) async {
2510 final valueChanged = !_matchesStoredValues( 2570 final valueChanged = !_matchesStoredValues(
2511 existingRow, 2571 existingRow,
2512 row, 2572 row,
@@ -2538,6 +2598,27 @@ CREATE TABLE IF NOT EXISTS $sleepResultsTable ( @@ -2538,6 +2598,27 @@ CREATE TABLE IF NOT EXISTS $sleepResultsTable (
2538 ); 2598 );
2539 } 2599 }
2540 2600
  2601 + Future<Map<String, Object?>?> _insertOrQueryOnConflict(
  2602 + DatabaseExecutor db,
  2603 + String table,
  2604 + Map<String, Object?> row, {
  2605 + required String column,
  2606 + }) async {
  2607 + try {
  2608 + await db.insert(table, row);
  2609 + return null;
  2610 + } on DatabaseException catch (error) {
  2611 + if (!error.isUniqueConstraintError()) rethrow;
  2612 + }
  2613 + final rows = await db.query(
  2614 + table,
  2615 + where: '$column = ?',
  2616 + whereArgs: [row[column]],
  2617 + limit: 1,
  2618 + );
  2619 + return rows.isEmpty ? null : rows.first;
  2620 + }
  2621 +
2541 int _currentUnixSeconds() { 2622 int _currentUnixSeconds() {
2542 return DateTime.now().millisecondsSinceEpoch ~/ 1000; 2623 return DateTime.now().millisecondsSinceEpoch ~/ 1000;
2543 } 2624 }
@@ -9,7 +9,7 @@ enum HuaweiHealthDataType { @@ -9,7 +9,7 @@ enum HuaweiHealthDataType {
9 heartRate(2), 9 heartRate(2),
10 10
11 /// 血氧饱和度 11 /// 血氧饱和度
12 - bloodOxygenSaturation(3), 12 + // bloodOxygenSaturation(3),
13 13
14 /// 活动量 14 /// 活动量
15 activity(4), 15 activity(4),
@@ -24,22 +24,22 @@ enum HuaweiHealthDataType { @@ -24,22 +24,22 @@ enum HuaweiHealthDataType {
24 stepCount(7), 24 stepCount(7),
25 25
26 /// 步行心率 26 /// 步行心率
27 - walkingHeartRate(8), 27 + // walkingHeartRate(8),
28 28
29 /// 静息心率 29 /// 静息心率
30 - restingHeartRate(9), 30 + restingHeartRate(9);
31 31
32 /// 睡眠心率 32 /// 睡眠心率
33 - sleepingHeartRate(10), 33 + // sleepingHeartRate(10),
34 34
35 /// 手腕温度 35 /// 手腕温度
36 - wristTemperature(11), 36 + // wristTemperature(11),
37 37
38 /// 睡眠呼吸频率 38 /// 睡眠呼吸频率
39 - sleepingRespiratoryRate(12), 39 + // sleepingRespiratoryRate(12),
40 40
41 /// 房颤 41 /// 房颤
42 - atrialFibrillation(13); 42 + // atrialFibrillation(13);
43 43
44 const HuaweiHealthDataType(this.dataType); 44 const HuaweiHealthDataType(this.dataType);
45 45
@@ -2,6 +2,7 @@ import 'package:doublefeel_flutter/core/network/api/harmony_api.dart'; @@ -2,6 +2,7 @@ import 'package:doublefeel_flutter/core/network/api/harmony_api.dart';
2 import 'package:doublefeel_flutter/core/result/app_result.dart'; 2 import 'package:doublefeel_flutter/core/result/app_result.dart';
3 import 'package:doublefeel_flutter/data/models/harmony/hm_health_data.dart'; 3 import 'package:doublefeel_flutter/data/models/harmony/hm_health_data.dart';
4 import 'package:doublefeel_flutter/data/models/harmony/hm_sleep_data.dart'; 4 import 'package:doublefeel_flutter/data/models/harmony/hm_sleep_data.dart';
  5 +import 'package:doublefeel_flutter/data/models/harmony/hm_workout_data.dart';
5 import 'package:get/get.dart'; 6 import 'package:get/get.dart';
6 7
7 import 'huawei_health_data_type.dart'; 8 import 'huawei_health_data_type.dart';
@@ -43,4 +44,9 @@ class HarmonyApiOhosRawDataClient implements OhosHarmonyRawDataClient { @@ -43,4 +44,9 @@ class HarmonyApiOhosRawDataClient implements OhosHarmonyRawDataClient {
43 Future<AppResult<HmSleepData>> getSleepData(int startDate, int endDate) { 44 Future<AppResult<HmSleepData>> getSleepData(int startDate, int endDate) {
44 return _harmonyApi.getSleepData(startDate, endDate); 45 return _harmonyApi.getSleepData(startDate, endDate);
45 } 46 }
  47 +
  48 + @override
  49 + Future<AppResult<HmWorkoutData>> getWorkoutData(int startTime, int endTime) {
  50 + return _harmonyApi.getWorkoutData(startTime, endTime);
  51 + }
46 } 52 }
@@ -5,12 +5,15 @@ import 'dart:isolate'; @@ -5,12 +5,15 @@ import 'dart:isolate';
5 import 'dart:math' as math; 5 import 'dart:math' as math;
6 6
7 import 'package:flutter/foundation.dart'; 7 import 'package:flutter/foundation.dart';
  8 +import 'package:get/get.dart';
8 9
9 import '../../../../data/models/enums/app_enums.dart'; 10 import '../../../../data/models/enums/app_enums.dart';
10 import '../../../../pigeon/health_kit_raw_data_api.g.dart'; 11 import '../../../../pigeon/health_kit_raw_data_api.g.dart';
11 import '../../../config/app_environment_config.dart'; 12 import '../../../config/app_environment_config.dart';
12 import '../../../logging/app_logger.dart'; 13 import '../../../logging/app_logger.dart';
13 import '../../../network/api/health_api.dart'; 14 import '../../../network/api/health_api.dart';
  15 +import '../../../network/api/harmony_api.dart';
  16 +import '../../../result/app_result.dart';
14 import '../health_raw_data_source.dart'; 17 import '../health_raw_data_source.dart';
15 import '../health_raw_models.dart'; 18 import '../health_raw_models.dart';
16 import '../health_raw_stress_calculator.dart'; 19 import '../health_raw_stress_calculator.dart';
@@ -28,16 +31,22 @@ class OHOSHealthRawDataCoreService { @@ -28,16 +31,22 @@ class OHOSHealthRawDataCoreService {
28 int Function()? userIdProvider, 31 int Function()? userIdProvider,
29 bool uploadResultsAfterCalculation = true, 32 bool uploadResultsAfterCalculation = true,
30 HealthApi? serverHealthApi, 33 HealthApi? serverHealthApi,
  34 + HarmonyApi? harmonyApi,
  35 + Future<bool> Function()? healthReadAuthorizationChecker,
31 }) : _rawDataSource = rawDataSource ?? 36 }) : _rawDataSource = rawDataSource ??
32 OhosHealthRawDataSource( 37 OhosHealthRawDataSource(
33 userIdProvider: userIdProvider, 38 userIdProvider: userIdProvider,
  39 + syncBeforeRead: false,
34 ), 40 ),
35 _localStore = localStore ?? 41 _localStore = localStore ??
36 HealthRawStressLocalStore(databaseNamePrefix: 'ohos_'), 42 HealthRawStressLocalStore(databaseNamePrefix: 'ohos_'),
37 _environmentConfig = environmentConfig, 43 _environmentConfig = environmentConfig,
38 _userIdProvider = userIdProvider, 44 _userIdProvider = userIdProvider,
39 _uploadResultsAfterCalculation = uploadResultsAfterCalculation, 45 _uploadResultsAfterCalculation = uploadResultsAfterCalculation,
40 - _serverHealthApi = serverHealthApi; 46 + _serverHealthApi = serverHealthApi,
  47 + _harmonyApi = harmonyApi ??
  48 + (Get.isRegistered<HarmonyApi>() ? Get.find<HarmonyApi>() : null),
  49 + _healthReadAuthorizationChecker = healthReadAuthorizationChecker;
41 50
42 final HealthRawDataSource _rawDataSource; 51 final HealthRawDataSource _rawDataSource;
43 final HealthRawStressLocalStore _localStore; 52 final HealthRawStressLocalStore _localStore;
@@ -45,6 +54,8 @@ class OHOSHealthRawDataCoreService { @@ -45,6 +54,8 @@ class OHOSHealthRawDataCoreService {
45 final int Function()? _userIdProvider; 54 final int Function()? _userIdProvider;
46 final bool _uploadResultsAfterCalculation; 55 final bool _uploadResultsAfterCalculation;
47 final HealthApi? _serverHealthApi; 56 final HealthApi? _serverHealthApi;
  57 + final HarmonyApi? _harmonyApi;
  58 + final Future<bool> Function()? _healthReadAuthorizationChecker;
48 final StreamController<HealthRawDataUpdatedEvent> 59 final StreamController<HealthRawDataUpdatedEvent>
49 _healthDataUpdatedController = 60 _healthDataUpdatedController =
50 StreamController<HealthRawDataUpdatedEvent>.broadcast(); 61 StreamController<HealthRawDataUpdatedEvent>.broadcast();
@@ -192,6 +203,17 @@ class OHOSHealthRawDataCoreService { @@ -192,6 +203,17 @@ class OHOSHealthRawDataCoreService {
192 } 203 }
193 204
194 final userId = _userId; 205 final userId = _userId;
  206 + final hasAuthorization = await _hasHealthReadAuthorization();
  207 + if (!hasAuthorization) {
  208 + _logInfo('skip calculate without OHOS health privacy authorization');
  209 + return HealthRawStressCalculationResult(
  210 + userId: userId,
  211 + hrvStressPoints: const <HealthRawHrvStressPoint>[],
  212 + realtimeStressPoints: const <HealthRawRealtimeStressPoint>[],
  213 + dailyStressPoints: const <HealthRawDailyStressPoint>[],
  214 + sleepResults: const <HealthRawSleepResult>[],
  215 + );
  216 + }
195 await _localStore.ensureReadable(userId); 217 await _localStore.ensureReadable(userId);
196 final effectiveEndTime = 218 final effectiveEndTime =
197 endTime ?? DateTime.now().millisecondsSinceEpoch ~/ 1000; 219 endTime ?? DateTime.now().millisecondsSinceEpoch ~/ 1000;
@@ -204,6 +226,10 @@ class OHOSHealthRawDataCoreService { @@ -204,6 +226,10 @@ class OHOSHealthRawDataCoreService {
204 if (effectiveEndTime < requestedStartTime) { 226 if (effectiveEndTime < requestedStartTime) {
205 throw ArgumentError.value(endTime, 'endTime'); 227 throw ArgumentError.value(endTime, 'endTime');
206 } 228 }
  229 + await _syncCalculationRawDataIfNeeded(
  230 + startTime: requestedStartTime,
  231 + endTime: effectiveEndTime,
  232 + );
207 233
208 final hrvContextStart = await _localStore.latestHrvSourceStartTime(userId); 234 final hrvContextStart = await _localStore.latestHrvSourceStartTime(userId);
209 final realtimeContextStart = 235 final realtimeContextStart =
@@ -420,7 +446,10 @@ class OHOSHealthRawDataCoreService { @@ -420,7 +446,10 @@ class OHOSHealthRawDataCoreService {
420 required int startTime, 446 required int startTime,
421 required int endTime, 447 required int endTime,
422 int readChunkDays = defaultReadChunkDays, 448 int readChunkDays = defaultReadChunkDays,
423 - }) { 449 + }) async {
  450 + if (!await _hasHealthReadAuthorization()) {
  451 + return const <HealthKitRawDataPoint>[];
  452 + }
424 return _fetchRawDataInChunks( 453 return _fetchRawDataInChunks(
425 dataType, 454 dataType,
426 startTime, 455 startTime,
@@ -433,7 +462,10 @@ class OHOSHealthRawDataCoreService { @@ -433,7 +462,10 @@ class OHOSHealthRawDataCoreService {
433 required int startTime, 462 required int startTime,
434 required int endTime, 463 required int endTime,
435 int readChunkDays = defaultReadChunkDays, 464 int readChunkDays = defaultReadChunkDays,
436 - }) { 465 + }) async {
  466 + if (!await _hasHealthReadAuthorization()) {
  467 + return const <HealthKitRawDataPoint>[];
  468 + }
437 return _fetchSleepIntervalsInChunks( 469 return _fetchSleepIntervalsInChunks(
438 startTime, 470 startTime,
439 endTime, 471 endTime,
@@ -445,6 +477,9 @@ class OHOSHealthRawDataCoreService { @@ -445,6 +477,9 @@ class OHOSHealthRawDataCoreService {
445 required int startTime, 477 required int startTime,
446 required int endTime, 478 required int endTime,
447 }) async { 479 }) async {
  480 + if (!await _hasHealthReadAuthorization()) {
  481 + return const <HealthKitRawActivityDataPoint>[];
  482 + }
448 final points = await _rawDataSource.getRawActivityData(startTime, endTime); 483 final points = await _rawDataSource.getRawActivityData(startTime, endTime);
449 return points 484 return points
450 .where((e) => e.endTime >= startTime && e.endTime <= endTime) 485 .where((e) => e.endTime >= startTime && e.endTime <= endTime)
@@ -476,6 +511,40 @@ class OHOSHealthRawDataCoreService { @@ -476,6 +511,40 @@ class OHOSHealthRawDataCoreService {
476 if (_serverHealthApi == null) return; 511 if (_serverHealthApi == null) return;
477 } 512 }
478 513
  514 + Future<void> _syncCalculationRawDataIfNeeded({
  515 + required int startTime,
  516 + required int endTime,
  517 + }) async {
  518 + final rawDataSource = _rawDataSource;
  519 + if (rawDataSource is! OhosHealthRawDataSource) return;
  520 + await rawDataSource.syncCalculationRawData(
  521 + startTime: startTime,
  522 + endTime: endTime,
  523 + );
  524 + }
  525 +
  526 + Future<bool> _hasHealthReadAuthorization() async {
  527 + final checker = _healthReadAuthorizationChecker;
  528 + if (checker != null) {
  529 + return checker();
  530 + }
  531 + final harmonyApi = _harmonyApi;
  532 + if (harmonyApi == null) {
  533 + _logInfo('skip health privacy authorization check: HarmonyApi missing');
  534 + return false;
  535 + }
  536 + final result = await harmonyApi.getPrivacyRecords();
  537 + return switch (result) {
  538 + AppSuccess(:final data) => data.opinion == 1,
  539 + AppFailure(:final error) => _logPrivacyAuthorizationFailure(error),
  540 + };
  541 + }
  542 +
  543 + bool _logPrivacyAuthorizationFailure(Object error) {
  544 + _logInfo('OHOS health privacy authorization check failed: $error');
  545 + return false;
  546 + }
  547 +
479 Future<List<HealthRawDailyStressPoint>> _calculateAndStoreDailyStressPoints({ 548 Future<List<HealthRawDailyStressPoint>> _calculateAndStoreDailyStressPoints({
480 required int userId, 549 required int userId,
481 required List<HealthRawRealtimeStressPoint> realtimePoints, 550 required List<HealthRawRealtimeStressPoint> realtimePoints,
@@ -683,4 +752,15 @@ class OHOSHealthRawDataCoreService { @@ -683,4 +752,15 @@ class OHOSHealthRawDataCoreService {
683 // Logger may be unavailable in isolated unit tests. 752 // Logger may be unavailable in isolated unit tests.
684 } 753 }
685 } 754 }
  755 +
  756 + static void _logInfo(String message) {
  757 + final tagged = 'OHOSHealthRawDataCoreService $message';
  758 + developer.log(tagged, name: 'OHOSHealthRawDataCoreService');
  759 + debugPrint(tagged);
  760 + try {
  761 + AppLogger.i(tagged);
  762 + } catch (_) {
  763 + // Logger may be unavailable in isolated unit tests.
  764 + }
  765 + }
686 } 766 }
1 -import 'dart:convert';  
2 import 'dart:io'; 1 import 'dart:io';
3 2
  3 +import 'package:doublefeel_flutter/core/logging/app_logger.dart';
  4 +import 'package:flutter/foundation.dart';
4 import 'package:path_provider/path_provider.dart'; 5 import 'package:path_provider/path_provider.dart';
5 import 'package:sqflite/sqflite.dart'; 6 import 'package:sqflite/sqflite.dart';
6 7
@@ -20,6 +21,7 @@ class OhosHealthRawDataSqliteStore implements OhosHealthRawDataLocalStore { @@ -20,6 +21,7 @@ class OhosHealthRawDataSqliteStore implements OhosHealthRawDataLocalStore {
20 21
21 static const rawDataTable = 'ohos_raw_data'; 22 static const rawDataTable = 'ohos_raw_data';
22 static const sleepDataTable = 'ohos_sleep_data'; 23 static const sleepDataTable = 'ohos_sleep_data';
  24 + static const logMarker = '[OHOS_RAW_DATA_DB]';
23 25
24 final int Function()? _userIdProvider; 26 final int Function()? _userIdProvider;
25 final Directory? _rootDirectory; 27 final Directory? _rootDirectory;
@@ -46,53 +48,159 @@ class OhosHealthRawDataSqliteStore implements OhosHealthRawDataLocalStore { @@ -46,53 +48,159 @@ class OhosHealthRawDataSqliteStore implements OhosHealthRawDataLocalStore {
46 if (dataType == OhosHealthRawDataType.sleepAnalysis) { 48 if (dataType == OhosHealthRawDataType.sleepAnalysis) {
47 final rows = await db.query( 49 final rows = await db.query(
48 sleepDataTable, 50 sleepDataTable,
49 - columns: ['to_time'],  
50 - orderBy: 'to_time DESC', 51 + columns: ['from_time'],
  52 + orderBy: 'from_time DESC',
51 limit: 1, 53 limit: 1,
52 ); 54 );
53 - return rows.isEmpty ? null : rows.first['to_time'] as int; 55 + return rows.isEmpty ? null : rows.first['from_time'] as int;
  56 + }
  57 + if (dataType == OhosHealthRawDataType.workout) {
  58 + final table = _rawDataTable(dataType);
  59 + await _createRawIntervalDataTypeTable(db, table);
  60 + final rows = await db.query(
  61 + table,
  62 + columns: ['from_time'],
  63 + orderBy: 'from_time DESC',
  64 + limit: 1,
  65 + );
  66 + return rows.isEmpty ? null : rows.first['from_time'] as int;
54 } 67 }
55 68
56 final storedDataType = _storedHealthDataType(dataType); 69 final storedDataType = _storedHealthDataType(dataType);
  70 + final table = _rawDataTable(storedDataType);
  71 + await _createRawDataTypeTable(db, table);
57 final rows = await db.query( 72 final rows = await db.query(
58 - rawDataTable,  
59 - columns: ['data_time'],  
60 - where: 'data_type = ?',  
61 - whereArgs: [storedDataType],  
62 - orderBy: 'data_time DESC', 73 + table,
  74 + columns: ['time'],
  75 + orderBy: 'time DESC',
63 limit: 1, 76 limit: 1,
64 ); 77 );
65 - return rows.isEmpty ? null : rows.first['data_time'] as int; 78 + return rows.isEmpty ? null : rows.first['time'] as int;
66 } 79 }
67 80
68 @override 81 @override
69 - Future<void> upsertRawDataBatch({ 82 + Future<int> upsertRawDataBatch({
70 required int dataType, 83 required int dataType,
71 required List<OhosHealthRawDataItem> items, 84 required List<OhosHealthRawDataItem> items,
72 }) async { 85 }) async {
73 - if (items.isEmpty) return; 86 + if (items.isEmpty) return 0;
74 final db = await _database(_userId); 87 final db = await _database(_userId);
75 final createTime = _nowProvider().millisecondsSinceEpoch ~/ 1000; 88 final createTime = _nowProvider().millisecondsSinceEpoch ~/ 1000;
  89 + final latestTime = await latestDataTime(dataType: dataType);
  90 + final filteredItems = items
  91 + .where(
  92 + (item) => _shouldStoreItem(
  93 + dataType: dataType,
  94 + item: item,
  95 + latestTime: latestTime,
  96 + ),
  97 + )
  98 + .toList(growable: false);
  99 + if (filteredItems.isEmpty) {
  100 + _log(
  101 + 'skip_store_no_new_items dataType=$dataType latestTime=$latestTime '
  102 + 'incoming=${items.length}',
  103 + );
  104 + return 0;
  105 + }
  106 + var storedCount = 0;
76 await db.transaction((txn) async { 107 await db.transaction((txn) async {
77 if (dataType == OhosHealthRawDataType.sleepAnalysis) { 108 if (dataType == OhosHealthRawDataType.sleepAnalysis) {
78 - for (final item in items) {  
79 - await txn.insert( 109 + for (final item in filteredItems) {
  110 + final rowId = await txn.insert(
80 sleepDataTable, 111 sleepDataTable,
81 _sleepRow(item, createTime), 112 _sleepRow(item, createTime),
82 conflictAlgorithm: ConflictAlgorithm.ignore, 113 conflictAlgorithm: ConflictAlgorithm.ignore,
83 ); 114 );
  115 + if (rowId > 0) storedCount += 1;
  116 + }
  117 + return;
  118 + }
  119 + if (dataType == OhosHealthRawDataType.workout) {
  120 + final table = _rawDataTable(dataType);
  121 + await _createRawIntervalDataTypeTable(txn, table);
  122 + for (final item in filteredItems) {
  123 + final rowId = await txn.insert(
  124 + table,
  125 + _intervalRow(item, createTime),
  126 + conflictAlgorithm: ConflictAlgorithm.ignore,
  127 + );
  128 + if (rowId > 0) storedCount += 1;
84 } 129 }
85 return; 130 return;
86 } 131 }
87 132
88 - for (final item in items) {  
89 - await txn.insert(  
90 - rawDataTable, 133 + final storedDataType = _storedHealthDataType(dataType);
  134 + final table = _rawDataTable(storedDataType);
  135 + await _createRawDataTypeTable(txn, table);
  136 + for (final item in filteredItems) {
  137 + final rowId = await txn.insert(
  138 + table,
91 _rawRow(item, createTime), 139 _rawRow(item, createTime),
92 - conflictAlgorithm: ConflictAlgorithm.ignore, 140 + conflictAlgorithm: _dailyItemNeedsRefresh(
  141 + dataType: dataType,
  142 + item: item,
  143 + latestTime: latestTime,
  144 + )
  145 + ? ConflictAlgorithm.replace
  146 + : ConflictAlgorithm.ignore,
93 ); 147 );
  148 + if (rowId > 0) storedCount += 1;
94 } 149 }
95 }); 150 });
  151 + final skippedCount = filteredItems.length - storedCount;
  152 + if (skippedCount > 0) {
  153 + _log(
  154 + 'skip_store_duplicates dataType=$dataType latestTime=$latestTime '
  155 + 'incoming=${items.length} candidates=${filteredItems.length} '
  156 + 'duplicates=$skippedCount storedItems=$storedCount',
  157 + );
  158 + }
  159 + return storedCount;
  160 + }
  161 +
  162 + @override
  163 + Future<List<OhosHealthRawDataItem>> queryRawData({
  164 + required int dataType,
  165 + required int startTime,
  166 + required int endTime,
  167 + }) async {
  168 + final db = await _database(_userId);
  169 + if (dataType == OhosHealthRawDataType.sleepAnalysis) {
  170 + final rows = await db.query(
  171 + sleepDataTable,
  172 + where: 'to_time >= ? AND from_time <= ?',
  173 + whereArgs: [startTime, endTime],
  174 + orderBy: 'from_time ASC',
  175 + );
  176 + return rows.map(_sleepItemFromRow).toList(growable: false);
  177 + }
  178 + if (dataType == OhosHealthRawDataType.workout) {
  179 + final table = _rawDataTable(dataType);
  180 + await _createRawIntervalDataTypeTable(db, table);
  181 + final rows = await db.query(
  182 + table,
  183 + where: 'to_time >= ? AND from_time <= ?',
  184 + whereArgs: [startTime, endTime],
  185 + orderBy: 'from_time ASC',
  186 + );
  187 + return rows
  188 + .map((row) => _intervalItemFromRow(row, dataType))
  189 + .toList(growable: false);
  190 + }
  191 +
  192 + final storedDataType = _storedHealthDataType(dataType);
  193 + final table = _rawDataTable(storedDataType);
  194 + await _createRawDataTypeTable(db, table);
  195 + final rows = await db.query(
  196 + table,
  197 + where: 'time >= ? AND time <= ?',
  198 + whereArgs: [startTime, endTime],
  199 + orderBy: 'time ASC',
  200 + );
  201 + return rows
  202 + .map((row) => _rawItemFromRow(row, storedDataType))
  203 + .toList(growable: false);
96 } 204 }
97 205
98 Future<void> close({int? userId}) async { 206 Future<void> close({int? userId}) async {
@@ -114,6 +222,7 @@ class OhosHealthRawDataSqliteStore implements OhosHealthRawDataLocalStore { @@ -114,6 +222,7 @@ class OhosHealthRawDataSqliteStore implements OhosHealthRawDataLocalStore {
114 final existing = _opened[userId]; 222 final existing = _opened[userId];
115 if (existing != null && existing.isOpen) return existing; 223 if (existing != null && existing.isOpen) return existing;
116 final path = await dbPath(userId); 224 final path = await dbPath(userId);
  225 + _log('open_database userId=$userId path=$path');
117 final parent = File(path).parent; 226 final parent = File(path).parent;
118 if (!await parent.exists()) { 227 if (!await parent.exists()) {
119 await parent.create(recursive: true); 228 await parent.create(recursive: true);
@@ -130,35 +239,66 @@ class OhosHealthRawDataSqliteStore implements OhosHealthRawDataLocalStore { @@ -130,35 +239,66 @@ class OhosHealthRawDataSqliteStore implements OhosHealthRawDataLocalStore {
130 return db; 239 return db;
131 } 240 }
132 241
  242 + void _log(String message) {
  243 + final taggedMessage = '$logMarker $message';
  244 + debugPrint(taggedMessage);
  245 + try {
  246 + AppLogger.i(taggedMessage);
  247 + } catch (_) {
  248 + // AppLogger may not be initialized in isolated test/bootstrap contexts.
  249 + }
  250 + }
  251 +
133 Future<void> _createTables(Database db) async { 252 Future<void> _createTables(Database db) async {
134 await db.execute(''' 253 await db.execute('''
135 -CREATE TABLE IF NOT EXISTS $rawDataTable ( 254 +CREATE TABLE IF NOT EXISTS $sleepDataTable (
136 id INTEGER PRIMARY KEY AUTOINCREMENT, 255 id INTEGER PRIMARY KEY AUTOINCREMENT,
137 - data_type INTEGER NOT NULL,  
138 - data_time INTEGER NOT NULL,  
139 - payload TEXT NOT NULL, 256 + data_type INTEGER,
  257 + from_time INTEGER NOT NULL,
  258 + to_time INTEGER NOT NULL,
140 create_time INTEGER NOT NULL, 259 create_time INTEGER NOT NULL,
141 - UNIQUE (data_type, data_time, payload) 260 + UNIQUE (from_time)
142 ) 261 )
143 '''); 262 ''');
  263 + await db.execute(
  264 + 'CREATE INDEX IF NOT EXISTS idx_ohos_sleep_data_time '
  265 + 'ON $sleepDataTable(to_time)',
  266 + );
  267 + }
  268 +
  269 + Future<void> _createRawDataTypeTable(
  270 + DatabaseExecutor db,
  271 + String table,
  272 + ) async {
144 await db.execute(''' 273 await db.execute('''
145 -CREATE TABLE IF NOT EXISTS $sleepDataTable ( 274 +CREATE TABLE IF NOT EXISTS $table (
146 id INTEGER PRIMARY KEY AUTOINCREMENT, 275 id INTEGER PRIMARY KEY AUTOINCREMENT,
147 - data_type INTEGER NOT NULL,  
148 - from_time INTEGER NOT NULL,  
149 - to_time INTEGER NOT NULL,  
150 - payload TEXT NOT NULL, 276 + time INTEGER NOT NULL,
  277 + value REAL,
  278 + is_asleep INTEGER,
151 create_time INTEGER NOT NULL, 279 create_time INTEGER NOT NULL,
152 - UNIQUE (data_type, from_time, to_time, payload) 280 + UNIQUE (time)
153 ) 281 )
154 '''); 282 ''');
155 await db.execute( 283 await db.execute(
156 - 'CREATE INDEX IF NOT EXISTS idx_ohos_raw_data_time '  
157 - 'ON $rawDataTable(data_type, data_time)', 284 + 'CREATE INDEX IF NOT EXISTS idx_${table}_time ON $table(time)',
158 ); 285 );
  286 + }
  287 +
  288 + Future<void> _createRawIntervalDataTypeTable(
  289 + DatabaseExecutor db, String table) async {
  290 + await db.execute('''
  291 +CREATE TABLE IF NOT EXISTS $table (
  292 + id INTEGER PRIMARY KEY AUTOINCREMENT,
  293 + activity_type INTEGER,
  294 + from_time INTEGER NOT NULL,
  295 + to_time INTEGER NOT NULL,
  296 + create_time INTEGER NOT NULL,
  297 + UNIQUE (from_time)
  298 +)
  299 +''');
159 await db.execute( 300 await db.execute(
160 - 'CREATE INDEX IF NOT EXISTS idx_ohos_sleep_data_time '  
161 - 'ON $sleepDataTable(to_time)', 301 + 'CREATE INDEX IF NOT EXISTS idx_${table}_time ON $table(to_time)',
162 ); 302 );
163 } 303 }
164 304
@@ -167,9 +307,25 @@ CREATE TABLE IF NOT EXISTS $sleepDataTable ( @@ -167,9 +307,25 @@ CREATE TABLE IF NOT EXISTS $sleepDataTable (
167 int createTime, 307 int createTime,
168 ) { 308 ) {
169 return { 309 return {
170 - 'data_type': item.dataType,  
171 - 'data_time': item.dataTime,  
172 - 'payload': jsonEncode(item.payload), 310 + 'time': _numPayload(item.payload, 'time') ?? item.dataTime,
  311 + 'value': _numPayload(item.payload, 'value'),
  312 + 'is_asleep': _numPayload(item.payload, 'is_asleep'),
  313 + 'create_time': createTime,
  314 + };
  315 + }
  316 +
  317 + Map<String, Object?> _intervalRow(
  318 + OhosHealthRawDataItem item,
  319 + int createTime,
  320 + ) {
  321 + return {
  322 + 'activity_type': _numPayload(item.payload, 'activity_type'),
  323 + 'from_time': _numPayload(item.payload, 'from_time') ??
  324 + _numPayload(item.payload, 'start_time') ??
  325 + 0,
  326 + 'to_time': _numPayload(item.payload, 'to_time') ??
  327 + _numPayload(item.payload, 'end_time') ??
  328 + item.dataTime,
173 'create_time': createTime, 329 'create_time': createTime,
174 }; 330 };
175 } 331 }
@@ -179,10 +335,9 @@ CREATE TABLE IF NOT EXISTS $sleepDataTable ( @@ -179,10 +335,9 @@ CREATE TABLE IF NOT EXISTS $sleepDataTable (
179 int createTime, 335 int createTime,
180 ) { 336 ) {
181 return { 337 return {
182 - 'data_type': item.dataType, 338 + 'data_type': _numPayload(item.payload, 'data_type') ?? item.dataType,
183 'from_time': _numPayload(item.payload, 'from_time') ?? 0, 339 'from_time': _numPayload(item.payload, 'from_time') ?? 0,
184 'to_time': _numPayload(item.payload, 'to_time') ?? item.dataTime, 340 'to_time': _numPayload(item.payload, 'to_time') ?? item.dataTime,
185 - 'payload': jsonEncode(item.payload),  
186 'create_time': createTime, 341 'create_time': createTime,
187 }; 342 };
188 } 343 }
@@ -191,12 +346,120 @@ CREATE TABLE IF NOT EXISTS $sleepDataTable ( @@ -191,12 +346,120 @@ CREATE TABLE IF NOT EXISTS $sleepDataTable (
191 if (dataType == OhosHealthRawDataType.activitySummary) { 346 if (dataType == OhosHealthRawDataType.activitySummary) {
192 return HuaweiHealthDataType.activity.dataType; 347 return HuaweiHealthDataType.activity.dataType;
193 } 348 }
194 - if (dataType == OhosHealthRawDataType.workout) {  
195 - return HuaweiHealthDataType.exerciseDuration.dataType;  
196 - }  
197 return dataType; 349 return dataType;
198 } 350 }
199 351
  352 + String _rawDataTable(int dataType) => dataType < 0
  353 + ? '${rawDataTable}_m${-dataType}'
  354 + : '${rawDataTable}_$dataType';
  355 +
  356 + OhosHealthRawDataItem _rawItemFromRow(
  357 + Map<String, Object?> row,
  358 + int dataType,
  359 + ) {
  360 + final dataTime = row['time'] as int;
  361 + return OhosHealthRawDataItem(
  362 + dataType: dataType,
  363 + dataTime: dataTime,
  364 + payload: <String, Object?>{
  365 + 'time': dataTime,
  366 + 'value': row['value'],
  367 + 'is_asleep': row['is_asleep'],
  368 + },
  369 + );
  370 + }
  371 +
  372 + OhosHealthRawDataItem _sleepItemFromRow(Map<String, Object?> row) {
  373 + final toTime = row['to_time'] as int;
  374 + final dataType =
  375 + (row['data_type'] as int?) ?? OhosHealthRawDataType.sleepAnalysis;
  376 + return OhosHealthRawDataItem(
  377 + dataType: dataType,
  378 + dataTime: toTime,
  379 + payload: <String, Object?>{
  380 + 'data_type': dataType,
  381 + 'from_time': row['from_time'],
  382 + 'to_time': toTime,
  383 + },
  384 + );
  385 + }
  386 +
  387 + OhosHealthRawDataItem _intervalItemFromRow(
  388 + Map<String, Object?> row,
  389 + int dataType,
  390 + ) {
  391 + final toTime = row['to_time'] as int;
  392 + return OhosHealthRawDataItem(
  393 + dataType: dataType,
  394 + dataTime: toTime,
  395 + payload: <String, Object?>{
  396 + 'activity_type': row['activity_type'],
  397 + 'from_time': row['from_time'],
  398 + 'to_time': toTime,
  399 + },
  400 + );
  401 + }
  402 +
  403 + bool _shouldStoreItem({
  404 + required int dataType,
  405 + required OhosHealthRawDataItem item,
  406 + required int? latestTime,
  407 + }) {
  408 + if (latestTime == null) return true;
  409 + final keyTime = _dedupeKeyTime(dataType: dataType, item: item);
  410 + if (_isDailyDataType(dataType)) {
  411 + return keyTime >= _startOfLocalDay(latestTime) - Duration.secondsPerDay;
  412 + }
  413 + return keyTime > latestTime;
  414 + }
  415 +
  416 + int _dedupeKeyTime({
  417 + required int dataType,
  418 + required OhosHealthRawDataItem item,
  419 + }) {
  420 + if (dataType == OhosHealthRawDataType.sleepAnalysis ||
  421 + dataType == OhosHealthRawDataType.workout) {
  422 + return _numPayload(item.payload, 'from_time') ??
  423 + _numPayload(item.payload, 'start_time') ??
  424 + item.dataTime;
  425 + }
  426 + return _numPayload(item.payload, 'time') ?? item.dataTime;
  427 + }
  428 +
  429 + bool _isDailyDataType(int dataType) {
  430 + final storedDataType = _storedHealthDataType(dataType);
  431 + return _isDailyHealthDataType(storedDataType);
  432 + }
  433 +
  434 + bool _dailyItemNeedsRefresh({
  435 + required int dataType,
  436 + required OhosHealthRawDataItem item,
  437 + required int? latestTime,
  438 + }) {
  439 + if (!_isDailyDataType(dataType) || latestTime == null) return false;
  440 + final keyTime = _dedupeKeyTime(dataType: dataType, item: item);
  441 + final refreshStart = _startOfLocalDay(latestTime);
  442 + final todayStart = _startOfLocalDay(
  443 + _nowProvider().millisecondsSinceEpoch ~/ 1000,
  444 + );
  445 + final tomorrowStart = todayStart + Duration.secondsPerDay;
  446 + return keyTime >= refreshStart && keyTime < tomorrowStart;
  447 + }
  448 +
  449 + bool _isDailyHealthDataType(int dataType) {
  450 + return dataType == HuaweiHealthDataType.activity.dataType ||
  451 + dataType == HuaweiHealthDataType.exerciseDuration.dataType ||
  452 + dataType == HuaweiHealthDataType.standingDuration.dataType ||
  453 + dataType == HuaweiHealthDataType.stepCount.dataType;
  454 + }
  455 +
  456 + int _startOfLocalDay(int seconds) {
  457 + final dateTime = DateTime.fromMillisecondsSinceEpoch(seconds * 1000);
  458 + return DateTime(dateTime.year, dateTime.month, dateTime.day)
  459 + .millisecondsSinceEpoch ~/
  460 + 1000;
  461 + }
  462 +
200 int? _numPayload(Map<String, Object?> payload, String key) { 463 int? _numPayload(Map<String, Object?> payload, String key) {
201 final value = payload[key]; 464 final value = payload[key];
202 return value is num ? value.toInt() : null; 465 return value is num ? value.toInt() : null;
1 import 'package:doublefeel_flutter/core/logging/app_logger.dart'; 1 import 'package:doublefeel_flutter/core/logging/app_logger.dart';
2 import 'package:doublefeel_flutter/core/result/app_result.dart'; 2 import 'package:doublefeel_flutter/core/result/app_result.dart';
  3 +import 'package:doublefeel_flutter/data/models/enums/app_enums.dart';
3 import 'package:doublefeel_flutter/data/models/harmony/hm_health_data.dart'; 4 import 'package:doublefeel_flutter/data/models/harmony/hm_health_data.dart';
4 import 'package:doublefeel_flutter/data/models/harmony/hm_sleep_data.dart'; 5 import 'package:doublefeel_flutter/data/models/harmony/hm_sleep_data.dart';
  6 +import 'package:doublefeel_flutter/data/models/harmony/hm_workout_data.dart';
5 import 'package:flutter/foundation.dart'; 7 import 'package:flutter/foundation.dart';
6 8
7 import 'huawei_health_data_type.dart'; 9 import 'huawei_health_data_type.dart';
@@ -27,12 +29,23 @@ class OhosHealthRawDataSyncService { @@ -27,12 +29,23 @@ class OhosHealthRawDataSyncService {
27 _logSink = logSink; 29 _logSink = logSink;
28 30
29 static const int defaultLookbackDays = 183; 31 static const int defaultLookbackDays = 183;
  32 + static const int heartRateFetchChunkDays = 10;
  33 + static const int defaultFetchChunkDays = 30;
30 static const String logMarker = '[OHOS_HEALTH_RAW_SYNC]'; 34 static const String logMarker = '[OHOS_HEALTH_RAW_SYNC]';
  35 + static final List<int> calculationDataTypes = List<int>.unmodifiable(
  36 + <int>[
  37 + for (final type in HuaweiHealthDataType.values) type.dataType,
  38 + OhosHealthRawDataType.sleepAnalysis,
  39 + OhosHealthRawDataType.workout,
  40 + ],
  41 + );
31 42
32 final OhosHealthRawDataRemoteDataSource _remoteDataSource; 43 final OhosHealthRawDataRemoteDataSource _remoteDataSource;
33 final OhosHealthRawDataLocalStore _localStore; 44 final OhosHealthRawDataLocalStore _localStore;
34 final DateTime Function() _nowProvider; 45 final DateTime Function() _nowProvider;
35 final void Function(String message)? _logSink; 46 final void Function(String message)? _logSink;
  47 + final Map<String, Future<OhosHealthRawDataSyncResult>> _runningSyncs =
  48 + <String, Future<OhosHealthRawDataSyncResult>>{};
36 49
37 Future<OhosHealthRawDataSyncResult> syncRawData({ 50 Future<OhosHealthRawDataSyncResult> syncRawData({
38 required int dataType, 51 required int dataType,
@@ -40,24 +53,121 @@ class OhosHealthRawDataSyncService { @@ -40,24 +53,121 @@ class OhosHealthRawDataSyncService {
40 int? endTime, 53 int? endTime,
41 }) async { 54 }) async {
42 final resolvedEndTime = endTime ?? _unixSeconds(_nowProvider()); 55 final resolvedEndTime = endTime ?? _unixSeconds(_nowProvider());
43 - final resolvedStartTime = startTime ??  
44 - await _resolveStartTime(  
45 - dataType: dataType,  
46 - endTime: resolvedEndTime,  
47 - ); 56 + final resolvedStartTime = await _resolveStartTime(
  57 + dataType: dataType,
  58 + requestedStartTime: startTime,
  59 + endTime: resolvedEndTime,
  60 + );
48 61
49 if (resolvedEndTime < resolvedStartTime) { 62 if (resolvedEndTime < resolvedStartTime) {
50 throw ArgumentError.value(endTime, 'endTime'); 63 throw ArgumentError.value(endTime, 'endTime');
51 } 64 }
52 65
53 - final fetchRanges = _splitIntoFetchRanges( 66 + final syncKey = '$dataType:$resolvedStartTime:$resolvedEndTime';
  67 + final running = _runningSyncs[syncKey];
  68 + if (running != null) {
  69 + _log(
  70 + 'sync_duplicate_join dataType=$dataType '
  71 + 'startTime=$resolvedStartTime endTime=$resolvedEndTime',
  72 + );
  73 + return running;
  74 + }
  75 +
  76 + final task = _syncResolvedRawData(
  77 + dataType: dataType,
54 startTime: resolvedStartTime, 78 startTime: resolvedStartTime,
55 endTime: resolvedEndTime, 79 endTime: resolvedEndTime,
56 ); 80 );
  81 + _runningSyncs[syncKey] = task;
  82 + try {
  83 + return await task;
  84 + } catch (error, stackTrace) {
  85 + _log(
  86 + 'sync_failed dataType=$dataType '
  87 + 'startTime=$resolvedStartTime endTime=$resolvedEndTime '
  88 + 'error=$error stackTrace=$stackTrace',
  89 + );
  90 + rethrow;
  91 + } finally {
  92 + if (identical(_runningSyncs[syncKey], task)) {
  93 + _runningSyncs.remove(syncKey);
  94 + }
  95 + }
  96 + }
  97 +
  98 + Future<List<OhosHealthRawDataSyncResult>> syncCalculationRawData({
  99 + int? startTime,
  100 + int? endTime,
  101 + List<int>? dataTypes,
  102 + }) async {
  103 + final resolvedEndTime = endTime ?? _unixSeconds(_nowProvider());
  104 + final resolvedDataTypes = dataTypes ?? calculationDataTypes;
  105 + _log(
  106 + 'calculation_sync_start dataTypes=${resolvedDataTypes.join(',')} '
  107 + 'startTime=${startTime ?? ''} endTime=$resolvedEndTime',
  108 + );
  109 + final List<OhosHealthRawDataSyncResult> results;
  110 + try {
  111 + results = await Future.wait(
  112 + resolvedDataTypes.map(
  113 + (dataType) => syncRawData(
  114 + dataType: dataType,
  115 + startTime: startTime,
  116 + endTime: resolvedEndTime,
  117 + ),
  118 + ),
  119 + eagerError: true,
  120 + );
  121 + } catch (error, stackTrace) {
  122 + _log(
  123 + 'calculation_sync_failed dataTypes=${resolvedDataTypes.join(',')} '
  124 + 'startTime=${startTime ?? ''} endTime=$resolvedEndTime '
  125 + 'error=$error stackTrace=$stackTrace',
  126 + );
  127 + rethrow;
  128 + }
  129 + final storedCount = results.fold<int>(
  130 + 0,
  131 + (sum, result) => sum + result.storedCount,
  132 + );
  133 + final pageCount = results.fold<int>(
  134 + 0,
  135 + (sum, result) => sum + result.pageCount,
  136 + );
  137 + _log(
  138 + 'calculation_sync_finish dataTypes=${resolvedDataTypes.join(',')} '
  139 + 'pageCount=$pageCount storedCount=$storedCount',
  140 + );
  141 + return results;
  142 + }
  143 +
  144 + Future<List<OhosHealthRawDataItem>> queryRawData({
  145 + required int dataType,
  146 + required int startTime,
  147 + required int endTime,
  148 + }) {
  149 + return _localStore.queryRawData(
  150 + dataType: dataType,
  151 + startTime: startTime,
  152 + endTime: endTime,
  153 + );
  154 + }
  155 +
  156 + Future<OhosHealthRawDataSyncResult> _syncResolvedRawData({
  157 + required int dataType,
  158 + required int startTime,
  159 + required int endTime,
  160 + }) async {
  161 + final fetchRanges = _splitIntoFetchRanges(
  162 + dataType: dataType,
  163 + startTime: startTime,
  164 + endTime: endTime,
  165 + );
  166 + final fetchChunkDays = _fetchChunkDays(dataType);
57 _log( 167 _log(
58 'sync_start dataType=$dataType ' 168 'sync_start dataType=$dataType '
59 - 'startTime=$resolvedStartTime endTime=$resolvedEndTime '  
60 - 'rangeCount=${fetchRanges.length}', 169 + 'startTime=$startTime endTime=$endTime '
  170 + 'chunkDays=$fetchChunkDays rangeCount=${fetchRanges.length}',
61 ); 171 );
62 172
63 var pageCount = 0; 173 var pageCount = 0;
@@ -68,6 +178,7 @@ class OhosHealthRawDataSyncService { @@ -68,6 +178,7 @@ class OhosHealthRawDataSyncService {
68 _log( 178 _log(
69 'segment_start dataType=$dataType ' 179 'segment_start dataType=$dataType '
70 'segment=${rangeIndex + 1}/${fetchRanges.length} ' 180 'segment=${rangeIndex + 1}/${fetchRanges.length} '
  181 + 'chunkDays=$fetchChunkDays '
71 'startTime=${range.startTime} endTime=${range.endTime} ' 182 'startTime=${range.startTime} endTime=${range.endTime} '
72 'startDate=${_dateKeyFromUnixSeconds(range.startTime)} ' 183 'startDate=${_dateKeyFromUnixSeconds(range.startTime)} '
73 'endDate=${_dateKeyFromUnixSeconds(range.endTime)}', 184 'endDate=${_dateKeyFromUnixSeconds(range.endTime)}',
@@ -90,15 +201,16 @@ class OhosHealthRawDataSyncService { @@ -90,15 +201,16 @@ class OhosHealthRawDataSyncService {
90 ); 201 );
91 202
92 if (page.items.isNotEmpty) { 203 if (page.items.isNotEmpty) {
93 - await _localStore.upsertRawDataBatch( 204 + final pageStoredCount = await _localStore.upsertRawDataBatch(
94 dataType: dataType, 205 dataType: dataType,
95 items: page.items, 206 items: page.items,
96 ); 207 );
97 - storedCount += page.items.length; 208 + storedCount += pageStoredCount;
98 _log( 209 _log(
99 'page_stored dataType=$dataType ' 210 'page_stored dataType=$dataType '
100 'segment=${rangeIndex + 1}/${fetchRanges.length} ' 211 'segment=${rangeIndex + 1}/${fetchRanges.length} '
101 - 'storedItems=${page.items.length} totalStored=$storedCount', 212 + 'fetchedItems=${page.items.length} '
  213 + 'storedItems=$pageStoredCount totalStored=$storedCount',
102 ); 214 );
103 } 215 }
104 216
@@ -113,15 +225,16 @@ class OhosHealthRawDataSyncService { @@ -113,15 +225,16 @@ class OhosHealthRawDataSyncService {
113 225
114 _log( 226 _log(
115 'sync_finish dataType=$dataType ' 227 'sync_finish dataType=$dataType '
116 - 'startTime=$resolvedStartTime endTime=$resolvedEndTime '  
117 - 'rangeCount=${fetchRanges.length} pageCount=$pageCount ' 228 + 'startTime=$startTime endTime=$endTime '
  229 + 'chunkDays=$fetchChunkDays rangeCount=${fetchRanges.length} '
  230 + 'pageCount=$pageCount '
118 'storedCount=$storedCount', 231 'storedCount=$storedCount',
119 ); 232 );
120 233
121 return OhosHealthRawDataSyncResult( 234 return OhosHealthRawDataSyncResult(
122 dataType: dataType, 235 dataType: dataType,
123 - startTime: resolvedStartTime,  
124 - endTime: resolvedEndTime, 236 + startTime: startTime,
  237 + endTime: endTime,
125 segmentCount: fetchRanges.length, 238 segmentCount: fetchRanges.length,
126 pageCount: pageCount, 239 pageCount: pageCount,
127 storedCount: storedCount, 240 storedCount: storedCount,
@@ -130,35 +243,56 @@ class OhosHealthRawDataSyncService { @@ -130,35 +243,56 @@ class OhosHealthRawDataSyncService {
130 243
131 Future<int> _resolveStartTime({ 244 Future<int> _resolveStartTime({
132 required int dataType, 245 required int dataType,
  246 + required int? requestedStartTime,
133 required int endTime, 247 required int endTime,
134 }) async { 248 }) async {
135 final latestDataTime = await _localStore.latestDataTime(dataType: dataType); 249 final latestDataTime = await _localStore.latestDataTime(dataType: dataType);
136 - if (latestDataTime != null) {  
137 - return latestDataTime;  
138 - }  
139 - final fallbackStartTime = _unixSeconds(  
140 - _nowProvider().subtract(const Duration(days: defaultLookbackDays)),  
141 - );  
142 - return fallbackStartTime > endTime ? endTime : fallbackStartTime; 250 + final baseStartTime = latestDataTime ??
  251 + requestedStartTime ??
  252 + _unixSeconds(
  253 + _nowProvider().subtract(const Duration(days: defaultLookbackDays)),
  254 + );
  255 + final dayStartTime = _startOfLocalDay(baseStartTime);
  256 + final resolvedStartTime = _isDailyDataType(dataType)
  257 + ? dayStartTime - Duration.secondsPerDay
  258 + : dayStartTime;
  259 + return resolvedStartTime > endTime ? endTime : resolvedStartTime;
143 } 260 }
144 261
145 int _unixSeconds(DateTime time) { 262 int _unixSeconds(DateTime time) {
146 return time.millisecondsSinceEpoch ~/ 1000; 263 return time.millisecondsSinceEpoch ~/ 1000;
147 } 264 }
148 265
  266 + int _startOfLocalDay(int seconds) {
  267 + final dateTime = DateTime.fromMillisecondsSinceEpoch(seconds * 1000);
  268 + return _unixSeconds(DateTime(dateTime.year, dateTime.month, dateTime.day));
  269 + }
  270 +
  271 + bool _isDailyDataType(int dataType) {
  272 + return dataType == HuaweiHealthDataType.activity.dataType ||
  273 + dataType == HuaweiHealthDataType.exerciseDuration.dataType ||
  274 + dataType == HuaweiHealthDataType.standingDuration.dataType ||
  275 + dataType == HuaweiHealthDataType.stepCount.dataType ||
  276 + dataType == OhosHealthRawDataType.activitySummary;
  277 + }
  278 +
149 List<OhosHealthRawDataFetchRange> _splitIntoFetchRanges({ 279 List<OhosHealthRawDataFetchRange> _splitIntoFetchRanges({
  280 + required int dataType,
150 required int startTime, 281 required int startTime,
151 required int endTime, 282 required int endTime,
152 }) { 283 }) {
153 final ranges = <OhosHealthRawDataFetchRange>[]; 284 final ranges = <OhosHealthRawDataFetchRange>[];
  285 + final fetchChunkDays = _fetchChunkDays(dataType);
154 var cursor = DateTime.fromMillisecondsSinceEpoch(startTime * 1000); 286 var cursor = DateTime.fromMillisecondsSinceEpoch(startTime * 1000);
155 final end = DateTime.fromMillisecondsSinceEpoch(endTime * 1000); 287 final end = DateTime.fromMillisecondsSinceEpoch(endTime * 1000);
156 288
157 while (!cursor.isAfter(end)) { 289 while (!cursor.isAfter(end)) {
158 - final nextMonthStart = DateTime(cursor.year, cursor.month + 1);  
159 - final monthEndTime = nextMonthStart.subtract(const Duration(seconds: 1)); 290 + final cursorDateStart = DateTime(cursor.year, cursor.month, cursor.day);
  291 + final segmentEndOfDay = cursorDateStart
  292 + .add(Duration(days: fetchChunkDays))
  293 + .subtract(const Duration(seconds: 1));
160 final segmentEndDateTime = 294 final segmentEndDateTime =
161 - monthEndTime.isBefore(end) ? monthEndTime : end; 295 + segmentEndOfDay.isBefore(end) ? segmentEndOfDay : end;
162 final segmentStartTime = _unixSeconds(cursor); 296 final segmentStartTime = _unixSeconds(cursor);
163 final segmentEndTime = _unixSeconds(segmentEndDateTime); 297 final segmentEndTime = _unixSeconds(segmentEndDateTime);
164 ranges.add( 298 ranges.add(
@@ -175,6 +309,12 @@ class OhosHealthRawDataSyncService { @@ -175,6 +309,12 @@ class OhosHealthRawDataSyncService {
175 return ranges; 309 return ranges;
176 } 310 }
177 311
  312 + int _fetchChunkDays(int dataType) {
  313 + return dataType == HealthDataUploadType.heartRate.type
  314 + ? heartRateFetchChunkDays
  315 + : defaultFetchChunkDays;
  316 + }
  317 +
178 void _log(String message) { 318 void _log(String message) {
179 final taggedMessage = '$logMarker $message'; 319 final taggedMessage = '$logMarker $message';
180 debugPrint(taggedMessage); 320 debugPrint(taggedMessage);
@@ -199,10 +339,16 @@ abstract class OhosHealthRawDataRemoteDataSource { @@ -199,10 +339,16 @@ abstract class OhosHealthRawDataRemoteDataSource {
199 abstract class OhosHealthRawDataLocalStore { 339 abstract class OhosHealthRawDataLocalStore {
200 Future<int?> latestDataTime({required int dataType}); 340 Future<int?> latestDataTime({required int dataType});
201 341
202 - Future<void> upsertRawDataBatch({ 342 + Future<int> upsertRawDataBatch({
203 required int dataType, 343 required int dataType,
204 required List<OhosHealthRawDataItem> items, 344 required List<OhosHealthRawDataItem> items,
205 }); 345 });
  346 +
  347 + Future<List<OhosHealthRawDataItem>> queryRawData({
  348 + required int dataType,
  349 + required int startTime,
  350 + required int endTime,
  351 + });
206 } 352 }
207 353
208 abstract class OhosHarmonyRawDataClient { 354 abstract class OhosHarmonyRawDataClient {
@@ -216,13 +362,22 @@ abstract class OhosHarmonyRawDataClient { @@ -216,13 +362,22 @@ abstract class OhosHarmonyRawDataClient {
216 int startDate, 362 int startDate,
217 int endDate, 363 int endDate,
218 ); 364 );
  365 +
  366 + Future<AppResult<HmWorkoutData>> getWorkoutData(
  367 + int startTime,
  368 + int endTime,
  369 + );
219 } 370 }
220 371
221 class OhosHarmonyHealthRawDataRemoteDataSource 372 class OhosHarmonyHealthRawDataRemoteDataSource
222 implements OhosHealthRawDataRemoteDataSource { 373 implements OhosHealthRawDataRemoteDataSource {
223 - const OhosHarmonyHealthRawDataRemoteDataSource(this._client); 374 + OhosHarmonyHealthRawDataRemoteDataSource(
  375 + this._client, {
  376 + DateTime Function()? nowProvider,
  377 + }) : _nowProvider = nowProvider ?? DateTime.now;
224 378
225 final OhosHarmonyRawDataClient _client; 379 final OhosHarmonyRawDataClient _client;
  380 + final DateTime Function() _nowProvider;
226 381
227 @override 382 @override
228 Future<OhosHealthRawDataPage> fetchRawDataPage({ 383 Future<OhosHealthRawDataPage> fetchRawDataPage({
@@ -232,18 +387,34 @@ class OhosHarmonyHealthRawDataRemoteDataSource @@ -232,18 +387,34 @@ class OhosHarmonyHealthRawDataRemoteDataSource
232 String? pageToken, 387 String? pageToken,
233 }) async { 388 }) async {
234 final startDate = _dateKeyFromUnixSeconds(startTime); 389 final startDate = _dateKeyFromUnixSeconds(startTime);
235 - final endDate = _dateKeyFromUnixSeconds(endTime); 390 + final endDate = _endDateKeyFromUnixSeconds(endTime);
236 if (dataType == OhosHealthRawDataType.sleepAnalysis) { 391 if (dataType == OhosHealthRawDataType.sleepAnalysis) {
237 final result = await _client.getSleepData(startDate, endDate); 392 final result = await _client.getSleepData(startDate, endDate);
238 return switch (result) { 393 return switch (result) {
239 AppSuccess<HmSleepData>(:final data) => OhosHealthRawDataPage( 394 AppSuccess<HmSleepData>(:final data) => OhosHealthRawDataPage(
240 - items: _sleepItems(data), 395 + items: _intervalItems(
  396 + data: data,
  397 + fallbackDataType: OhosHealthRawDataType.sleepAnalysis,
  398 + ),
241 ), 399 ),
242 AppFailure<HmSleepData>(:final error) => throw StateError( 400 AppFailure<HmSleepData>(:final error) => throw StateError(
243 'OHOS sleep raw data fetch failed: $error', 401 'OHOS sleep raw data fetch failed: $error',
244 ), 402 ),
245 }; 403 };
246 } 404 }
  405 + if (dataType == OhosHealthRawDataType.workout) {
  406 + final result = await _client.getWorkoutData(startTime, endTime);
  407 + return switch (result) {
  408 + AppSuccess<HmWorkoutData>(:final data) => OhosHealthRawDataPage(
  409 + items: _workoutItems(
  410 + data: data,
  411 + ),
  412 + ),
  413 + AppFailure<HmWorkoutData>(:final error) => throw StateError(
  414 + 'OHOS workout raw data fetch failed: $error',
  415 + ),
  416 + };
  417 + }
247 418
248 final huaweiDataType = _resolveHuaweiDataType(dataType); 419 final huaweiDataType = _resolveHuaweiDataType(dataType);
249 final result = await _client.getHealthData( 420 final result = await _client.getHealthData(
@@ -268,9 +439,6 @@ class OhosHarmonyHealthRawDataRemoteDataSource @@ -268,9 +439,6 @@ class OhosHarmonyHealthRawDataRemoteDataSource
268 if (dataType == OhosHealthRawDataType.activitySummary) { 439 if (dataType == OhosHealthRawDataType.activitySummary) {
269 return HuaweiHealthDataType.activity; 440 return HuaweiHealthDataType.activity;
270 } 441 }
271 - if (dataType == OhosHealthRawDataType.workout) {  
272 - return HuaweiHealthDataType.exerciseDuration;  
273 - }  
274 for (final value in HuaweiHealthDataType.values) { 442 for (final value in HuaweiHealthDataType.values) {
275 if (value.dataType == dataType) { 443 if (value.dataType == dataType) {
276 return value; 444 return value;
@@ -295,19 +463,47 @@ class OhosHarmonyHealthRawDataRemoteDataSource @@ -295,19 +463,47 @@ class OhosHarmonyHealthRawDataRemoteDataSource
295 .toList(growable: false); 463 .toList(growable: false);
296 } 464 }
297 465
298 - List<OhosHealthRawDataItem> _sleepItems(HmSleepData data) { 466 + List<OhosHealthRawDataItem> _intervalItems({
  467 + required HmSleepData data,
  468 + required int fallbackDataType,
  469 + }) {
299 return (data.list ?? const <HmSleepDataItem>[]) 470 return (data.list ?? const <HmSleepDataItem>[])
300 .where((item) => item.toTime != null) 471 .where((item) => item.toTime != null)
301 .map( 472 .map(
302 (item) => OhosHealthRawDataItem( 473 (item) => OhosHealthRawDataItem(
303 - dataType:  
304 - item.dataType?.toInt() ?? OhosHealthRawDataType.sleepAnalysis, 474 + dataType: item.dataType?.toInt() ?? fallbackDataType,
305 dataTime: item.toTime!.toInt(), 475 dataTime: item.toTime!.toInt(),
306 payload: Map<String, Object?>.from(item.toJson()), 476 payload: Map<String, Object?>.from(item.toJson()),
307 ), 477 ),
308 ) 478 )
309 .toList(growable: false); 479 .toList(growable: false);
310 } 480 }
  481 +
  482 + List<OhosHealthRawDataItem> _workoutItems({
  483 + required HmWorkoutData data,
  484 + }) {
  485 + return (data.list ?? const <HmWorkoutDataItem>[])
  486 + .where((item) => item.endTime != null)
  487 + .map(
  488 + (item) => OhosHealthRawDataItem(
  489 + dataType: OhosHealthRawDataType.workout,
  490 + dataTime: item.endTime!.toInt(),
  491 + payload: Map<String, Object?>.from(item.toJson()),
  492 + ),
  493 + )
  494 + .toList(growable: false);
  495 + }
  496 +
  497 + int _endDateKeyFromUnixSeconds(int seconds) {
  498 + final end = DateTime.fromMillisecondsSinceEpoch(seconds * 1000);
  499 + final now = _nowProvider();
  500 + final today = DateTime(now.year, now.month, now.day);
  501 + final endDate = DateTime(end.year, end.month, end.day);
  502 + if (endDate == today) {
  503 + return _dateKeyFromDateTime(today.add(const Duration(days: 1)));
  504 + }
  505 + return _dateKeyFromDateTime(endDate);
  506 + }
311 } 507 }
312 508
313 class TodoOhosHealthRawDataRemoteDataSource 509 class TodoOhosHealthRawDataRemoteDataSource
@@ -337,11 +533,21 @@ class TodoOhosHealthRawDataLocalStore implements OhosHealthRawDataLocalStore { @@ -337,11 +533,21 @@ class TodoOhosHealthRawDataLocalStore implements OhosHealthRawDataLocalStore {
337 } 533 }
338 534
339 @override 535 @override
340 - Future<void> upsertRawDataBatch({ 536 + Future<int> upsertRawDataBatch({
341 required int dataType, 537 required int dataType,
342 required List<OhosHealthRawDataItem> items, 538 required List<OhosHealthRawDataItem> items,
343 }) async { 539 }) async {
344 // TODO: Store one fetched OHOS raw-data page into local database. 540 // TODO: Store one fetched OHOS raw-data page into local database.
  541 + return 0;
  542 + }
  543 +
  544 + @override
  545 + Future<List<OhosHealthRawDataItem>> queryRawData({
  546 + required int dataType,
  547 + required int startTime,
  548 + required int endTime,
  549 + }) async {
  550 + return const <OhosHealthRawDataItem>[];
345 } 551 }
346 } 552 }
347 553
@@ -397,5 +603,9 @@ class OhosHealthRawDataSyncResult { @@ -397,5 +603,9 @@ class OhosHealthRawDataSyncResult {
397 603
398 int _dateKeyFromUnixSeconds(int seconds) { 604 int _dateKeyFromUnixSeconds(int seconds) {
399 final dateTime = DateTime.fromMillisecondsSinceEpoch(seconds * 1000); 605 final dateTime = DateTime.fromMillisecondsSinceEpoch(seconds * 1000);
  606 + return _dateKeyFromDateTime(dateTime);
  607 +}
  608 +
  609 +int _dateKeyFromDateTime(DateTime dateTime) {
400 return dateTime.year * 10000 + dateTime.month * 100 + dateTime.day; 610 return dateTime.year * 10000 + dateTime.month * 100 + dateTime.day;
401 } 611 }
@@ -38,6 +38,14 @@ class HmSleepDataItem { @@ -38,6 +38,14 @@ class HmSleepDataItem {
38 38
39 num? fromTime; 39 num? fromTime;
40 num? toTime; 40 num? toTime;
  41 + /// 0:未知
  42 + /// 1:深睡
  43 + /// 2:浅睡
  44 + /// 3:REM
  45 + /// 4:清醒
  46 + /// 5:午睡(零星小睡)
  47 + /// 6:卧床
  48 + /// 7:睡眠(手工)
41 num? dataType; 49 num? dataType;
42 50
43 Map<String, dynamic> toJson() { 51 Map<String, dynamic> toJson() {
  1 +class HmWorkoutData {
  2 + HmWorkoutData({
  3 + this.list,
  4 + });
  5 +
  6 + HmWorkoutData.fromJson(dynamic json) {
  7 + if (json['list'] != null) {
  8 + list = <HmWorkoutDataItem>[];
  9 + json['list'].forEach((v) {
  10 + list?.add(HmWorkoutDataItem.fromJson(v));
  11 + });
  12 + }
  13 + }
  14 +
  15 + List<HmWorkoutDataItem>? list;
  16 +
  17 + Map<String, dynamic> toJson() {
  18 + final map = <String, dynamic>{};
  19 + if (list != null) {
  20 + map['list'] = list?.map((v) => v.toJson()).toList();
  21 + }
  22 + return map;
  23 + }
  24 +}
  25 +
  26 +class HmWorkoutDataItem {
  27 + HmWorkoutDataItem({
  28 + this.startTime,
  29 + this.endTime,
  30 + this.activityType,
  31 + });
  32 +
  33 + HmWorkoutDataItem.fromJson(dynamic json) {
  34 + startTime = json['start_time'];
  35 + endTime = json['end_time'];
  36 + activityType = json['activity_type'];
  37 + }
  38 +
  39 + num? startTime;
  40 + num? endTime;
  41 + num? activityType;
  42 +
  43 + Map<String, dynamic> toJson() {
  44 + final map = <String, dynamic>{};
  45 + map['start_time'] = startTime;
  46 + map['end_time'] = endTime;
  47 + map['activity_type'] = activityType;
  48 + return map;
  49 + }
  50 +}
@@ -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,7 +30,7 @@ packages: @@ -30,7 +30,7 @@ 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:
@@ -38,7 +38,7 @@ packages: @@ -38,7 +38,7 @@ packages:
38 description: 38 description:
39 name: async 39 name: async
40 sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" 40 sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
41 - url: "https://pub.dev" 41 + url: "https://pub.flutter-io.cn"
42 source: hosted 42 source: hosted
43 version: "2.11.0" 43 version: "2.11.0"
44 boolean_selector: 44 boolean_selector:
@@ -46,7 +46,7 @@ packages: @@ -46,7 +46,7 @@ packages:
46 description: 46 description:
47 name: boolean_selector 47 name: boolean_selector
48 sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" 48 sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
49 - url: "https://pub.dev" 49 + url: "https://pub.flutter-io.cn"
50 source: hosted 50 source: hosted
51 version: "2.1.1" 51 version: "2.1.1"
52 build: 52 build:
@@ -54,7 +54,7 @@ packages: @@ -54,7 +54,7 @@ packages:
54 description: 54 description:
55 name: build 55 name: build
56 sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0 56 sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0
57 - url: "https://pub.dev" 57 + url: "https://pub.flutter-io.cn"
58 source: hosted 58 source: hosted
59 version: "2.4.2" 59 version: "2.4.2"
60 build_config: 60 build_config:
@@ -62,7 +62,7 @@ packages: @@ -62,7 +62,7 @@ packages:
62 description: 62 description:
63 name: build_config 63 name: build_config
64 sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" 64 sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33"
65 - url: "https://pub.dev" 65 + url: "https://pub.flutter-io.cn"
66 source: hosted 66 source: hosted
67 version: "1.1.2" 67 version: "1.1.2"
68 build_daemon: 68 build_daemon:
@@ -70,7 +70,7 @@ packages: @@ -70,7 +70,7 @@ packages:
70 description: 70 description:
71 name: build_daemon 71 name: build_daemon
72 sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa" 72 sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa"
73 - url: "https://pub.dev" 73 + url: "https://pub.flutter-io.cn"
74 source: hosted 74 source: hosted
75 version: "4.0.4" 75 version: "4.0.4"
76 build_resolvers: 76 build_resolvers:
@@ -78,7 +78,7 @@ packages: @@ -78,7 +78,7 @@ packages:
78 description: 78 description:
79 name: build_resolvers 79 name: build_resolvers
80 sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0 80 sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0
81 - url: "https://pub.dev" 81 + url: "https://pub.flutter-io.cn"
82 source: hosted 82 source: hosted
83 version: "2.4.4" 83 version: "2.4.4"
84 build_runner: 84 build_runner:
@@ -86,7 +86,7 @@ packages: @@ -86,7 +86,7 @@ packages:
86 description: 86 description:
87 name: build_runner 87 name: build_runner
88 sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99" 88 sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99"
89 - url: "https://pub.dev" 89 + url: "https://pub.flutter-io.cn"
90 source: hosted 90 source: hosted
91 version: "2.4.15" 91 version: "2.4.15"
92 build_runner_core: 92 build_runner_core:
@@ -94,7 +94,7 @@ packages: @@ -94,7 +94,7 @@ packages:
94 description: 94 description:
95 name: build_runner_core 95 name: build_runner_core
96 sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021" 96 sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021"
97 - url: "https://pub.dev" 97 + url: "https://pub.flutter-io.cn"
98 source: hosted 98 source: hosted
99 version: "8.0.0" 99 version: "8.0.0"
100 built_collection: 100 built_collection:
@@ -102,7 +102,7 @@ packages: @@ -102,7 +102,7 @@ packages:
102 description: 102 description:
103 name: built_collection 103 name: built_collection
104 sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" 104 sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100"
105 - url: "https://pub.dev" 105 + url: "https://pub.flutter-io.cn"
106 source: hosted 106 source: hosted
107 version: "5.1.1" 107 version: "5.1.1"
108 built_value: 108 built_value:
@@ -110,7 +110,7 @@ packages: @@ -110,7 +110,7 @@ packages:
110 description: 110 description:
111 name: built_value 111 name: built_value
112 sha256: "31b24be6615ec7fcf70b3aa5a7469fe35826485e639a16dd7eb83ba30e4cc6a8" 112 sha256: "31b24be6615ec7fcf70b3aa5a7469fe35826485e639a16dd7eb83ba30e4cc6a8"
113 - url: "https://pub.dev" 113 + url: "https://pub.flutter-io.cn"
114 source: hosted 114 source: hosted
115 version: "8.12.7" 115 version: "8.12.7"
116 cached_network_image: 116 cached_network_image:
@@ -118,7 +118,7 @@ packages: @@ -118,7 +118,7 @@ packages:
118 description: 118 description:
119 name: cached_network_image 119 name: cached_network_image
120 sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916" 120 sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916"
121 - url: "https://pub.dev" 121 + url: "https://pub.flutter-io.cn"
122 source: hosted 122 source: hosted
123 version: "3.4.1" 123 version: "3.4.1"
124 cached_network_image_platform_interface: 124 cached_network_image_platform_interface:
@@ -126,7 +126,7 @@ packages: @@ -126,7 +126,7 @@ packages:
126 description: 126 description:
127 name: cached_network_image_platform_interface 127 name: cached_network_image_platform_interface
128 sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829" 128 sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829"
129 - url: "https://pub.dev" 129 + url: "https://pub.flutter-io.cn"
130 source: hosted 130 source: hosted
131 version: "4.1.1" 131 version: "4.1.1"
132 cached_network_image_web: 132 cached_network_image_web:
@@ -134,7 +134,7 @@ packages: @@ -134,7 +134,7 @@ packages:
134 description: 134 description:
135 name: cached_network_image_web 135 name: cached_network_image_web
136 sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062" 136 sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062"
137 - url: "https://pub.dev" 137 + url: "https://pub.flutter-io.cn"
138 source: hosted 138 source: hosted
139 version: "1.3.1" 139 version: "1.3.1"
140 characters: 140 characters:
@@ -142,7 +142,7 @@ packages: @@ -142,7 +142,7 @@ packages:
142 description: 142 description:
143 name: characters 143 name: characters
144 sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" 144 sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
145 - url: "https://pub.dev" 145 + url: "https://pub.flutter-io.cn"
146 source: hosted 146 source: hosted
147 version: "1.3.0" 147 version: "1.3.0"
148 checked_yaml: 148 checked_yaml:
@@ -150,7 +150,7 @@ packages: @@ -150,7 +150,7 @@ packages:
150 description: 150 description:
151 name: checked_yaml 151 name: checked_yaml
152 sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff 152 sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
153 - url: "https://pub.dev" 153 + url: "https://pub.flutter-io.cn"
154 source: hosted 154 source: hosted
155 version: "2.0.3" 155 version: "2.0.3"
156 clock: 156 clock:
@@ -158,7 +158,7 @@ packages: @@ -158,7 +158,7 @@ packages:
158 description: 158 description:
159 name: clock 159 name: clock
160 sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf 160 sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
161 - url: "https://pub.dev" 161 + url: "https://pub.flutter-io.cn"
162 source: hosted 162 source: hosted
163 version: "1.1.1" 163 version: "1.1.1"
164 code_builder: 164 code_builder:
@@ -166,7 +166,7 @@ packages: @@ -166,7 +166,7 @@ packages:
166 description: 166 description:
167 name: code_builder 167 name: code_builder
168 sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e" 168 sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e"
169 - url: "https://pub.dev" 169 + url: "https://pub.flutter-io.cn"
170 source: hosted 170 source: hosted
171 version: "4.10.1" 171 version: "4.10.1"
172 collection: 172 collection:
@@ -174,7 +174,7 @@ packages: @@ -174,7 +174,7 @@ packages:
174 description: 174 description:
175 name: collection 175 name: collection
176 sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf 176 sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf
177 - url: "https://pub.dev" 177 + url: "https://pub.flutter-io.cn"
178 source: hosted 178 source: hosted
179 version: "1.19.0" 179 version: "1.19.0"
180 convert: 180 convert:
@@ -182,7 +182,7 @@ packages: @@ -182,7 +182,7 @@ packages:
182 description: 182 description:
183 name: convert 183 name: convert
184 sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 184 sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
185 - url: "https://pub.dev" 185 + url: "https://pub.flutter-io.cn"
186 source: hosted 186 source: hosted
187 version: "3.1.2" 187 version: "3.1.2"
188 cross_file: 188 cross_file:
@@ -190,7 +190,7 @@ packages: @@ -190,7 +190,7 @@ packages:
190 description: 190 description:
191 name: cross_file 191 name: cross_file
192 sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" 192 sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670"
193 - url: "https://pub.dev" 193 + url: "https://pub.flutter-io.cn"
194 source: hosted 194 source: hosted
195 version: "0.3.4+2" 195 version: "0.3.4+2"
196 crypto: 196 crypto:
@@ -198,7 +198,7 @@ packages: @@ -198,7 +198,7 @@ packages:
198 description: 198 description:
199 name: crypto 199 name: crypto
200 sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf 200 sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
201 - url: "https://pub.dev" 201 + url: "https://pub.flutter-io.cn"
202 source: hosted 202 source: hosted
203 version: "3.0.7" 203 version: "3.0.7"
204 cupertino_icons: 204 cupertino_icons:
@@ -206,7 +206,7 @@ packages: @@ -206,7 +206,7 @@ packages:
206 description: 206 description:
207 name: cupertino_icons 207 name: cupertino_icons
208 sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 208 sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
209 - url: "https://pub.dev" 209 + url: "https://pub.flutter-io.cn"
210 source: hosted 210 source: hosted
211 version: "1.0.8" 211 version: "1.0.8"
212 dart_style: 212 dart_style:
@@ -214,7 +214,7 @@ packages: @@ -214,7 +214,7 @@ packages:
214 description: 214 description:
215 name: dart_style 215 name: dart_style
216 sha256: "27eb0ae77836989a3bc541ce55595e8ceee0992807f14511552a898ddd0d88ac" 216 sha256: "27eb0ae77836989a3bc541ce55595e8ceee0992807f14511552a898ddd0d88ac"
217 - url: "https://pub.dev" 217 + url: "https://pub.flutter-io.cn"
218 source: hosted 218 source: hosted
219 version: "3.0.1" 219 version: "3.0.1"
220 dio: 220 dio:
@@ -222,7 +222,7 @@ packages: @@ -222,7 +222,7 @@ packages:
222 description: 222 description:
223 name: dio 223 name: dio
224 sha256: "0df44ebba85e503958eb75d07eedd3c86275a58c1d3eda2f2ce8f0a2c3abbb3c" 224 sha256: "0df44ebba85e503958eb75d07eedd3c86275a58c1d3eda2f2ce8f0a2c3abbb3c"
225 - url: "https://pub.dev" 225 + url: "https://pub.flutter-io.cn"
226 source: hosted 226 source: hosted
227 version: "5.11.0" 227 version: "5.11.0"
228 dio_web_adapter: 228 dio_web_adapter:
@@ -230,7 +230,7 @@ packages: @@ -230,7 +230,7 @@ packages:
230 description: 230 description:
231 name: dio_web_adapter 231 name: dio_web_adapter
232 sha256: "0786d0b7295a373de356fc0af4f6f1d0ab2844ed31b19dfc5e7556b70e24212c" 232 sha256: "0786d0b7295a373de356fc0af4f6f1d0ab2844ed31b19dfc5e7556b70e24212c"
233 - url: "https://pub.dev" 233 + url: "https://pub.flutter-io.cn"
234 source: hosted 234 source: hosted
235 version: "2.2.1" 235 version: "2.2.1"
236 equatable: 236 equatable:
@@ -238,7 +238,7 @@ packages: @@ -238,7 +238,7 @@ packages:
238 description: 238 description:
239 name: equatable 239 name: equatable
240 sha256: "3bce007a596ff8b3119c45d68aaef631272537c03d30e5d4534dd24bf4c5eaa2" 240 sha256: "3bce007a596ff8b3119c45d68aaef631272537c03d30e5d4534dd24bf4c5eaa2"
241 - url: "https://pub.dev" 241 + url: "https://pub.flutter-io.cn"
242 source: hosted 242 source: hosted
243 version: "2.1.0" 243 version: "2.1.0"
244 fake_async: 244 fake_async:
@@ -246,7 +246,7 @@ packages: @@ -246,7 +246,7 @@ packages:
246 description: 246 description:
247 name: fake_async 247 name: fake_async
248 sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" 248 sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
249 - url: "https://pub.dev" 249 + url: "https://pub.flutter-io.cn"
250 source: hosted 250 source: hosted
251 version: "1.3.1" 251 version: "1.3.1"
252 ffi: 252 ffi:
@@ -254,7 +254,7 @@ packages: @@ -254,7 +254,7 @@ packages:
254 description: 254 description:
255 name: ffi 255 name: ffi
256 sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" 256 sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
257 - url: "https://pub.dev" 257 + url: "https://pub.flutter-io.cn"
258 source: hosted 258 source: hosted
259 version: "2.1.3" 259 version: "2.1.3"
260 file: 260 file:
@@ -262,7 +262,7 @@ packages: @@ -262,7 +262,7 @@ packages:
262 description: 262 description:
263 name: file 263 name: file
264 sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 264 sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
265 - url: "https://pub.dev" 265 + url: "https://pub.flutter-io.cn"
266 source: hosted 266 source: hosted
267 version: "7.0.1" 267 version: "7.0.1"
268 file_selector_linux: 268 file_selector_linux:
@@ -270,7 +270,7 @@ packages: @@ -270,7 +270,7 @@ packages:
270 description: 270 description:
271 name: file_selector_linux 271 name: file_selector_linux
272 sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33" 272 sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33"
273 - url: "https://pub.dev" 273 + url: "https://pub.flutter-io.cn"
274 source: hosted 274 source: hosted
275 version: "0.9.3+2" 275 version: "0.9.3+2"
276 file_selector_macos: 276 file_selector_macos:
@@ -278,7 +278,7 @@ packages: @@ -278,7 +278,7 @@ packages:
278 description: 278 description:
279 name: file_selector_macos 279 name: file_selector_macos
280 sha256: "8c9250b2bd2d8d4268e39c82543bacbaca0fda7d29e0728c3c4bbb7c820fd711" 280 sha256: "8c9250b2bd2d8d4268e39c82543bacbaca0fda7d29e0728c3c4bbb7c820fd711"
281 - url: "https://pub.dev" 281 + url: "https://pub.flutter-io.cn"
282 source: hosted 282 source: hosted
283 version: "0.9.4+3" 283 version: "0.9.4+3"
284 file_selector_platform_interface: 284 file_selector_platform_interface:
@@ -286,7 +286,7 @@ packages: @@ -286,7 +286,7 @@ packages:
286 description: 286 description:
287 name: file_selector_platform_interface 287 name: file_selector_platform_interface
288 sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b 288 sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b
289 - url: "https://pub.dev" 289 + url: "https://pub.flutter-io.cn"
290 source: hosted 290 source: hosted
291 version: "2.6.2" 291 version: "2.6.2"
292 file_selector_windows: 292 file_selector_windows:
@@ -294,7 +294,7 @@ packages: @@ -294,7 +294,7 @@ packages:
294 description: 294 description:
295 name: file_selector_windows 295 name: file_selector_windows
296 sha256: "320fcfb6f33caa90f0b58380489fc5ac05d99ee94b61aa96ec2bff0ba81d3c2b" 296 sha256: "320fcfb6f33caa90f0b58380489fc5ac05d99ee94b61aa96ec2bff0ba81d3c2b"
297 - url: "https://pub.dev" 297 + url: "https://pub.flutter-io.cn"
298 source: hosted 298 source: hosted
299 version: "0.9.3+4" 299 version: "0.9.3+4"
300 fixnum: 300 fixnum:
@@ -302,7 +302,7 @@ packages: @@ -302,7 +302,7 @@ packages:
302 description: 302 description:
303 name: fixnum 303 name: fixnum
304 sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be 304 sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
305 - url: "https://pub.dev" 305 + url: "https://pub.flutter-io.cn"
306 source: hosted 306 source: hosted
307 version: "1.1.1" 307 version: "1.1.1"
308 fl_chart: 308 fl_chart:
@@ -310,7 +310,7 @@ packages: @@ -310,7 +310,7 @@ packages:
310 description: 310 description:
311 name: fl_chart 311 name: fl_chart
312 sha256: "5276944c6ffc975ae796569a826c38a62d2abcf264e26b88fa6f482e107f4237" 312 sha256: "5276944c6ffc975ae796569a826c38a62d2abcf264e26b88fa6f482e107f4237"
313 - url: "https://pub.dev" 313 + url: "https://pub.flutter-io.cn"
314 source: hosted 314 source: hosted
315 version: "0.70.2" 315 version: "0.70.2"
316 flutter: 316 flutter:
@@ -323,7 +323,7 @@ packages: @@ -323,7 +323,7 @@ packages:
323 description: 323 description:
324 name: flutter_cache_manager 324 name: flutter_cache_manager
325 sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386" 325 sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386"
326 - url: "https://pub.dev" 326 + url: "https://pub.flutter-io.cn"
327 source: hosted 327 source: hosted
328 version: "3.4.1" 328 version: "3.4.1"
329 flutter_lints: 329 flutter_lints:
@@ -331,7 +331,7 @@ packages: @@ -331,7 +331,7 @@ packages:
331 description: 331 description:
332 name: flutter_lints 332 name: flutter_lints
333 sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" 333 sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1"
334 - url: "https://pub.dev" 334 + url: "https://pub.flutter-io.cn"
335 source: hosted 335 source: hosted
336 version: "5.0.0" 336 version: "5.0.0"
337 flutter_localizations: 337 flutter_localizations:
@@ -344,7 +344,7 @@ packages: @@ -344,7 +344,7 @@ packages:
344 description: 344 description:
345 name: flutter_plugin_android_lifecycle 345 name: flutter_plugin_android_lifecycle
346 sha256: "6382ce712ff69b0f719640ce957559dde459e55ecd433c767e06d139ddf16cab" 346 sha256: "6382ce712ff69b0f719640ce957559dde459e55ecd433c767e06d139ddf16cab"
347 - url: "https://pub.dev" 347 + url: "https://pub.flutter-io.cn"
348 source: hosted 348 source: hosted
349 version: "2.0.29" 349 version: "2.0.29"
350 flutter_test: 350 flutter_test:
@@ -357,7 +357,7 @@ packages: @@ -357,7 +357,7 @@ packages:
357 description: 357 description:
358 name: flutter_timezone 358 name: flutter_timezone
359 sha256: "869677426fde92dbe170fb7d2d4929f2a8343c2f5f62f08b0bb64f908630b073" 359 sha256: "869677426fde92dbe170fb7d2d4929f2a8343c2f5f62f08b0bb64f908630b073"
360 - url: "https://pub.dev" 360 + url: "https://pub.flutter-io.cn"
361 source: hosted 361 source: hosted
362 version: "5.1.0" 362 version: "5.1.0"
363 flutter_web_plugins: 363 flutter_web_plugins:
@@ -379,7 +379,7 @@ packages: @@ -379,7 +379,7 @@ packages:
379 description: 379 description:
380 name: frontend_server_client 380 name: frontend_server_client
381 sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 381 sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
382 - url: "https://pub.dev" 382 + url: "https://pub.flutter-io.cn"
383 source: hosted 383 source: hosted
384 version: "4.0.0" 384 version: "4.0.0"
385 get: 385 get:
@@ -387,7 +387,7 @@ packages: @@ -387,7 +387,7 @@ packages:
387 description: 387 description:
388 name: get 388 name: get
389 sha256: "5ed34a7925b85336e15d472cc4cfe7d9ebf4ab8e8b9f688585bf6b50f4c3d79a" 389 sha256: "5ed34a7925b85336e15d472cc4cfe7d9ebf4ab8e8b9f688585bf6b50f4c3d79a"
390 - url: "https://pub.dev" 390 + url: "https://pub.flutter-io.cn"
391 source: hosted 391 source: hosted
392 version: "4.7.3" 392 version: "4.7.3"
393 glob: 393 glob:
@@ -395,7 +395,7 @@ packages: @@ -395,7 +395,7 @@ packages:
395 description: 395 description:
396 name: glob 396 name: glob
397 sha256: "218aeb56050c714f62a3182775320dfa04602b55074873e24e31bbd39bda96fb" 397 sha256: "218aeb56050c714f62a3182775320dfa04602b55074873e24e31bbd39bda96fb"
398 - url: "https://pub.dev" 398 + url: "https://pub.flutter-io.cn"
399 source: hosted 399 source: hosted
400 version: "2.2.0" 400 version: "2.2.0"
401 graphs: 401 graphs:
@@ -403,7 +403,7 @@ packages: @@ -403,7 +403,7 @@ packages:
403 description: 403 description:
404 name: graphs 404 name: graphs
405 sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" 405 sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0"
406 - url: "https://pub.dev" 406 + url: "https://pub.flutter-io.cn"
407 source: hosted 407 source: hosted
408 version: "2.3.2" 408 version: "2.3.2"
409 http: 409 http:
@@ -411,7 +411,7 @@ packages: @@ -411,7 +411,7 @@ packages:
411 description: 411 description:
412 name: http 412 name: http
413 sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" 413 sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
414 - url: "https://pub.dev" 414 + url: "https://pub.flutter-io.cn"
415 source: hosted 415 source: hosted
416 version: "1.6.0" 416 version: "1.6.0"
417 http_multi_server: 417 http_multi_server:
@@ -419,7 +419,7 @@ packages: @@ -419,7 +419,7 @@ packages:
419 description: 419 description:
420 name: http_multi_server 420 name: http_multi_server
421 sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 421 sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8
422 - url: "https://pub.dev" 422 + url: "https://pub.flutter-io.cn"
423 source: hosted 423 source: hosted
424 version: "3.2.2" 424 version: "3.2.2"
425 http_parser: 425 http_parser:
@@ -427,7 +427,7 @@ packages: @@ -427,7 +427,7 @@ packages:
427 description: 427 description:
428 name: http_parser 428 name: http_parser
429 sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" 429 sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
430 - url: "https://pub.dev" 430 + url: "https://pub.flutter-io.cn"
431 source: hosted 431 source: hosted
432 version: "4.1.2" 432 version: "4.1.2"
433 image_cropper: 433 image_cropper:
@@ -471,7 +471,7 @@ packages: @@ -471,7 +471,7 @@ packages:
471 description: 471 description:
472 name: image_picker_android 472 name: image_picker_android
473 sha256: e83b2b05141469c5e19d77e1dfa11096b6b1567d09065b2265d7c6904560050c 473 sha256: e83b2b05141469c5e19d77e1dfa11096b6b1567d09065b2265d7c6904560050c
474 - url: "https://pub.dev" 474 + url: "https://pub.flutter-io.cn"
475 source: hosted 475 source: hosted
476 version: "0.8.13" 476 version: "0.8.13"
477 image_picker_for_web: 477 image_picker_for_web:
@@ -479,7 +479,7 @@ packages: @@ -479,7 +479,7 @@ packages:
479 description: 479 description:
480 name: image_picker_for_web 480 name: image_picker_for_web
481 sha256: "40c2a6a0da15556dc0f8e38a3246064a971a9f512386c3339b89f76db87269b6" 481 sha256: "40c2a6a0da15556dc0f8e38a3246064a971a9f512386c3339b89f76db87269b6"
482 - url: "https://pub.dev" 482 + url: "https://pub.flutter-io.cn"
483 source: hosted 483 source: hosted
484 version: "3.1.0" 484 version: "3.1.0"
485 image_picker_ios: 485 image_picker_ios:
@@ -487,7 +487,7 @@ packages: @@ -487,7 +487,7 @@ packages:
487 description: 487 description:
488 name: image_picker_ios 488 name: image_picker_ios
489 sha256: eb06fe30bab4c4497bad449b66448f50edcc695f1c59408e78aa3a8059eb8f0e 489 sha256: eb06fe30bab4c4497bad449b66448f50edcc695f1c59408e78aa3a8059eb8f0e
490 - url: "https://pub.dev" 490 + url: "https://pub.flutter-io.cn"
491 source: hosted 491 source: hosted
492 version: "0.8.13" 492 version: "0.8.13"
493 image_picker_linux: 493 image_picker_linux:
@@ -495,7 +495,7 @@ packages: @@ -495,7 +495,7 @@ packages:
495 description: 495 description:
496 name: image_picker_linux 496 name: image_picker_linux
497 sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4" 497 sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4"
498 - url: "https://pub.dev" 498 + url: "https://pub.flutter-io.cn"
499 source: hosted 499 source: hosted
500 version: "0.2.2" 500 version: "0.2.2"
501 image_picker_macos: 501 image_picker_macos:
@@ -503,7 +503,7 @@ packages: @@ -503,7 +503,7 @@ packages:
503 description: 503 description:
504 name: image_picker_macos 504 name: image_picker_macos
505 sha256: d58cd9d67793d52beefd6585b12050af0a7663c0c2a6ece0fb110a35d6955e04 505 sha256: d58cd9d67793d52beefd6585b12050af0a7663c0c2a6ece0fb110a35d6955e04
506 - url: "https://pub.dev" 506 + url: "https://pub.flutter-io.cn"
507 source: hosted 507 source: hosted
508 version: "0.2.2" 508 version: "0.2.2"
509 image_picker_ohos: 509 image_picker_ohos:
@@ -520,7 +520,7 @@ packages: @@ -520,7 +520,7 @@ packages:
520 description: 520 description:
521 name: image_picker_platform_interface 521 name: image_picker_platform_interface
522 sha256: "9f143b0dba3e459553209e20cc425c9801af48e6dfa4f01a0fcf927be3f41665" 522 sha256: "9f143b0dba3e459553209e20cc425c9801af48e6dfa4f01a0fcf927be3f41665"
523 - url: "https://pub.dev" 523 + url: "https://pub.flutter-io.cn"
524 source: hosted 524 source: hosted
525 version: "2.11.0" 525 version: "2.11.0"
526 image_picker_windows: 526 image_picker_windows:
@@ -528,7 +528,7 @@ packages: @@ -528,7 +528,7 @@ packages:
528 description: 528 description:
529 name: image_picker_windows 529 name: image_picker_windows
530 sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae 530 sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae
531 - url: "https://pub.dev" 531 + url: "https://pub.flutter-io.cn"
532 source: hosted 532 source: hosted
533 version: "0.2.2" 533 version: "0.2.2"
534 intl: 534 intl:
@@ -536,7 +536,7 @@ packages: @@ -536,7 +536,7 @@ packages:
536 description: 536 description:
537 name: intl 537 name: intl
538 sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf 538 sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
539 - url: "https://pub.dev" 539 + url: "https://pub.flutter-io.cn"
540 source: hosted 540 source: hosted
541 version: "0.19.0" 541 version: "0.19.0"
542 io: 542 io:
@@ -544,7 +544,7 @@ packages: @@ -544,7 +544,7 @@ packages:
544 description: 544 description:
545 name: io 545 name: io
546 sha256: "2635216ca6a737e60de577ffa1a48a0bec76ca8a62917cfc1bb88c14c570646f" 546 sha256: "2635216ca6a737e60de577ffa1a48a0bec76ca8a62917cfc1bb88c14c570646f"
547 - url: "https://pub.dev" 547 + url: "https://pub.flutter-io.cn"
548 source: hosted 548 source: hosted
549 version: "1.1.0" 549 version: "1.1.0"
550 js: 550 js:
@@ -552,7 +552,7 @@ packages: @@ -552,7 +552,7 @@ packages:
552 description: 552 description:
553 name: js 553 name: js
554 sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf 554 sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf
555 - url: "https://pub.dev" 555 + url: "https://pub.flutter-io.cn"
556 source: hosted 556 source: hosted
557 version: "0.7.1" 557 version: "0.7.1"
558 json_annotation: 558 json_annotation:
@@ -560,7 +560,7 @@ packages: @@ -560,7 +560,7 @@ packages:
560 description: 560 description:
561 name: json_annotation 561 name: json_annotation
562 sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" 562 sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
563 - url: "https://pub.dev" 563 + url: "https://pub.flutter-io.cn"
564 source: hosted 564 source: hosted
565 version: "4.9.0" 565 version: "4.9.0"
566 leak_tracker: 566 leak_tracker:
@@ -568,7 +568,7 @@ packages: @@ -568,7 +568,7 @@ packages:
568 description: 568 description:
569 name: leak_tracker 569 name: leak_tracker
570 sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" 570 sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06"
571 - url: "https://pub.dev" 571 + url: "https://pub.flutter-io.cn"
572 source: hosted 572 source: hosted
573 version: "10.0.7" 573 version: "10.0.7"
574 leak_tracker_flutter_testing: 574 leak_tracker_flutter_testing:
@@ -576,7 +576,7 @@ packages: @@ -576,7 +576,7 @@ packages:
576 description: 576 description:
577 name: leak_tracker_flutter_testing 577 name: leak_tracker_flutter_testing
578 sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" 578 sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379"
579 - url: "https://pub.dev" 579 + url: "https://pub.flutter-io.cn"
580 source: hosted 580 source: hosted
581 version: "3.0.8" 581 version: "3.0.8"
582 leak_tracker_testing: 582 leak_tracker_testing:
@@ -584,7 +584,7 @@ packages: @@ -584,7 +584,7 @@ packages:
584 description: 584 description:
585 name: leak_tracker_testing 585 name: leak_tracker_testing
586 sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" 586 sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
587 - url: "https://pub.dev" 587 + url: "https://pub.flutter-io.cn"
588 source: hosted 588 source: hosted
589 version: "3.0.1" 589 version: "3.0.1"
590 lints: 590 lints:
@@ -592,7 +592,7 @@ packages: @@ -592,7 +592,7 @@ packages:
592 description: 592 description:
593 name: lints 593 name: lints
594 sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 594 sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7
595 - url: "https://pub.dev" 595 + url: "https://pub.flutter-io.cn"
596 source: hosted 596 source: hosted
597 version: "5.1.1" 597 version: "5.1.1"
598 logger: 598 logger:
@@ -600,7 +600,7 @@ packages: @@ -600,7 +600,7 @@ packages:
600 description: 600 description:
601 name: logger 601 name: logger
602 sha256: "25aee487596a6257655a1e091ec2ae66bc30e7af663592cc3a27e6591e05035c" 602 sha256: "25aee487596a6257655a1e091ec2ae66bc30e7af663592cc3a27e6591e05035c"
603 - url: "https://pub.dev" 603 + url: "https://pub.flutter-io.cn"
604 source: hosted 604 source: hosted
605 version: "2.7.0" 605 version: "2.7.0"
606 logging: 606 logging:
@@ -608,7 +608,7 @@ packages: @@ -608,7 +608,7 @@ packages:
608 description: 608 description:
609 name: logging 609 name: logging
610 sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 610 sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
611 - url: "https://pub.dev" 611 + url: "https://pub.flutter-io.cn"
612 source: hosted 612 source: hosted
613 version: "1.3.0" 613 version: "1.3.0"
614 lottie: 614 lottie:
@@ -616,7 +616,7 @@ packages: @@ -616,7 +616,7 @@ packages:
616 description: 616 description:
617 name: lottie 617 name: lottie
618 sha256: c5fa04a80a620066c15cf19cc44773e19e9b38e989ff23ea32e5903ef1015950 618 sha256: c5fa04a80a620066c15cf19cc44773e19e9b38e989ff23ea32e5903ef1015950
619 - url: "https://pub.dev" 619 + url: "https://pub.flutter-io.cn"
620 source: hosted 620 source: hosted
621 version: "3.3.1" 621 version: "3.3.1"
622 matcher: 622 matcher:
@@ -624,7 +624,7 @@ packages: @@ -624,7 +624,7 @@ packages:
624 description: 624 description:
625 name: matcher 625 name: matcher
626 sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb 626 sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
627 - url: "https://pub.dev" 627 + url: "https://pub.flutter-io.cn"
628 source: hosted 628 source: hosted
629 version: "0.12.16+1" 629 version: "0.12.16+1"
630 material_color_utilities: 630 material_color_utilities:
@@ -632,7 +632,7 @@ packages: @@ -632,7 +632,7 @@ packages:
632 description: 632 description:
633 name: material_color_utilities 633 name: material_color_utilities
634 sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec 634 sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
635 - url: "https://pub.dev" 635 + url: "https://pub.flutter-io.cn"
636 source: hosted 636 source: hosted
637 version: "0.11.1" 637 version: "0.11.1"
638 meta: 638 meta:
@@ -640,7 +640,7 @@ packages: @@ -640,7 +640,7 @@ packages:
640 description: 640 description:
641 name: meta 641 name: meta
642 sha256: "307249ce4ff29d58a18e97f6345f539382eb9c9c29ecda628900f31de0443dd9" 642 sha256: "307249ce4ff29d58a18e97f6345f539382eb9c9c29ecda628900f31de0443dd9"
643 - url: "https://pub.dev" 643 + url: "https://pub.flutter-io.cn"
644 source: hosted 644 source: hosted
645 version: "1.19.0" 645 version: "1.19.0"
646 mime: 646 mime:
@@ -648,7 +648,7 @@ packages: @@ -648,7 +648,7 @@ packages:
648 description: 648 description:
649 name: mime 649 name: mime
650 sha256: bd47de35f07e27267e69c8c8b22edf9473bfee170a60d60fcc93730c5144b7f6 650 sha256: bd47de35f07e27267e69c8c8b22edf9473bfee170a60d60fcc93730c5144b7f6
651 - url: "https://pub.dev" 651 + url: "https://pub.flutter-io.cn"
652 source: hosted 652 source: hosted
653 version: "2.1.0" 653 version: "2.1.0"
654 octo_image: 654 octo_image:
@@ -656,7 +656,7 @@ packages: @@ -656,7 +656,7 @@ packages:
656 description: 656 description:
657 name: octo_image 657 name: octo_image
658 sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd" 658 sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd"
659 - url: "https://pub.dev" 659 + url: "https://pub.flutter-io.cn"
660 source: hosted 660 source: hosted
661 version: "2.1.0" 661 version: "2.1.0"
662 package_config: 662 package_config:
@@ -664,7 +664,7 @@ packages: @@ -664,7 +664,7 @@ packages:
664 description: 664 description:
665 name: package_config 665 name: package_config
666 sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc 666 sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc
667 - url: "https://pub.dev" 667 + url: "https://pub.flutter-io.cn"
668 source: hosted 668 source: hosted
669 version: "2.2.0" 669 version: "2.2.0"
670 path: 670 path:
@@ -672,23 +672,24 @@ packages: @@ -672,23 +672,24 @@ packages:
672 description: 672 description:
673 name: path 673 name: path
674 sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" 674 sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
675 - url: "https://pub.dev" 675 + url: "https://pub.flutter-io.cn"
676 source: hosted 676 source: hosted
677 version: "1.9.0" 677 version: "1.9.0"
678 path_provider: 678 path_provider:
679 dependency: "direct main" 679 dependency: "direct main"
680 description: 680 description:
681 - name: path_provider  
682 - sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"  
683 - url: "https://pub.dev"  
684 - source: hosted  
685 - version: "2.1.5" 681 + path: "packages/path_provider/path_provider"
  682 + ref: master
  683 + resolved-ref: d25cb31f29abf5b1d67364c184b3f354b5ad1928
  684 + url: "https://gitcode.com/openharmony-sig/flutter_packages.git"
  685 + source: git
  686 + version: "2.1.0"
686 path_provider_android: 687 path_provider_android:
687 dependency: transitive 688 dependency: transitive
688 description: 689 description:
689 name: path_provider_android 690 name: path_provider_android
690 sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9 691 sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9
691 - url: "https://pub.dev" 692 + url: "https://pub.flutter-io.cn"
692 source: hosted 693 source: hosted
693 version: "2.2.17" 694 version: "2.2.17"
694 path_provider_foundation: 695 path_provider_foundation:
@@ -696,7 +697,7 @@ packages: @@ -696,7 +697,7 @@ packages:
696 description: 697 description:
697 name: path_provider_foundation 698 name: path_provider_foundation
698 sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" 699 sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942"
699 - url: "https://pub.dev" 700 + url: "https://pub.flutter-io.cn"
700 source: hosted 701 source: hosted
701 version: "2.4.1" 702 version: "2.4.1"
702 path_provider_linux: 703 path_provider_linux:
@@ -704,15 +705,24 @@ packages: @@ -704,15 +705,24 @@ packages:
704 description: 705 description:
705 name: path_provider_linux 706 name: path_provider_linux
706 sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 707 sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
707 - url: "https://pub.dev" 708 + url: "https://pub.flutter-io.cn"
708 source: hosted 709 source: hosted
709 version: "2.2.1" 710 version: "2.2.1"
  711 + path_provider_ohos:
  712 + dependency: transitive
  713 + description:
  714 + path: "packages/path_provider/path_provider_ohos"
  715 + ref: HEAD
  716 + resolved-ref: d25cb31f29abf5b1d67364c184b3f354b5ad1928
  717 + url: "https://gitcode.com/openharmony-tpc/flutter_packages.git"
  718 + source: git
  719 + version: "2.2.1"
710 path_provider_platform_interface: 720 path_provider_platform_interface:
711 dependency: transitive 721 dependency: transitive
712 description: 722 description:
713 name: path_provider_platform_interface 723 name: path_provider_platform_interface
714 sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" 724 sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
715 - url: "https://pub.dev" 725 + url: "https://pub.flutter-io.cn"
716 source: hosted 726 source: hosted
717 version: "2.1.2" 727 version: "2.1.2"
718 path_provider_windows: 728 path_provider_windows:
@@ -720,7 +730,7 @@ packages: @@ -720,7 +730,7 @@ packages:
720 description: 730 description:
721 name: path_provider_windows 731 name: path_provider_windows
722 sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 732 sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
723 - url: "https://pub.dev" 733 + url: "https://pub.flutter-io.cn"
724 source: hosted 734 source: hosted
725 version: "2.3.0" 735 version: "2.3.0"
726 permission_handler: 736 permission_handler:
@@ -728,7 +738,7 @@ packages: @@ -728,7 +738,7 @@ packages:
728 description: 738 description:
729 name: permission_handler 739 name: permission_handler
730 sha256: "59adad729136f01ea9e35a48f5d1395e25cba6cea552249ddbe9cf950f5d7849" 740 sha256: "59adad729136f01ea9e35a48f5d1395e25cba6cea552249ddbe9cf950f5d7849"
731 - url: "https://pub.dev" 741 + url: "https://pub.flutter-io.cn"
732 source: hosted 742 source: hosted
733 version: "11.4.0" 743 version: "11.4.0"
734 permission_handler_android: 744 permission_handler_android:
@@ -736,7 +746,7 @@ packages: @@ -736,7 +746,7 @@ packages:
736 description: 746 description:
737 name: permission_handler_android 747 name: permission_handler_android
738 sha256: d3971dcdd76182a0c198c096b5db2f0884b0d4196723d21a866fc4cdea057ebc 748 sha256: d3971dcdd76182a0c198c096b5db2f0884b0d4196723d21a866fc4cdea057ebc
739 - url: "https://pub.dev" 749 + url: "https://pub.flutter-io.cn"
740 source: hosted 750 source: hosted
741 version: "12.1.0" 751 version: "12.1.0"
742 permission_handler_apple: 752 permission_handler_apple:
@@ -744,7 +754,7 @@ packages: @@ -744,7 +754,7 @@ packages:
744 description: 754 description:
745 name: permission_handler_apple 755 name: permission_handler_apple
746 sha256: f49cb15a064ea9d974fc7fbb302099353b7b170d07284e86e264561579e5bcf8 756 sha256: f49cb15a064ea9d974fc7fbb302099353b7b170d07284e86e264561579e5bcf8
747 - url: "https://pub.dev" 757 + url: "https://pub.flutter-io.cn"
748 source: hosted 758 source: hosted
749 version: "9.6.1" 759 version: "9.6.1"
750 permission_handler_html: 760 permission_handler_html:
@@ -752,7 +762,7 @@ packages: @@ -752,7 +762,7 @@ packages:
752 description: 762 description:
753 name: permission_handler_html 763 name: permission_handler_html
754 sha256: "6ea98b3f17f60d3b527f2647ed2ab4dc0f6bfe25b22cb1c363f5d8f62252f6ac" 764 sha256: "6ea98b3f17f60d3b527f2647ed2ab4dc0f6bfe25b22cb1c363f5d8f62252f6ac"
755 - url: "https://pub.dev" 765 + url: "https://pub.flutter-io.cn"
756 source: hosted 766 source: hosted
757 version: "0.1.4+1" 767 version: "0.1.4+1"
758 permission_handler_ohos: 768 permission_handler_ohos:
@@ -769,7 +779,7 @@ packages: @@ -769,7 +779,7 @@ packages:
769 description: 779 description:
770 name: permission_handler_platform_interface 780 name: permission_handler_platform_interface
771 sha256: a5c8a97ecf5616112a5b16d4b8e9ec0e5ae90ef63ac69c0d7b8ae240be760b23 781 sha256: a5c8a97ecf5616112a5b16d4b8e9ec0e5ae90ef63ac69c0d7b8ae240be760b23
772 - url: "https://pub.dev" 782 + url: "https://pub.flutter-io.cn"
773 source: hosted 783 source: hosted
774 version: "4.4.0" 784 version: "4.4.0"
775 permission_handler_windows: 785 permission_handler_windows:
@@ -777,7 +787,7 @@ packages: @@ -777,7 +787,7 @@ packages:
777 description: 787 description:
778 name: permission_handler_windows 788 name: permission_handler_windows
779 sha256: caeae01858a0a7d2df67a445ac98e1ad95e55a0e77c73044f4e9b1c8c2289cbd 789 sha256: caeae01858a0a7d2df67a445ac98e1ad95e55a0e77c73044f4e9b1c8c2289cbd
780 - url: "https://pub.dev" 790 + url: "https://pub.flutter-io.cn"
781 source: hosted 791 source: hosted
782 version: "0.2.2" 792 version: "0.2.2"
783 pigeon: 793 pigeon:
@@ -794,7 +804,7 @@ packages: @@ -794,7 +804,7 @@ packages:
794 description: 804 description:
795 name: platform 805 name: platform
796 sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" 806 sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
797 - url: "https://pub.dev" 807 + url: "https://pub.flutter-io.cn"
798 source: hosted 808 source: hosted
799 version: "3.1.6" 809 version: "3.1.6"
800 plugin_platform_interface: 810 plugin_platform_interface:
@@ -802,7 +812,7 @@ packages: @@ -802,7 +812,7 @@ packages:
802 description: 812 description:
803 name: plugin_platform_interface 813 name: plugin_platform_interface
804 sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" 814 sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
805 - url: "https://pub.dev" 815 + url: "https://pub.flutter-io.cn"
806 source: hosted 816 source: hosted
807 version: "2.1.8" 817 version: "2.1.8"
808 pool: 818 pool:
@@ -810,7 +820,7 @@ packages: @@ -810,7 +820,7 @@ packages:
810 description: 820 description:
811 name: pool 821 name: pool
812 sha256: "4177f68c237ea2128d1bee66ac17b2ce05ba3dbaafcbdd54c5d40a39d0b6b11c" 822 sha256: "4177f68c237ea2128d1bee66ac17b2ce05ba3dbaafcbdd54c5d40a39d0b6b11c"
813 - url: "https://pub.dev" 823 + url: "https://pub.flutter-io.cn"
814 source: hosted 824 source: hosted
815 version: "1.5.3" 825 version: "1.5.3"
816 posix: 826 posix:
@@ -818,7 +828,7 @@ packages: @@ -818,7 +828,7 @@ packages:
818 description: 828 description:
819 name: posix 829 name: posix
820 sha256: bc1bad54ad2b735816e31f8d4600cfde6c7839975085ddfbca48b6c9f7c4044e 830 sha256: bc1bad54ad2b735816e31f8d4600cfde6c7839975085ddfbca48b6c9f7c4044e
821 - url: "https://pub.dev" 831 + url: "https://pub.flutter-io.cn"
822 source: hosted 832 source: hosted
823 version: "6.5.2" 833 version: "6.5.2"
824 pretty_dio_logger: 834 pretty_dio_logger:
@@ -826,7 +836,7 @@ packages: @@ -826,7 +836,7 @@ packages:
826 description: 836 description:
827 name: pretty_dio_logger 837 name: pretty_dio_logger
828 sha256: "36f2101299786d567869493e2f5731de61ce130faa14679473b26905a92b6407" 838 sha256: "36f2101299786d567869493e2f5731de61ce130faa14679473b26905a92b6407"
829 - url: "https://pub.dev" 839 + url: "https://pub.flutter-io.cn"
830 source: hosted 840 source: hosted
831 version: "1.4.0" 841 version: "1.4.0"
832 pub_semver: 842 pub_semver:
@@ -834,7 +844,7 @@ packages: @@ -834,7 +844,7 @@ packages:
834 description: 844 description:
835 name: pub_semver 845 name: pub_semver
836 sha256: "261236774e8b1d69cfc6b9eabbc96c40f25e7a2d6b171f3385d4f65d5734fb24" 846 sha256: "261236774e8b1d69cfc6b9eabbc96c40f25e7a2d6b171f3385d4f65d5734fb24"
837 - url: "https://pub.dev" 847 + url: "https://pub.flutter-io.cn"
838 source: hosted 848 source: hosted
839 version: "2.2.1" 849 version: "2.2.1"
840 pubspec_parse: 850 pubspec_parse:
@@ -842,7 +852,7 @@ packages: @@ -842,7 +852,7 @@ packages:
842 description: 852 description:
843 name: pubspec_parse 853 name: pubspec_parse
844 sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" 854 sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082"
845 - url: "https://pub.dev" 855 + url: "https://pub.flutter-io.cn"
846 source: hosted 856 source: hosted
847 version: "1.5.0" 857 version: "1.5.0"
848 rxdart: 858 rxdart:
@@ -850,7 +860,7 @@ packages: @@ -850,7 +860,7 @@ packages:
850 description: 860 description:
851 name: rxdart 861 name: rxdart
852 sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" 862 sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962"
853 - url: "https://pub.dev" 863 + url: "https://pub.flutter-io.cn"
854 source: hosted 864 source: hosted
855 version: "0.28.0" 865 version: "0.28.0"
856 share_plus: 866 share_plus:
@@ -885,7 +895,7 @@ packages: @@ -885,7 +895,7 @@ packages:
885 description: 895 description:
886 name: shared_preferences_android 896 name: shared_preferences_android
887 sha256: "5bcf0772a761b04f8c6bf814721713de6f3e5d9d89caf8d3fe031b02a342379e" 897 sha256: "5bcf0772a761b04f8c6bf814721713de6f3e5d9d89caf8d3fe031b02a342379e"
888 - url: "https://pub.dev" 898 + url: "https://pub.flutter-io.cn"
889 source: hosted 899 source: hosted
890 version: "2.4.11" 900 version: "2.4.11"
891 shared_preferences_foundation: 901 shared_preferences_foundation:
@@ -893,7 +903,7 @@ packages: @@ -893,7 +903,7 @@ packages:
893 description: 903 description:
894 name: shared_preferences_foundation 904 name: shared_preferences_foundation
895 sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" 905 sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03"
896 - url: "https://pub.dev" 906 + url: "https://pub.flutter-io.cn"
897 source: hosted 907 source: hosted
898 version: "2.5.4" 908 version: "2.5.4"
899 shared_preferences_linux: 909 shared_preferences_linux:
@@ -901,7 +911,7 @@ packages: @@ -901,7 +911,7 @@ packages:
901 description: 911 description:
902 name: shared_preferences_linux 912 name: shared_preferences_linux
903 sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" 913 sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
904 - url: "https://pub.dev" 914 + url: "https://pub.flutter-io.cn"
905 source: hosted 915 source: hosted
906 version: "2.4.1" 916 version: "2.4.1"
907 shared_preferences_ohos: 917 shared_preferences_ohos:
@@ -918,7 +928,7 @@ packages: @@ -918,7 +928,7 @@ packages:
918 description: 928 description:
919 name: shared_preferences_platform_interface 929 name: shared_preferences_platform_interface
920 sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" 930 sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
921 - url: "https://pub.dev" 931 + url: "https://pub.flutter-io.cn"
922 source: hosted 932 source: hosted
923 version: "2.4.1" 933 version: "2.4.1"
924 shared_preferences_web: 934 shared_preferences_web:
@@ -926,7 +936,7 @@ packages: @@ -926,7 +936,7 @@ packages:
926 description: 936 description:
927 name: shared_preferences_web 937 name: shared_preferences_web
928 sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 938 sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
929 - url: "https://pub.dev" 939 + url: "https://pub.flutter-io.cn"
930 source: hosted 940 source: hosted
931 version: "2.4.3" 941 version: "2.4.3"
932 shared_preferences_windows: 942 shared_preferences_windows:
@@ -934,7 +944,7 @@ packages: @@ -934,7 +944,7 @@ packages:
934 description: 944 description:
935 name: shared_preferences_windows 945 name: shared_preferences_windows
936 sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" 946 sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
937 - url: "https://pub.dev" 947 + url: "https://pub.flutter-io.cn"
938 source: hosted 948 source: hosted
939 version: "2.4.1" 949 version: "2.4.1"
940 shelf: 950 shelf:
@@ -942,7 +952,7 @@ packages: @@ -942,7 +952,7 @@ packages:
942 description: 952 description:
943 name: shelf 953 name: shelf
944 sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 954 sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
945 - url: "https://pub.dev" 955 + url: "https://pub.flutter-io.cn"
946 source: hosted 956 source: hosted
947 version: "1.4.2" 957 version: "1.4.2"
948 shelf_web_socket: 958 shelf_web_socket:
@@ -950,7 +960,7 @@ packages: @@ -950,7 +960,7 @@ packages:
950 description: 960 description:
951 name: shelf_web_socket 961 name: shelf_web_socket
952 sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" 962 sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925"
953 - url: "https://pub.dev" 963 + url: "https://pub.flutter-io.cn"
954 source: hosted 964 source: hosted
955 version: "3.0.0" 965 version: "3.0.0"
956 simple_gesture_detector: 966 simple_gesture_detector:
@@ -958,7 +968,7 @@ packages: @@ -958,7 +968,7 @@ packages:
958 description: 968 description:
959 name: simple_gesture_detector 969 name: simple_gesture_detector
960 sha256: ba2cd5af24ff20a0b8d609cec3f40e5b0744d2a71804a2616ae086b9c19d19a3 970 sha256: ba2cd5af24ff20a0b8d609cec3f40e5b0744d2a71804a2616ae086b9c19d19a3
961 - url: "https://pub.dev" 971 + url: "https://pub.flutter-io.cn"
962 source: hosted 972 source: hosted
963 version: "0.2.1" 973 version: "0.2.1"
964 sky_engine: 974 sky_engine:
@@ -971,7 +981,7 @@ packages: @@ -971,7 +981,7 @@ packages:
971 description: 981 description:
972 name: source_span 982 name: source_span
973 sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" 983 sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
974 - url: "https://pub.dev" 984 + url: "https://pub.flutter-io.cn"
975 source: hosted 985 source: hosted
976 version: "1.10.0" 986 version: "1.10.0"
977 sqflite: 987 sqflite:
@@ -988,7 +998,7 @@ packages: @@ -988,7 +998,7 @@ packages:
988 description: 998 description:
989 name: sqflite_android 999 name: sqflite_android
990 sha256: "78f489aab276260cdd26676d2169446c7ecd3484bbd5fead4ca14f3ed4dd9ee3" 1000 sha256: "78f489aab276260cdd26676d2169446c7ecd3484bbd5fead4ca14f3ed4dd9ee3"
991 - url: "https://pub.dev" 1001 + url: "https://pub.flutter-io.cn"
992 source: hosted 1002 source: hosted
993 version: "2.4.0" 1003 version: "2.4.0"
994 sqflite_common: 1004 sqflite_common:
@@ -996,7 +1006,7 @@ packages: @@ -996,7 +1006,7 @@ packages:
996 description: 1006 description:
997 name: sqflite_common 1007 name: sqflite_common
998 sha256: "761b9740ecbd4d3e66b8916d784e581861fd3c3553eda85e167bc49fdb68f709" 1008 sha256: "761b9740ecbd4d3e66b8916d784e581861fd3c3553eda85e167bc49fdb68f709"
999 - url: "https://pub.dev" 1009 + url: "https://pub.flutter-io.cn"
1000 source: hosted 1010 source: hosted
1001 version: "2.5.4+6" 1011 version: "2.5.4+6"
1002 sqflite_darwin: 1012 sqflite_darwin:
@@ -1004,7 +1014,7 @@ packages: @@ -1004,7 +1014,7 @@ packages:
1004 description: 1014 description:
1005 name: sqflite_darwin 1015 name: sqflite_darwin
1006 sha256: "22adfd9a2c7d634041e96d6241e6e1c8138ca6817018afc5d443fef91dcefa9c" 1016 sha256: "22adfd9a2c7d634041e96d6241e6e1c8138ca6817018afc5d443fef91dcefa9c"
1007 - url: "https://pub.dev" 1017 + url: "https://pub.flutter-io.cn"
1008 source: hosted 1018 source: hosted
1009 version: "2.4.1+1" 1019 version: "2.4.1+1"
1010 sqflite_ohos: 1020 sqflite_ohos:
@@ -1021,7 +1031,7 @@ packages: @@ -1021,7 +1031,7 @@ packages:
1021 description: 1031 description:
1022 name: sqflite_platform_interface 1032 name: sqflite_platform_interface
1023 sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" 1033 sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920"
1024 - url: "https://pub.dev" 1034 + url: "https://pub.flutter-io.cn"
1025 source: hosted 1035 source: hosted
1026 version: "2.4.0" 1036 version: "2.4.0"
1027 stack_trace: 1037 stack_trace:
@@ -1029,7 +1039,7 @@ packages: @@ -1029,7 +1039,7 @@ packages:
1029 description: 1039 description:
1030 name: stack_trace 1040 name: stack_trace
1031 sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" 1041 sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377"
1032 - url: "https://pub.dev" 1042 + url: "https://pub.flutter-io.cn"
1033 source: hosted 1043 source: hosted
1034 version: "1.12.0" 1044 version: "1.12.0"
1035 stream_channel: 1045 stream_channel:
@@ -1037,7 +1047,7 @@ packages: @@ -1037,7 +1047,7 @@ packages:
1037 description: 1047 description:
1038 name: stream_channel 1048 name: stream_channel
1039 sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 1049 sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
1040 - url: "https://pub.dev" 1050 + url: "https://pub.flutter-io.cn"
1041 source: hosted 1051 source: hosted
1042 version: "2.1.2" 1052 version: "2.1.2"
1043 stream_transform: 1053 stream_transform:
@@ -1045,7 +1055,7 @@ packages: @@ -1045,7 +1055,7 @@ packages:
1045 description: 1055 description:
1046 name: stream_transform 1056 name: stream_transform
1047 sha256: a00e5f18bffc764f923e7dec1038527f7fe7a1791361a7117f0358193f13d53a 1057 sha256: a00e5f18bffc764f923e7dec1038527f7fe7a1791361a7117f0358193f13d53a
1048 - url: "https://pub.dev" 1058 + url: "https://pub.flutter-io.cn"
1049 source: hosted 1059 source: hosted
1050 version: "2.1.2" 1060 version: "2.1.2"
1051 string_scanner: 1061 string_scanner:
@@ -1053,7 +1063,7 @@ packages: @@ -1053,7 +1063,7 @@ packages:
1053 description: 1063 description:
1054 name: string_scanner 1064 name: string_scanner
1055 sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" 1065 sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3"
1056 - url: "https://pub.dev" 1066 + url: "https://pub.flutter-io.cn"
1057 source: hosted 1067 source: hosted
1058 version: "1.3.0" 1068 version: "1.3.0"
1059 synchronized: 1069 synchronized:
@@ -1061,7 +1071,7 @@ packages: @@ -1061,7 +1071,7 @@ packages:
1061 description: 1071 description:
1062 name: synchronized 1072 name: synchronized
1063 sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225" 1073 sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225"
1064 - url: "https://pub.dev" 1074 + url: "https://pub.flutter-io.cn"
1065 source: hosted 1075 source: hosted
1066 version: "3.3.0+3" 1076 version: "3.3.0+3"
1067 table_calendar: 1077 table_calendar:
@@ -1069,7 +1079,7 @@ packages: @@ -1069,7 +1079,7 @@ packages:
1069 description: 1079 description:
1070 name: table_calendar 1080 name: table_calendar
1071 sha256: b2896b7c86adf3a4d9c911d860120fe3dbe03c85db43b22fd61f14ee78cdbb63 1081 sha256: b2896b7c86adf3a4d9c911d860120fe3dbe03c85db43b22fd61f14ee78cdbb63
1072 - url: "https://pub.dev" 1082 + url: "https://pub.flutter-io.cn"
1073 source: hosted 1083 source: hosted
1074 version: "3.1.3" 1084 version: "3.1.3"
1075 term_glyph: 1085 term_glyph:
@@ -1077,7 +1087,7 @@ packages: @@ -1077,7 +1087,7 @@ packages:
1077 description: 1087 description:
1078 name: term_glyph 1088 name: term_glyph
1079 sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 1089 sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
1080 - url: "https://pub.dev" 1090 + url: "https://pub.flutter-io.cn"
1081 source: hosted 1091 source: hosted
1082 version: "1.2.1" 1092 version: "1.2.1"
1083 test_api: 1093 test_api:
@@ -1085,7 +1095,7 @@ packages: @@ -1085,7 +1095,7 @@ packages:
1085 description: 1095 description:
1086 name: test_api 1096 name: test_api
1087 sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" 1097 sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c"
1088 - url: "https://pub.dev" 1098 + url: "https://pub.flutter-io.cn"
1089 source: hosted 1099 source: hosted
1090 version: "0.7.3" 1100 version: "0.7.3"
1091 thinking_analytics: 1101 thinking_analytics:
@@ -1093,7 +1103,7 @@ packages: @@ -1093,7 +1103,7 @@ packages:
1093 description: 1103 description:
1094 name: thinking_analytics 1104 name: thinking_analytics
1095 sha256: b01cac0b5482e71c1d75c44c77d27f427662cc65a77b7bc3c8b49617d7a01e02 1105 sha256: b01cac0b5482e71c1d75c44c77d27f427662cc65a77b7bc3c8b49617d7a01e02
1096 - url: "https://pub.dev" 1106 + url: "https://pub.flutter-io.cn"
1097 source: hosted 1107 source: hosted
1098 version: "3.3.3" 1108 version: "3.3.3"
1099 timing: 1109 timing:
@@ -1101,7 +1111,7 @@ packages: @@ -1101,7 +1111,7 @@ packages:
1101 description: 1111 description:
1102 name: timing 1112 name: timing
1103 sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" 1113 sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe"
1104 - url: "https://pub.dev" 1114 + url: "https://pub.flutter-io.cn"
1105 source: hosted 1115 source: hosted
1106 version: "1.0.2" 1116 version: "1.0.2"
1107 typed_data: 1117 typed_data:
@@ -1109,7 +1119,7 @@ packages: @@ -1109,7 +1119,7 @@ packages:
1109 description: 1119 description:
1110 name: typed_data 1120 name: typed_data
1111 sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 1121 sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
1112 - url: "https://pub.dev" 1122 + url: "https://pub.flutter-io.cn"
1113 source: hosted 1123 source: hosted
1114 version: "1.4.0" 1124 version: "1.4.0"
1115 url_launcher_linux: 1125 url_launcher_linux:
@@ -1117,7 +1127,7 @@ packages: @@ -1117,7 +1127,7 @@ packages:
1117 description: 1127 description:
1118 name: url_launcher_linux 1128 name: url_launcher_linux
1119 sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" 1129 sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935"
1120 - url: "https://pub.dev" 1130 + url: "https://pub.flutter-io.cn"
1121 source: hosted 1131 source: hosted
1122 version: "3.2.1" 1132 version: "3.2.1"
1123 url_launcher_platform_interface: 1133 url_launcher_platform_interface:
@@ -1125,7 +1135,7 @@ packages: @@ -1125,7 +1135,7 @@ packages:
1125 description: 1135 description:
1126 name: url_launcher_platform_interface 1136 name: url_launcher_platform_interface
1127 sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" 1137 sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
1128 - url: "https://pub.dev" 1138 + url: "https://pub.flutter-io.cn"
1129 source: hosted 1139 source: hosted
1130 version: "2.3.2" 1140 version: "2.3.2"
1131 url_launcher_web: 1141 url_launcher_web:
@@ -1133,7 +1143,7 @@ packages: @@ -1133,7 +1143,7 @@ packages:
1133 description: 1143 description:
1134 name: url_launcher_web 1144 name: url_launcher_web
1135 sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" 1145 sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2"
1136 - url: "https://pub.dev" 1146 + url: "https://pub.flutter-io.cn"
1137 source: hosted 1147 source: hosted
1138 version: "2.4.1" 1148 version: "2.4.1"
1139 url_launcher_windows: 1149 url_launcher_windows:
@@ -1141,7 +1151,7 @@ packages: @@ -1141,7 +1151,7 @@ packages:
1141 description: 1151 description:
1142 name: url_launcher_windows 1152 name: url_launcher_windows
1143 sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" 1153 sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77"
1144 - url: "https://pub.dev" 1154 + url: "https://pub.flutter-io.cn"
1145 source: hosted 1155 source: hosted
1146 version: "3.1.4" 1156 version: "3.1.4"
1147 uuid: 1157 uuid:
@@ -1149,7 +1159,7 @@ packages: @@ -1149,7 +1159,7 @@ packages:
1149 description: 1159 description:
1150 name: uuid 1160 name: uuid
1151 sha256: "9b129329f58692f6e6578329498a8fe9fbe98f090beb764ffbb8ee2eadd01dcd" 1161 sha256: "9b129329f58692f6e6578329498a8fe9fbe98f090beb764ffbb8ee2eadd01dcd"
1152 - url: "https://pub.dev" 1162 + url: "https://pub.flutter-io.cn"
1153 source: hosted 1163 source: hosted
1154 version: "4.6.0" 1164 version: "4.6.0"
1155 vector_math: 1165 vector_math:
@@ -1157,7 +1167,7 @@ packages: @@ -1157,7 +1167,7 @@ packages:
1157 description: 1167 description:
1158 name: vector_math 1168 name: vector_math
1159 sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" 1169 sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
1160 - url: "https://pub.dev" 1170 + url: "https://pub.flutter-io.cn"
1161 source: hosted 1171 source: hosted
1162 version: "2.1.4" 1172 version: "2.1.4"
1163 video_thumbnail: 1173 video_thumbnail:
@@ -1165,7 +1175,7 @@ packages: @@ -1165,7 +1175,7 @@ packages:
1165 description: 1175 description:
1166 name: video_thumbnail 1176 name: video_thumbnail
1167 sha256: "181a0c205b353918954a881f53a3441476b9e301641688a581e0c13f00dc588b" 1177 sha256: "181a0c205b353918954a881f53a3441476b9e301641688a581e0c13f00dc588b"
1168 - url: "https://pub.dev" 1178 + url: "https://pub.flutter-io.cn"
1169 source: hosted 1179 source: hosted
1170 version: "0.5.6" 1180 version: "0.5.6"
1171 vm_service: 1181 vm_service:
@@ -1173,7 +1183,7 @@ packages: @@ -1173,7 +1183,7 @@ packages:
1173 description: 1183 description:
1174 name: vm_service 1184 name: vm_service
1175 sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b 1185 sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b
1176 - url: "https://pub.dev" 1186 + url: "https://pub.flutter-io.cn"
1177 source: hosted 1187 source: hosted
1178 version: "14.3.0" 1188 version: "14.3.0"
1179 watcher: 1189 watcher:
@@ -1181,7 +1191,7 @@ packages: @@ -1181,7 +1191,7 @@ packages:
1181 description: 1191 description:
1182 name: watcher 1192 name: watcher
1183 sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" 1193 sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635"
1184 - url: "https://pub.dev" 1194 + url: "https://pub.flutter-io.cn"
1185 source: hosted 1195 source: hosted
1186 version: "1.2.1" 1196 version: "1.2.1"
1187 web: 1197 web:
@@ -1189,7 +1199,7 @@ packages: @@ -1189,7 +1199,7 @@ packages:
1189 description: 1199 description:
1190 name: web 1200 name: web
1191 sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" 1201 sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
1192 - url: "https://pub.dev" 1202 + url: "https://pub.flutter-io.cn"
1193 source: hosted 1203 source: hosted
1194 version: "1.1.1" 1204 version: "1.1.1"
1195 web_socket: 1205 web_socket:
@@ -1197,7 +1207,7 @@ packages: @@ -1197,7 +1207,7 @@ packages:
1197 description: 1207 description:
1198 name: web_socket 1208 name: web_socket
1199 sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" 1209 sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c"
1200 - url: "https://pub.dev" 1210 + url: "https://pub.flutter-io.cn"
1201 source: hosted 1211 source: hosted
1202 version: "1.0.1" 1212 version: "1.0.1"
1203 web_socket_channel: 1213 web_socket_channel:
@@ -1205,7 +1215,7 @@ packages: @@ -1205,7 +1215,7 @@ packages:
1205 description: 1215 description:
1206 name: web_socket_channel 1216 name: web_socket_channel
1207 sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 1217 sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
1208 - url: "https://pub.dev" 1218 + url: "https://pub.flutter-io.cn"
1209 source: hosted 1219 source: hosted
1210 version: "3.0.3" 1220 version: "3.0.3"
1211 webview_flutter: 1221 webview_flutter:
@@ -1258,7 +1268,7 @@ packages: @@ -1258,7 +1268,7 @@ packages:
1258 description: 1268 description:
1259 name: win32 1269 name: win32
1260 sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e 1270 sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e
1261 - url: "https://pub.dev" 1271 + url: "https://pub.flutter-io.cn"
1262 source: hosted 1272 source: hosted
1263 version: "5.10.1" 1273 version: "5.10.1"
1264 xdg_directories: 1274 xdg_directories:
@@ -1266,7 +1276,7 @@ packages: @@ -1266,7 +1276,7 @@ packages:
1266 description: 1276 description:
1267 name: xdg_directories 1277 name: xdg_directories
1268 sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" 1278 sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
1269 - url: "https://pub.dev" 1279 + url: "https://pub.flutter-io.cn"
1270 source: hosted 1280 source: hosted
1271 version: "1.1.0" 1281 version: "1.1.0"
1272 yaml: 1282 yaml:
@@ -1274,7 +1284,7 @@ packages: @@ -1274,7 +1284,7 @@ packages:
1274 description: 1284 description:
1275 name: yaml 1285 name: yaml
1276 sha256: f67cdd8e07d3c6329146aaef1ba043542b3134c12489f553ca9a7435d1068aea 1286 sha256: f67cdd8e07d3c6329146aaef1ba043542b3134c12489f553ca9a7435d1068aea
1277 - url: "https://pub.dev" 1287 + url: "https://pub.flutter-io.cn"
1278 source: hosted 1288 source: hosted
1279 version: "3.1.4" 1289 version: "3.1.4"
1280 sdks: 1290 sdks:
@@ -64,9 +64,9 @@ dependencies: @@ -64,9 +64,9 @@ dependencies:
64 # path_provider: 2.1.5 64 # path_provider: 2.1.5
65 path_provider: 65 path_provider:
66 git: 66 git:
67 - url: https://gitcode.com/openharmony-tpc/flutter_packages.git 67 + url: https://gitcode.com/openharmony-sig/flutter_packages.git
68 path: packages/path_provider/path_provider 68 path: packages/path_provider/path_provider
69 - ref: br_path_provider-v2.1.5_ohos 69 + ref: master
70 thinking_analytics: ^3.3.2 70 thinking_analytics: ^3.3.2
71 flutter_localizations: 71 flutter_localizations:
72 sdk: flutter 72 sdk: flutter
@@ -81,11 +81,14 @@ dependencies: @@ -81,11 +81,14 @@ dependencies:
81 flutter_timezone: ^5.1.0 81 flutter_timezone: ^5.1.0
82 82
83 83
84 -# share_plus (git) 间接依赖 path_provider 的 git 版本,  
85 -# 与 cached_network_image → flutter_cache_manager 依赖的 hosted 版本冲突。  
86 -# 强制使用 hosted 版本统一来源。 84 +# share_plus (git) 间接依赖 openharmony-sig/flutter_packages.git@master 的
  85 +# path_provider,direct dependency 也保持同源,避免 pub source 冲突。
87 dependency_overrides: 86 dependency_overrides:
88 - path_provider: 2.1.5 87 + path_provider:
  88 + git:
  89 + url: https://gitcode.com/openharmony-sig/flutter_packages.git
  90 + path: packages/path_provider/path_provider
  91 + ref: master
89 # image_cropper_platform_interface: 7.1.0 92 # image_cropper_platform_interface: 7.1.0
90 # Override meta to satisfy flutter_timezone 5.1.0 (requires ^1.16.0) 93 # Override meta to satisfy flutter_timezone 5.1.0 (requires ^1.16.0)
91 # while the ohos flutter_test bundle pins 1.15.0. 94 # while the ohos flutter_test bundle pins 1.15.0.
  1 +import 'dart:async';
  2 +
1 import 'package:doublefeel_flutter/core/result/app_result.dart'; 3 import 'package:doublefeel_flutter/core/result/app_result.dart';
2 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';
3 import 'package:doublefeel_flutter/core/services/raw_data_service/platform_ohos/ohos_health_raw_data_sync_service.dart'; 5 import 'package:doublefeel_flutter/core/services/raw_data_service/platform_ohos/ohos_health_raw_data_sync_service.dart';
4 import 'package:doublefeel_flutter/data/models/harmony/hm_health_data.dart'; 6 import 'package:doublefeel_flutter/data/models/harmony/hm_health_data.dart';
5 import 'package:doublefeel_flutter/data/models/harmony/hm_sleep_data.dart'; 7 import 'package:doublefeel_flutter/data/models/harmony/hm_sleep_data.dart';
  8 +import 'package:doublefeel_flutter/data/models/harmony/hm_workout_data.dart';
6 import 'package:flutter_test/flutter_test.dart'; 9 import 'package:flutter_test/flutter_test.dart';
7 10
8 void main() { 11 void main() {
@@ -38,13 +41,14 @@ void main() { @@ -38,13 +41,14 @@ void main() {
38 41
39 final result = await service.syncRawData(dataType: 2, endTime: 2000); 42 final result = await service.syncRawData(dataType: 2, endTime: 2000);
40 43
41 - expect(result.startTime, 1000); 44 + final expectedStart = _unixSeconds(DateTime(1970));
  45 + expect(result.startTime, expectedStart);
42 expect(result.endTime, 2000); 46 expect(result.endTime, 2000);
43 expect(result.segmentCount, 1); 47 expect(result.segmentCount, 1);
44 expect(result.pageCount, 2); 48 expect(result.pageCount, 2);
45 expect(result.storedCount, 2); 49 expect(result.storedCount, 2);
46 expect(remote.calls.map((e) => e.pageToken), [null, 'next']); 50 expect(remote.calls.map((e) => e.pageToken), [null, 'next']);
47 - expect(remote.calls.every((e) => e.startTime == 1000), isTrue); 51 + expect(remote.calls.every((e) => e.startTime == expectedStart), isTrue);
48 expect(local.storedBatches.map((e) => e.length), [1, 1]); 52 expect(local.storedBatches.map((e) => e.length), [1, 1]);
49 }); 53 });
50 54
@@ -63,14 +67,14 @@ void main() { @@ -63,14 +67,14 @@ void main() {
63 67
64 final result = await service.syncRawData(dataType: 7); 68 final result = await service.syncRawData(dataType: 7);
65 69
66 - final expectedStart = now  
67 - .subtract(  
68 - const Duration(  
69 - days: OhosHealthRawDataSyncService.defaultLookbackDays,  
70 - ),  
71 - )  
72 - .millisecondsSinceEpoch ~/  
73 - 1000; 70 + final fallback = now.subtract(
  71 + const Duration(
  72 + days: OhosHealthRawDataSyncService.defaultLookbackDays,
  73 + ),
  74 + );
  75 + final expectedStart =
  76 + _unixSeconds(DateTime(fallback.year, fallback.month, fallback.day)) -
  77 + Duration.secondsPerDay;
74 final expectedEnd = now.millisecondsSinceEpoch ~/ 1000; 78 final expectedEnd = now.millisecondsSinceEpoch ~/ 1000;
75 79
76 expect(result.startTime, expectedStart); 80 expect(result.startTime, expectedStart);
@@ -81,7 +85,7 @@ void main() { @@ -81,7 +85,7 @@ void main() {
81 expect(local.storedBatches, isEmpty); 85 expect(local.storedBatches, isEmpty);
82 }); 86 });
83 87
84 - test('syncRawData splits cross-month ranges and writes marked logs', 88 + test('syncRawData splits non-heart-rate ranges every 30 days and logs marker',
85 () async { 89 () async {
86 final start = _unixSeconds(DateTime(2026, 1, 30, 10)); 90 final start = _unixSeconds(DateTime(2026, 1, 30, 10));
87 final end = _unixSeconds(DateTime(2026, 3, 2, 9)); 91 final end = _unixSeconds(DateTime(2026, 3, 2, 9));
@@ -104,12 +108,74 @@ void main() { @@ -104,12 +108,74 @@ void main() {
104 ), 108 ),
105 ], 109 ],
106 ), 110 ),
  111 + ]);
  112 + final local = _FakeOhosHealthRawDataLocalStore();
  113 + final logs = <String>[];
  114 + final service = OhosHealthRawDataSyncService(
  115 + remoteDataSource: remote,
  116 + localStore: local,
  117 + logSink: logs.add,
  118 + );
  119 +
  120 + final result = await service.syncRawData(
  121 + dataType: 1,
  122 + startTime: start,
  123 + endTime: end,
  124 + );
  125 +
  126 + expect(result.segmentCount, 2);
  127 + expect(result.pageCount, 2);
  128 + expect(result.storedCount, 2);
  129 + expect(remote.calls.map((e) => _dateKey(e.startTime)), [
  130 + 20260130,
  131 + 20260301,
  132 + ]);
  133 + expect(remote.calls.map((e) => _dateKey(e.endTime)), [
  134 + 20260228,
  135 + 20260302,
  136 + ]);
  137 + expect(
  138 + logs,
  139 + contains(
  140 + contains(OhosHealthRawDataSyncService.logMarker),
  141 + ),
  142 + );
  143 + expect(
  144 + logs,
  145 + contains(contains('segment_start dataType=1 segment=1/2 chunkDays=30')),
  146 + );
  147 + expect(logs, contains(contains('sync_finish dataType=1')));
  148 + expect(logs, contains(contains('chunkDays=30 rangeCount=2')));
  149 + });
  150 +
  151 + test('syncRawData splits heart-rate ranges every 10 days', () async {
  152 + final start = _unixSeconds(DateTime(2026, 1, 30, 10));
  153 + final end = _unixSeconds(DateTime(2026, 2, 22, 9));
  154 + final remote = _FakeOhosHealthRawDataRemoteDataSource([
  155 + const OhosHealthRawDataPage(
  156 + items: [
  157 + OhosHealthRawDataItem(
  158 + dataType: 2,
  159 + dataTime: 1,
  160 + payload: {'value': 70},
  161 + ),
  162 + ],
  163 + ),
107 const OhosHealthRawDataPage( 164 const OhosHealthRawDataPage(
108 items: [ 165 items: [
109 OhosHealthRawDataItem( 166 OhosHealthRawDataItem(
110 - dataType: 1, 167 + dataType: 2,
  168 + dataTime: 2,
  169 + payload: {'value': 71},
  170 + ),
  171 + ],
  172 + ),
  173 + const OhosHealthRawDataPage(
  174 + items: [
  175 + OhosHealthRawDataItem(
  176 + dataType: 2,
111 dataTime: 3, 177 dataTime: 3,
112 - payload: {'value': 47}, 178 + payload: {'value': 72},
113 ), 179 ),
114 ], 180 ],
115 ), 181 ),
@@ -123,32 +189,133 @@ void main() { @@ -123,32 +189,133 @@ void main() {
123 ); 189 );
124 190
125 final result = await service.syncRawData( 191 final result = await service.syncRawData(
126 - dataType: 1, 192 + dataType: HuaweiHealthDataType.heartRate.dataType,
127 startTime: start, 193 startTime: start,
128 endTime: end, 194 endTime: end,
129 ); 195 );
130 196
131 expect(result.segmentCount, 3); 197 expect(result.segmentCount, 3);
132 expect(result.pageCount, 3); 198 expect(result.pageCount, 3);
133 - expect(result.storedCount, 3);  
134 expect(remote.calls.map((e) => _dateKey(e.startTime)), [ 199 expect(remote.calls.map((e) => _dateKey(e.startTime)), [
135 20260130, 200 20260130,
136 - 20260201,  
137 - 20260301, 201 + 20260209,
  202 + 20260219,
138 ]); 203 ]);
139 expect(remote.calls.map((e) => _dateKey(e.endTime)), [ 204 expect(remote.calls.map((e) => _dateKey(e.endTime)), [
140 - 20260131,  
141 - 20260228,  
142 - 20260302, 205 + 20260208,
  206 + 20260218,
  207 + 20260222,
143 ]); 208 ]);
144 expect( 209 expect(
145 logs, 210 logs,
146 - contains(  
147 - contains(OhosHealthRawDataSyncService.logMarker),  
148 - ), 211 + contains(contains('segment_start dataType=2 segment=1/3 chunkDays=10')),
149 ); 212 );
150 - expect(logs, contains(contains('segment_start dataType=1 segment=1/3')));  
151 - expect(logs, contains(contains('sync_finish dataType=1'))); 213 + });
  214 +
  215 + test('syncRawData joins duplicate in-flight syncs', () async {
  216 + final gate = Completer<void>();
  217 + final remote = _FakeOhosHealthRawDataRemoteDataSource(
  218 + [
  219 + const OhosHealthRawDataPage(
  220 + items: [
  221 + OhosHealthRawDataItem(
  222 + dataType: 1,
  223 + dataTime: 1001,
  224 + payload: {'value': 45},
  225 + ),
  226 + ],
  227 + ),
  228 + ],
  229 + gate: gate,
  230 + );
  231 + final local = _FakeOhosHealthRawDataLocalStore();
  232 + final logs = <String>[];
  233 + final service = OhosHealthRawDataSyncService(
  234 + remoteDataSource: remote,
  235 + localStore: local,
  236 + logSink: logs.add,
  237 + );
  238 +
  239 + final first = service.syncRawData(
  240 + dataType: 1,
  241 + startTime: 1000,
  242 + endTime: 2000,
  243 + );
  244 + final second = service.syncRawData(
  245 + dataType: 1,
  246 + startTime: 1000,
  247 + endTime: 2000,
  248 + );
  249 + await Future<void>.delayed(Duration.zero);
  250 +
  251 + expect(remote.calls, hasLength(1));
  252 + expect(logs, contains(contains('sync_duplicate_join dataType=1')));
  253 +
  254 + gate.complete();
  255 + final results = await Future.wait([first, second]);
  256 +
  257 + expect(results.first.storedCount, 1);
  258 + expect(results.last.storedCount, 1);
  259 + expect(remote.calls, hasLength(1));
  260 + expect(local.storedBatches, hasLength(1));
  261 + });
  262 +
  263 + test('syncRawData fails immediately when a fetch page fails', () async {
  264 + final remote = _FakeOhosHealthRawDataRemoteDataSource(
  265 + const <OhosHealthRawDataPage>[],
  266 + failingDataTypes: {1},
  267 + );
  268 + final local = _FakeOhosHealthRawDataLocalStore();
  269 + final logs = <String>[];
  270 + final service = OhosHealthRawDataSyncService(
  271 + remoteDataSource: remote,
  272 + localStore: local,
  273 + logSink: logs.add,
  274 + );
  275 +
  276 + await expectLater(
  277 + service.syncRawData(dataType: 1, startTime: 1000, endTime: 2000),
  278 + throwsA(isA<StateError>()),
  279 + );
  280 +
  281 + expect(local.storedBatches, isEmpty);
  282 + expect(logs, contains(contains('sync_failed dataType=1')));
  283 + });
  284 +
  285 + test('syncCalculationRawData syncs every calculation dependency type',
  286 + () async {
  287 + final remote = _FakeOhosHealthRawDataRemoteDataSource([]);
  288 + final local = _FakeOhosHealthRawDataLocalStore();
  289 + final logs = <String>[];
  290 + final service = OhosHealthRawDataSyncService(
  291 + remoteDataSource: remote,
  292 + localStore: local,
  293 + logSink: logs.add,
  294 + );
  295 +
  296 + final results = await service.syncCalculationRawData(
  297 + startTime: 1000,
  298 + endTime: 2000,
  299 + );
  300 +
  301 + expect(
  302 + results.map((result) => result.dataType),
  303 + [
  304 + for (final type in HuaweiHealthDataType.values) type.dataType,
  305 + OhosHealthRawDataType.sleepAnalysis,
  306 + OhosHealthRawDataType.workout,
  307 + ],
  308 + );
  309 + expect(
  310 + remote.calls.map((call) => call.dataType).toSet(),
  311 + {
  312 + for (final type in HuaweiHealthDataType.values) type.dataType,
  313 + OhosHealthRawDataType.sleepAnalysis,
  314 + OhosHealthRawDataType.workout,
  315 + },
  316 + );
  317 + expect(logs, contains(contains('calculation_sync_start')));
  318 + expect(logs, contains(contains('calculation_sync_finish')));
152 }); 319 });
153 320
154 test('Harmony remote data source calls getHealthData and converts items', 321 test('Harmony remote data source calls getHealthData and converts items',
@@ -169,7 +336,10 @@ void main() { @@ -169,7 +336,10 @@ void main() {
169 ], 336 ],
170 ), 337 ),
171 ); 338 );
172 - final remote = OhosHarmonyHealthRawDataRemoteDataSource(client); 339 + final remote = OhosHarmonyHealthRawDataRemoteDataSource(
  340 + client,
  341 + nowProvider: () => DateTime(2026, 9, 1),
  342 + );
173 343
174 final page = await remote.fetchRawDataPage( 344 final page = await remote.fetchRawDataPage(
175 dataType: HuaweiHealthDataType.hrv.dataType, 345 dataType: HuaweiHealthDataType.hrv.dataType,
@@ -187,6 +357,24 @@ void main() { @@ -187,6 +357,24 @@ void main() {
187 expect(page.items.single.payload['value'], 53); 357 expect(page.items.single.payload['value'], 53);
188 }); 358 });
189 359
  360 + test('Harmony remote data source uses tomorrow as endDate for today data',
  361 + () async {
  362 + final client = _FakeOhosHarmonyRawDataClient();
  363 + final remote = OhosHarmonyHealthRawDataRemoteDataSource(
  364 + client,
  365 + nowProvider: () => DateTime(2026, 8, 31, 10),
  366 + );
  367 +
  368 + await remote.fetchRawDataPage(
  369 + dataType: HuaweiHealthDataType.hrv.dataType,
  370 + startTime: _unixSeconds(DateTime(2026, 8, 30)),
  371 + endTime: _unixSeconds(DateTime(2026, 8, 31, 12)),
  372 + );
  373 +
  374 + expect(client.healthCalls.single.startDate, 20260830);
  375 + expect(client.healthCalls.single.endDate, 20260901);
  376 + });
  377 +
190 test('Harmony remote data source calls getSleepData and converts items', 378 test('Harmony remote data source calls getSleepData and converts items',
191 () async { 379 () async {
192 final client = _FakeOhosHarmonyRawDataClient( 380 final client = _FakeOhosHarmonyRawDataClient(
@@ -218,13 +406,52 @@ void main() { @@ -218,13 +406,52 @@ void main() {
218 expect(page.items.single.dataTime, 1787870000); 406 expect(page.items.single.dataTime, 1787870000);
219 expect(page.items.single.payload['from_time'], 1787850000); 407 expect(page.items.single.payload['from_time'], 1787850000);
220 }); 408 });
  409 +
  410 + test('Harmony remote data source calls dedicated workout API', () async {
  411 + final client = _FakeOhosHarmonyRawDataClient(
  412 + workoutData: HmWorkoutData(
  413 + list: [
  414 + HmWorkoutDataItem(
  415 + startTime: 1787850000,
  416 + endTime: 1787870000,
  417 + activityType: 90,
  418 + ),
  419 + ],
  420 + ),
  421 + );
  422 + final remote = OhosHarmonyHealthRawDataRemoteDataSource(client);
  423 + final startTime = _unixSeconds(DateTime(2026, 8));
  424 + final endTime = _unixSeconds(DateTime(2026, 8, 2, 23, 59, 59));
  425 +
  426 + final page = await remote.fetchRawDataPage(
  427 + dataType: OhosHealthRawDataType.workout,
  428 + startTime: startTime,
  429 + endTime: endTime,
  430 + );
  431 +
  432 + expect(client.healthCalls, isEmpty);
  433 + expect(client.workoutCalls.single.startDate, startTime);
  434 + expect(client.workoutCalls.single.endDate, endTime);
  435 + expect(page.items.single.dataType, OhosHealthRawDataType.workout);
  436 + expect(page.items.single.dataTime, 1787870000);
  437 + expect(page.items.single.payload['start_time'], 1787850000);
  438 + expect(page.items.single.payload['end_time'], 1787870000);
  439 + expect(page.items.single.payload['activity_type'], 90);
  440 + });
221 } 441 }
222 442
223 class _FakeOhosHealthRawDataRemoteDataSource 443 class _FakeOhosHealthRawDataRemoteDataSource
224 implements OhosHealthRawDataRemoteDataSource { 444 implements OhosHealthRawDataRemoteDataSource {
225 - _FakeOhosHealthRawDataRemoteDataSource(this._pages); 445 + _FakeOhosHealthRawDataRemoteDataSource(
  446 + this._pages, {
  447 + Completer<void>? gate,
  448 + Set<int> failingDataTypes = const <int>{},
  449 + }) : _gate = gate,
  450 + _failingDataTypes = failingDataTypes;
226 451
227 final List<OhosHealthRawDataPage> _pages; 452 final List<OhosHealthRawDataPage> _pages;
  453 + final Completer<void>? _gate;
  454 + final Set<int> _failingDataTypes;
228 final List<_FetchCall> calls = <_FetchCall>[]; 455 final List<_FetchCall> calls = <_FetchCall>[];
229 456
230 @override 457 @override
@@ -242,6 +469,12 @@ class _FakeOhosHealthRawDataRemoteDataSource @@ -242,6 +469,12 @@ class _FakeOhosHealthRawDataRemoteDataSource
242 pageToken: pageToken, 469 pageToken: pageToken,
243 ), 470 ),
244 ); 471 );
  472 + if (_gate != null) {
  473 + await _gate.future;
  474 + }
  475 + if (_failingDataTypes.contains(dataType)) {
  476 + throw StateError('failed dataType=$dataType');
  477 + }
245 final pageIndex = calls.length - 1; 478 final pageIndex = calls.length - 1;
246 if (pageIndex >= _pages.length) { 479 if (pageIndex >= _pages.length) {
247 return const OhosHealthRawDataPage(items: <OhosHealthRawDataItem>[]); 480 return const OhosHealthRawDataPage(items: <OhosHealthRawDataItem>[]);
@@ -264,11 +497,29 @@ class _FakeOhosHealthRawDataLocalStore implements OhosHealthRawDataLocalStore { @@ -264,11 +497,29 @@ class _FakeOhosHealthRawDataLocalStore implements OhosHealthRawDataLocalStore {
264 } 497 }
265 498
266 @override 499 @override
267 - Future<void> upsertRawDataBatch({ 500 + Future<int> upsertRawDataBatch({
268 required int dataType, 501 required int dataType,
269 required List<OhosHealthRawDataItem> items, 502 required List<OhosHealthRawDataItem> items,
270 }) async { 503 }) async {
271 storedBatches.add(items); 504 storedBatches.add(items);
  505 + return items.length;
  506 + }
  507 +
  508 + @override
  509 + Future<List<OhosHealthRawDataItem>> queryRawData({
  510 + required int dataType,
  511 + required int startTime,
  512 + required int endTime,
  513 + }) async {
  514 + return storedBatches
  515 + .expand((batch) => batch)
  516 + .where(
  517 + (item) =>
  518 + item.dataType == dataType &&
  519 + item.dataTime >= startTime &&
  520 + item.dataTime <= endTime,
  521 + )
  522 + .toList(growable: false);
272 } 523 }
273 } 524 }
274 525
@@ -290,13 +541,17 @@ class _FakeOhosHarmonyRawDataClient implements OhosHarmonyRawDataClient { @@ -290,13 +541,17 @@ class _FakeOhosHarmonyRawDataClient implements OhosHarmonyRawDataClient {
290 _FakeOhosHarmonyRawDataClient({ 541 _FakeOhosHarmonyRawDataClient({
291 HmHealthData? healthData, 542 HmHealthData? healthData,
292 HmSleepData? sleepData, 543 HmSleepData? sleepData,
  544 + HmWorkoutData? workoutData,
293 }) : _healthData = healthData ?? HmHealthData(), 545 }) : _healthData = healthData ?? HmHealthData(),
294 - _sleepData = sleepData ?? HmSleepData(); 546 + _sleepData = sleepData ?? HmSleepData(),
  547 + _workoutData = workoutData ?? HmWorkoutData();
295 548
296 final HmHealthData _healthData; 549 final HmHealthData _healthData;
297 final HmSleepData _sleepData; 550 final HmSleepData _sleepData;
  551 + final HmWorkoutData _workoutData;
298 final List<_HealthCall> healthCalls = <_HealthCall>[]; 552 final List<_HealthCall> healthCalls = <_HealthCall>[];
299 final List<_SleepCall> sleepCalls = <_SleepCall>[]; 553 final List<_SleepCall> sleepCalls = <_SleepCall>[];
  554 + final List<_SleepCall> workoutCalls = <_SleepCall>[];
300 555
301 @override 556 @override
302 Future<AppResult<HmHealthData>> getHealthData( 557 Future<AppResult<HmHealthData>> getHealthData(
@@ -327,6 +582,20 @@ class _FakeOhosHarmonyRawDataClient implements OhosHarmonyRawDataClient { @@ -327,6 +582,20 @@ class _FakeOhosHarmonyRawDataClient implements OhosHarmonyRawDataClient {
327 ); 582 );
328 return AppSuccess(_sleepData); 583 return AppSuccess(_sleepData);
329 } 584 }
  585 +
  586 + @override
  587 + Future<AppResult<HmWorkoutData>> getWorkoutData(
  588 + int startDate,
  589 + int endDate,
  590 + ) async {
  591 + workoutCalls.add(
  592 + _SleepCall(
  593 + startDate: startDate,
  594 + endDate: endDate,
  595 + ),
  596 + );
  597 + return AppSuccess(_workoutData);
  598 + }
330 } 599 }
331 600
332 class _HealthCall { 601 class _HealthCall {