Showing
15 changed files
with
1455 additions
and
307 deletions
| @@ -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() { |
lib/data/models/harmony/hm_workout_data.dart
0 → 100644
| 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 { |
-
Please register or login to post a comment